From ca1ed746d6863e5300f38a22214950447b6d69e1 Mon Sep 17 00:00:00 2001 From: Thuy Nguyen <145430420+nntthuy-axonivy@users.noreply.github.com> Date: Tue, 17 Sep 2024 15:26:14 +0700 Subject: [PATCH 01/20] MARP-1015: Fix sonar (#158) --- marketplace-ui/src/app/app.component.ts | 2 +- .../src/app/shared/components/header/header.component.ts | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/marketplace-ui/src/app/app.component.ts b/marketplace-ui/src/app/app.component.ts index 2d15456b4..720a809d8 100644 --- a/marketplace-ui/src/app/app.component.ts +++ b/marketplace-ui/src/app/app.component.ts @@ -24,7 +24,7 @@ export class AppComponent { loadingService = inject(LoadingService); routingQueryParamService = inject(RoutingQueryParamService); route = inject(ActivatedRoute); - isMobileMenuCollapsed: boolean = true; + isMobileMenuCollapsed = true; constructor(private readonly router: Router) {} diff --git a/marketplace-ui/src/app/shared/components/header/header.component.ts b/marketplace-ui/src/app/shared/components/header/header.component.ts index 2812dbb8e..dee44f476 100644 --- a/marketplace-ui/src/app/shared/components/header/header.component.ts +++ b/marketplace-ui/src/app/shared/components/header/header.component.ts @@ -1,11 +1,5 @@ import { CommonModule } from '@angular/common'; -import { - Component, - EventEmitter, - inject, - Output, - model -} from '@angular/core'; +import { Component, inject, model } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { LanguageService } from '../../../core/services/language/language.service'; @@ -31,7 +25,6 @@ import { ThemeSelectionComponent } from './theme-selection/theme-selection.compo styleUrls: ['./header.component.scss', '../../../app.component.scss'] }) export class HeaderComponent { - selectedNav = '/'; isMobileMenuCollapsed = model(true); From f0eb9bcf4a387d2aa6b600ffcd33aa3728d0c157 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen <127725498+ntqdinh-axonivy@users.noreply.github.com> Date: Wed, 18 Sep 2024 15:55:46 +0700 Subject: [PATCH 02/20] Marp-1100 market website error occurring after choosing snapshot version --- .../product-detail-version-action.component.html | 2 +- .../product-detail/product-detail.component.spec.ts | 11 +++++++++++ .../product-detail/product-detail.component.ts | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) 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 29048377e..b6bb3e8d0 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 @@ -12,7 +12,7 @@ (minimum version 9.2.0)

" - (click)="onUpdateInstallationCountForDesigner()" data-bs-custom-class="custom-tooltip" + data-bs-custom-class="custom-tooltip" [ngClass]="themeService.isDarkMode() ? 'btn-light' : 'btn-primary'"> {{ 'common.product.detail.install.buttonLabel' | translate }} 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 407437495..bcb83a011 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 @@ -248,6 +248,17 @@ describe('ProductDetailComponent', () => { expect(component.getContent('description')).toBeFalse(); }); + it('should return false for any tab when detail content is undefined or null', () => { + component.productModuleContent.set(null as any as ProductModuleContent); + expect(component.getContent('description')).toBeFalse(); + component.productModuleContent.set( + undefined as any as ProductModuleContent + ); + expect(component.getContent('description')).toBeFalse(); + component.productModuleContent.set({} as any as ProductModuleContent); + expect(component.getContent('description')).toBeFalse(); + }); + it('should return false for description when in EN language it is an empty string', () => { const mockContent: ProductModuleContent = { ...MOCK_PRODUCT_MODULE_CONTENT, 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 46737a8dc..491d74e8f 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 @@ -208,7 +208,7 @@ export class ProductDetailComponent { getContent(value: string): boolean { const content = this.productModuleContent(); - if (Object.keys(content).length === 0) { + if (!content || Object.keys(content).length === 0) { return false; } From 8e5bb1b344fdda4acf7c275bfb9fd23c43fa82d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=E1=BA=A7n=20V=C4=A9nh=20Thi=E1=BB=87n=20Ph=C3=BAc?= <143604440+tvtphuc-axonivy@users.noreply.github.com> Date: Thu, 19 Sep 2024 11:18:10 +0700 Subject: [PATCH 03/20] Feature/marp 1058 market website remove using of images url from GitHub in product detail (#159) --- .../product-card/product-card.component.html | 3 ++- .../product-card/product-card.component.ts | 13 +++++++++++-- .../product-detail/product-detail.component.html | 6 ++++-- .../product-detail/product-detail.component.scss | 5 ++++- .../product-detail/product-detail.component.ts | 13 ++++++++++--- .../src/app/shared/constants/common.constant.ts | 4 +++- marketplace-ui/src/app/shared/pipes/logo.pipe.ts | 16 ---------------- 7 files changed, 34 insertions(+), 26 deletions(-) delete mode 100644 marketplace-ui/src/app/shared/pipes/logo.pipe.ts diff --git a/marketplace-ui/src/app/modules/product/product-card/product-card.component.html b/marketplace-ui/src/app/modules/product/product-card/product-card.component.html index dd414407c..687471ce6 100644 --- a/marketplace-ui/src/app/modules/product/product-card/product-card.component.html +++ b/marketplace-ui/src/app/modules/product/product-card/product-card.component.html @@ -6,7 +6,8 @@ class="card-img-top rounded" width="70" height="70" - [ngSrc]="product | logo" + [ngSrc]="logoUrl" + (error)="onLogoError()" [alt]=" product.names | multilingualism: languageService.selectedLanguage() " diff --git a/marketplace-ui/src/app/modules/product/product-card/product-card.component.ts b/marketplace-ui/src/app/modules/product/product-card/product-card.component.ts index 5de39f622..c268a4421 100644 --- a/marketplace-ui/src/app/modules/product/product-card/product-card.component.ts +++ b/marketplace-ui/src/app/modules/product/product-card/product-card.component.ts @@ -4,14 +4,14 @@ import { TranslateModule } from '@ngx-translate/core'; import { LanguageService } from '../../../core/services/language/language.service'; import { ThemeService } from '../../../core/services/theme/theme.service'; import { Product } from '../../../shared/models/product.model'; -import { ProductLogoPipe } from '../../../shared/pipes/logo.pipe'; import { MultilingualismPipe } from '../../../shared/pipes/multilingualism.pipe'; import { ProductComponent } from '../product.component'; +import { DEFAULT_IMAGE_URL } from '../../../shared/constants/common.constant'; @Component({ selector: 'app-product-card', standalone: true, - imports: [CommonModule, ProductLogoPipe, MultilingualismPipe, TranslateModule, NgOptimizedImage], + imports: [CommonModule, MultilingualismPipe, TranslateModule, NgOptimizedImage], templateUrl: './product-card.component.html', styleUrl: './product-card.component.scss' }) @@ -21,4 +21,13 @@ export class ProductCardComponent { isShowInRESTClientEditor = inject(ProductComponent).isRESTClient(); @Input() product!: Product; + logoUrl = DEFAULT_IMAGE_URL; + + ngOnInit(): void { + this.logoUrl = this.product.logoUrl; + } + + onLogoError() { + this.logoUrl = DEFAULT_IMAGE_URL; + } } 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 b20928040..fbb560db5 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 @@ -20,13 +20,15 @@
+ "/>

(false); installationCount = 0; - + logoUrl = DEFAULT_IMAGE_URL; @HostListener('window:popstate', ['$event']) onPopState() { this.activeTab = window.location.hash.split('#tab-')[1]; @@ -147,6 +149,7 @@ export class ProductDetailComponent { this.installationCount = productDetail.installationCount; this.handleProductContentVersion(); this.updateProductDetailActionType(productDetail); + this.logoUrl = productDetail.logoUrl; }); this.productFeedbackService.initFeedbacks(); @@ -155,6 +158,10 @@ export class ProductDetailComponent { this.updateDropdownSelection(); } + onLogoError() { + this.logoUrl = DEFAULT_IMAGE_URL; + } + handleProductContentVersion() { if (this.isEmptyProductContent()) { return; diff --git a/marketplace-ui/src/app/shared/constants/common.constant.ts b/marketplace-ui/src/app/shared/constants/common.constant.ts index ac0d39c6b..ffc6e73ef 100644 --- a/marketplace-ui/src/app/shared/constants/common.constant.ts +++ b/marketplace-ui/src/app/shared/constants/common.constant.ts @@ -204,4 +204,6 @@ export const ERROR_CODES = [ UNDEFINED_ERROR_CODE, NOT_FOUND_ERROR_CODE, INTERNAL_SERVER_ERROR_CODE -]; \ No newline at end of file +]; + +export const DEFAULT_IMAGE_URL = '/assets/images/misc/axonivy-logo-round.png'; diff --git a/marketplace-ui/src/app/shared/pipes/logo.pipe.ts b/marketplace-ui/src/app/shared/pipes/logo.pipe.ts deleted file mode 100644 index 93554c3e6..000000000 --- a/marketplace-ui/src/app/shared/pipes/logo.pipe.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core'; -import { Product } from '../models/product.model'; - -@Pipe({ - standalone: true, - name: 'logo' -}) -export class ProductLogoPipe implements PipeTransform { - transform(product: Product, _args?: []): string { - let logoUrl = product.logoUrl; - if (logoUrl === undefined || logoUrl === '') { - logoUrl = `/assets/images/misc/axonivy-logo-round.png`; - } - return logoUrl; - } -} From a55cbb8db834db94dcb377f3ff17c83c7f763a76 Mon Sep 17 00:00:00 2001 From: Khanh Nguyen <119989010+ndkhanh-axonivy@users.noreply.github.com> Date: Thu, 19 Sep 2024 12:00:23 +0700 Subject: [PATCH 04/20] MARP-1104 Market website portal versions and artifacts aren t shown marketplace website (#162) --- .../axonivy/market/enums/NonStandardProduct.java | 2 +- .../market/service/impl/VersionServiceImpl.java | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) 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 index 0535ce237..4c914204a 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/enums/NonStandardProduct.java +++ b/marketplace-service/src/main/java/com/axonivy/market/enums/NonStandardProduct.java @@ -47,6 +47,6 @@ public enum NonStandardProduct { } public static NonStandardProduct findById(String id) { - return NON_STANDARD_PRODUCT_MAP.getOrDefault(id,DEFAULT); + return NON_STANDARD_PRODUCT_MAP.getOrDefault(id, DEFAULT); } } 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 2f0a81e8d..556bb8e8c 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 @@ -48,6 +48,7 @@ import static com.axonivy.market.constants.ProductJsonConstants.NAME; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; + @Log4j2 @Service @Getter @@ -96,7 +97,8 @@ public List getArtifactsAndVersionToDisplay(String pr this.productId = productId; artifactsFromMeta = getProductMetaArtifacts(productId); - List versionsToDisplay = VersionUtils.getVersionsToDisplay(getVersionsFromMavenArtifacts(), isShowDevVersion, designerVersion); + List versionsToDisplay = VersionUtils.getVersionsToDisplay(getVersionsFromMavenArtifacts(), + isShowDevVersion, designerVersion); proceedDataCache = mavenArtifactVersionRepository.findById(productId).orElse(new MavenArtifactVersion(productId)); metaProductArtifact = artifactsFromMeta.stream() .filter(artifact -> artifact.getArtifactId().endsWith(MavenConstants.PRODUCT_ARTIFACT_POSTFIX)).findAny() @@ -112,17 +114,18 @@ public List getArtifactsAndVersionToDisplay(String pr } @Override - public Map getProductJsonContentByIdAndVersion(String productId, String version){ + public Map getProductJsonContentByIdAndVersion(String productId, String version) { Map result = new HashMap<>(); try { - ProductJsonContent productJsonContent = productJsonContentRepository.findByProductIdAndVersion(productId, version); + ProductJsonContent productJsonContent = productJsonContentRepository.findByProductIdAndVersion(productId, + version); if (ObjectUtils.isEmpty(productJsonContent)) { return new HashMap<>(); } result = mapper.readValue(productJsonContent.getContent(), Map.class); result.computeIfAbsent(NAME, k -> productJsonContent.getName()); - } catch (JsonProcessingException jsonProcessingException){ + } catch (JsonProcessingException jsonProcessingException) { log.error(jsonProcessingException.getMessage()); } return result; @@ -249,7 +252,9 @@ public String getVersionTag(String version) { public String buildProductJsonFilePath() { String pathToProductFolderFromTagContent = metaProductArtifact.getArtifactId(); - GitHubUtils.getNonStandardProductFilePath(productId); + if (NonStandardProduct.DEFAULT != NonStandardProduct.findById(productId)) { + pathToProductFolderFromTagContent = GitHubUtils.getNonStandardProductFilePath(productId); + } productJsonFilePath = String.format(GitHubConstants.PRODUCT_JSON_FILE_PATH_FORMAT, pathToProductFolderFromTagContent); return productJsonFilePath; From bf33dc06e4d89805a1a3747a1eb5f29146afaaee Mon Sep 17 00:00:00 2001 From: Dinh Nguyen <127725498+ntqdinh-axonivy@users.noreply.github.com> Date: Fri, 20 Sep 2024 15:11:55 +0700 Subject: [PATCH 05/20] MARP-1067 marketplace website can not download install artifact snapshot --- .../ArchivedArtifactsComparator.java | 3 +- .../comparator/LatestVersionComparator.java | 23 +- .../comparator/MavenVersionComparator.java | 4 +- .../market/constants/MavenConstants.java | 4 +- .../constants/ProductJsonConstants.java | 1 + .../constants/RequestMappingConstants.java | 1 - .../market/entity/MavenArtifactVersion.java | 2 - .../service/GHAxonIvyProductRepoService.java | 3 + .../impl/GHAxonIvyProductRepoServiceImpl.java | 10 +- .../market/service/VersionService.java | 4 - .../service/impl/VersionServiceImpl.java | 269 ++++++--------- .../com/axonivy/market/util/VersionUtils.java | 16 +- .../service/impl/VersionServiceImplTest.java | 318 +++++------------- ...product-detail-version-action.component.ts | 6 +- 14 files changed, 207 insertions(+), 457 deletions(-) diff --git a/marketplace-service/src/main/java/com/axonivy/market/comparator/ArchivedArtifactsComparator.java b/marketplace-service/src/main/java/com/axonivy/market/comparator/ArchivedArtifactsComparator.java index d5c553ed2..c88a426cb 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/comparator/ArchivedArtifactsComparator.java +++ b/marketplace-service/src/main/java/com/axonivy/market/comparator/ArchivedArtifactsComparator.java @@ -5,10 +5,9 @@ import java.util.Comparator; public class ArchivedArtifactsComparator implements Comparator { - private final LatestVersionComparator comparator = new LatestVersionComparator(); @Override public int compare(ArchivedArtifact artifact1, ArchivedArtifact artifact2) { - return comparator.compare(artifact1.getLastVersion(), artifact2.getLastVersion()); + return MavenVersionComparator.compare(artifact2.getLastVersion(), artifact1.getLastVersion()); } } \ No newline at end of file diff --git a/marketplace-service/src/main/java/com/axonivy/market/comparator/LatestVersionComparator.java b/marketplace-service/src/main/java/com/axonivy/market/comparator/LatestVersionComparator.java index 17d90e397..108036fb0 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/comparator/LatestVersionComparator.java +++ b/marketplace-service/src/main/java/com/axonivy/market/comparator/LatestVersionComparator.java @@ -6,27 +6,6 @@ public class LatestVersionComparator implements Comparator { @Override public int compare(String v1, String v2) { - // Split by "." - String[] parts1 = v1.split("\\."); - String[] parts2 = v2.split("\\."); - - // Compare up to the shorter length - int length = Math.min(parts1.length, parts2.length); - for (int i = 0; i < length; i++) { - try { - int num1 = Integer.parseInt(parts1[i]); - int num2 = Integer.parseInt(parts2[i]); - // Return difference for numeric parts - if (num1 != num2) { - return num2 - num1; - } - // Handle non-numeric parts (e.g., "m229") - } catch (NumberFormatException e) { - return parts2[i].replaceAll("\\D", "").compareTo(parts1[i].replaceAll("\\D", "")); - } - } - - // Versions with more parts are considered larger - return parts2.length - parts1.length; + return MavenVersionComparator.compare(v2, v1); } } 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 d7df9ed85..2c49ebf23 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 @@ -11,12 +11,12 @@ import java.util.regex.Pattern; import static com.axonivy.market.constants.CommonConstants.DASH_SEPARATOR; +import static com.axonivy.market.constants.MavenConstants.MAIN_VERSION_REGEX; import static com.axonivy.market.constants.MavenConstants.SNAPSHOT_VERSION; import static org.apache.commons.lang3.StringUtils.EMPTY; public class MavenVersionComparator { - private static final String MAIN_VERSION_REGEX = "\\."; private static final int GREATER_THAN = 1; private static final int EQUAL = 0; private static final int LESS_THAN = -1; @@ -46,7 +46,7 @@ public static String findHighestMavenVersion(List versions) { return highestVersion; } - private static int compare(String version, String otherVersion) { + public static int compare(String version, String otherVersion) { version = stripLeadingChars(version); otherVersion = stripLeadingChars(otherVersion); String[] versionParts = createMainAndQualifierArray(version); 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 67ec50e5a..0c99d0118 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 @@ -8,9 +8,9 @@ private MavenConstants() { public static final String SNAPSHOT_RELEASE_POSTFIX = "-" + SNAPSHOT_VERSION; public static final String SPRINT_RELEASE_POSTFIX = "-m"; public static final String PRODUCT_ARTIFACT_POSTFIX = "-product"; - public static final String METADATA_URL_FORMAT = "%s/%s/%s/maven-metadata.xml"; public static final String DEFAULT_IVY_MAVEN_BASE_URL = "https://maven.axonivy.com"; - public static final String ARTIFACT_DOWNLOAD_URL_FORMAT = "%s/%s/%s/%s/%s-%s.%s"; + public static final String ARTIFACT_FILE_NAME_FORMAT = "%s-%s.%s"; public static final String ARTIFACT_NAME_FORMAT = "%s (%s)"; public static final String VERSION_EXTRACT_FORMAT_FROM_METADATA_FILE = "//versions/version/text()"; + public static final String MAIN_VERSION_REGEX = "\\."; } diff --git a/marketplace-service/src/main/java/com/axonivy/market/constants/ProductJsonConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/ProductJsonConstants.java index fd63733dd..e0bcaaf1b 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/constants/ProductJsonConstants.java +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/ProductJsonConstants.java @@ -20,6 +20,7 @@ public class ProductJsonConstants { public static final String CUSTOM_ORDER = "customOrder"; public static final String EN_LANGUAGE = "en"; public static final String NAME = "name"; + public static final String DEFAULT_PRODUCT_TYPE = "iar"; private ProductJsonConstants() { } diff --git a/marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java index 8d3124988..95105083e 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java @@ -24,7 +24,6 @@ public class RequestMappingConstants { public static final String INSTALLATION_COUNT_BY_ID = "/installationcount/{id}"; public static final String PRODUCT_JSON_CONTENT_BY_PRODUCT_ID_AND_VERSION = "/{id}/{version}/json"; public static final String VERSIONS_IN_DESIGNER = "/{id}/designerversions"; - public static final String DESIGNER_INSTALLATION_BY_PRODUCT_ID_AND_DESIGNER_VERSION = "/installation/{productId}/designer/{designerVersion}"; public static final String DESIGNER_INSTALLATION_BY_ID = "/installation/{id}/designer"; public static final String CUSTOM_SORT = "custom-sort"; public static final String IMAGE = API + "/image"; diff --git a/marketplace-service/src/main/java/com/axonivy/market/entity/MavenArtifactVersion.java b/marketplace-service/src/main/java/com/axonivy/market/entity/MavenArtifactVersion.java index e7a6a2e92..f4e437225 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/entity/MavenArtifactVersion.java +++ b/marketplace-service/src/main/java/com/axonivy/market/entity/MavenArtifactVersion.java @@ -9,7 +9,6 @@ import java.io.Serial; import java.io.Serializable; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -27,7 +26,6 @@ public class MavenArtifactVersion implements Serializable { @Id private String productId; - private List versions = new ArrayList<>(); private Map> productArtifactWithVersionReleased = new HashMap<>(); public MavenArtifactVersion(String productId) { 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 debe27c42..1a846a105 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 @@ -8,9 +8,12 @@ import org.kohsuke.github.GHTag; import java.io.IOException; +import java.io.InputStream; import java.util.List; public interface GHAxonIvyProductRepoService { + + List extractMavenArtifactsFromContentStream(InputStream contentStream) throws IOException; GHContent getContentFromGHRepoAndTag(String repoName, String filePath, String tagVersion); 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 2d0e673f0..edb0e1155 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 @@ -5,7 +5,6 @@ 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.entity.ProductModuleContent; import com.axonivy.market.entity.ProductJsonContent; @@ -33,7 +32,6 @@ import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; -import javax.swing.text.html.Option; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -77,12 +75,16 @@ public GHAxonIvyProductRepoServiceImpl(GitHubService gitHubService, ImageService @Override public List convertProductJsonToMavenProductInfo(GHContent content) throws IOException { - List artifacts = new ArrayList<>(); InputStream contentStream = extractedContentStream(content); if (Objects.isNull(contentStream)) { - return artifacts; + return new ArrayList<>(); } + return extractMavenArtifactsFromContentStream(contentStream); + } + @Override + public List extractMavenArtifactsFromContentStream(InputStream contentStream) throws IOException { + List artifacts = new ArrayList<>(); JsonNode rootNode = objectMapper.readTree(contentStream); JsonNode installersNode = rootNode.path(ProductJsonConstants.INSTALLERS); 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 e4800c5a9..b06fd5344 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 @@ -7,10 +7,6 @@ public interface VersionService { - List getVersionsFromArtifactDetails(String repoUrl, String groupId, String artifactId); - - String buildMavenMetadataUrlFromArtifact(String repoUrl, String groupId, String artifactId); - List getArtifactsAndVersionToDisplay(String productId, Boolean isShowDevVersion, String designerVersion); 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 556bb8e8c..6866840d7 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 @@ -1,16 +1,15 @@ package com.axonivy.market.service.impl; import com.axonivy.market.comparator.ArchivedArtifactsComparator; -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.constants.MavenConstants; +import com.axonivy.market.constants.ProductJsonConstants; import com.axonivy.market.controller.ProductDetailsController; import com.axonivy.market.entity.MavenArtifactModel; import com.axonivy.market.entity.MavenArtifactVersion; import com.axonivy.market.entity.Product; import com.axonivy.market.entity.ProductJsonContent; -import com.axonivy.market.enums.NonStandardProduct; import com.axonivy.market.github.model.ArchivedArtifact; import com.axonivy.market.github.model.MavenArtifact; import com.axonivy.market.github.service.GHAxonIvyProductRepoService; @@ -19,22 +18,23 @@ import com.axonivy.market.model.VersionAndUrlModel; import com.axonivy.market.repository.MavenArtifactVersionRepository; import com.axonivy.market.repository.ProductJsonContentRepository; +import com.axonivy.market.repository.ProductModuleContentRepository; import com.axonivy.market.repository.ProductRepository; import com.axonivy.market.service.VersionService; import com.axonivy.market.util.VersionUtils; -import com.axonivy.market.util.XmlReaderUtils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; 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.kohsuke.github.GHContent; import org.springframework.hateoas.Link; 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.Collections; import java.util.HashMap; @@ -44,6 +44,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import static com.axonivy.market.constants.ProductJsonConstants.NAME; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; @@ -51,74 +52,56 @@ @Log4j2 @Service -@Getter public class VersionServiceImpl implements VersionService { private final GHAxonIvyProductRepoService gitHubService; private final MavenArtifactVersionRepository mavenArtifactVersionRepository; - private final ProductRepository productRepository; - private final ProductJsonContentRepository productJsonContentRepository; - @Getter - private String repoName; - private Map> archivedArtifactsMap; - private List artifactsFromMeta; - private MavenArtifactVersion proceedDataCache; - private MavenArtifact metaProductArtifact; - private final LatestVersionComparator latestVersionComparator = new LatestVersionComparator(); - @Getter - private String productJsonFilePath; - private String productId; + private final ProductRepository productRepo; + private final ProductJsonContentRepository productJsonRepo; + private final ProductModuleContentRepository productContentRepo; private final ObjectMapper mapper = new ObjectMapper(); public VersionServiceImpl(GHAxonIvyProductRepoService gitHubService, - MavenArtifactVersionRepository mavenArtifactVersionRepository, ProductRepository productRepository, - ProductJsonContentRepository productJsonContentRepository) { + MavenArtifactVersionRepository mavenArtifactVersionRepository, ProductRepository productRepo, + ProductJsonContentRepository productJsonRepo, ProductModuleContentRepository productContentRepo) { this.gitHubService = gitHubService; this.mavenArtifactVersionRepository = mavenArtifactVersionRepository; - this.productRepository = productRepository; - - this.productJsonContentRepository = productJsonContentRepository; + this.productRepo = productRepo; + this.productJsonRepo = productJsonRepo; + this.productContentRepo = productContentRepo; } - private void resetData() { - repoName = null; - archivedArtifactsMap = new HashMap<>(); - artifactsFromMeta = Collections.emptyList(); - proceedDataCache = null; - metaProductArtifact = null; - productJsonFilePath = null; - productId = null; + public static Map> getArchivedArtifactMapFromProduct( + List artifactsFromMeta) { + Map> result = new HashMap<>(); + artifactsFromMeta.forEach(artifact -> { + List archivedArtifacts = new ArrayList<>( + Optional.ofNullable(artifact.getArchivedArtifacts()).orElse(Collections.emptyList()).stream() + .sorted(new ArchivedArtifactsComparator()).toList()); + Collections.reverse(archivedArtifacts); + result.put(artifact.getArtifactId(), archivedArtifacts); + }); + return result; } public List getArtifactsAndVersionToDisplay(String productId, Boolean isShowDevVersion, String designerVersion) { - List results = new ArrayList<>(); - resetData(); - - this.productId = productId; - artifactsFromMeta = getProductMetaArtifacts(productId); - List versionsToDisplay = VersionUtils.getVersionsToDisplay(getVersionsFromMavenArtifacts(), + List versionsToDisplay = VersionUtils.getVersionsToDisplay(getPersistedVersions(productId), isShowDevVersion, designerVersion); - proceedDataCache = mavenArtifactVersionRepository.findById(productId).orElse(new MavenArtifactVersion(productId)); - metaProductArtifact = artifactsFromMeta.stream() + List artifactsFromMeta = getArtifactsFromMeta(productId); + MavenArtifact productArtifact = artifactsFromMeta.stream() .filter(artifact -> artifact.getArtifactId().endsWith(MavenConstants.PRODUCT_ARTIFACT_POSTFIX)).findAny() .orElse(new MavenArtifact()); - - sanitizeMetaArtifactBeforeHandle(); - - boolean isNewVersionDetected = handleArtifactForVersionToDisplay(versionsToDisplay, results); - if (isNewVersionDetected) { - mavenArtifactVersionRepository.save(proceedDataCache); - } - return results; + artifactsFromMeta.remove(productArtifact); + Map> archivedArtifactsMap = getArchivedArtifactMapFromProduct(artifactsFromMeta); + return handleArtifactForVersionToDisplay(versionsToDisplay, productId, artifactsFromMeta, archivedArtifactsMap); } @Override public Map getProductJsonContentByIdAndVersion(String productId, String version) { Map result = new HashMap<>(); try { - ProductJsonContent productJsonContent = productJsonContentRepository.findByProductIdAndVersion(productId, - version); + ProductJsonContent productJsonContent = productJsonRepo.findByProductIdAndVersion(productId, version); if (ObjectUtils.isEmpty(productJsonContent)) { return new HashMap<>(); } @@ -134,7 +117,7 @@ public Map getProductJsonContentByIdAndVersion(String productId, @Override public List getVersionsForDesigner(String productId) { List versionAndUrlList = new ArrayList<>(); - List versions = productRepository.getReleasedVersionsById(productId); + List versions = productRepo.getReleasedVersionsById(productId); for (String version : versions) { Link link = linkTo( methodOn(ProductDetailsController.class).findProductJsonContent(productId, version)).withSelfRel(); @@ -144,176 +127,122 @@ public List getVersionsForDesigner(String productId) { return versionAndUrlList; } - public boolean handleArtifactForVersionToDisplay(List versionsToDisplay, - List result) { + /** + * This function will combine default artifacts (from product.json) and custom artifacts from (meta.json) + * of each version and return it to user. + * By default, all artifacts model (from product.json) by version to display will be taken from db. + * If new version is detected, new model will be built and save back to db. + **/ + public List handleArtifactForVersionToDisplay(List versionsToDisplay, + String productId, List artifactsFromMeta, + Map> archivedArtifactsMap) { boolean isNewVersionDetected = false; + List results = new ArrayList<>(); + MavenArtifactVersion cache = mavenArtifactVersionRepository.findById(productId) + .orElse(new MavenArtifactVersion(productId)); for (String version : versionsToDisplay) { - List artifactsInVersion = convertMavenArtifactsToModels(artifactsFromMeta, version); - List productArtifactModels = proceedDataCache.getProductArtifactWithVersionReleased() - .get(version); + List artifactsInVersion = convertArtifactsToModels(artifactsFromMeta, version, + archivedArtifactsMap); + List productArtifactModels = cache.getProductArtifactWithVersionReleased().get(version); if (productArtifactModels == null) { isNewVersionDetected = true; - productArtifactModels = updateArtifactsInVersionWithProductArtifact(version); + productArtifactModels = convertArtifactsToModels(getMavenArtifactsFromProductJsonByVersion(version, productId), + version, archivedArtifactsMap); + cache.getProductArtifactWithVersionReleased().put(version, productArtifactModels); } artifactsInVersion.addAll(productArtifactModels); - result.add(new MavenArtifactVersionModel(version, artifactsInVersion.stream().distinct().toList())); + results.add(new MavenArtifactVersionModel(version, artifactsInVersion.stream().distinct().toList())); } - return isNewVersionDetected; - } - - public List updateArtifactsInVersionWithProductArtifact(String version) { - List productArtifactModels = convertMavenArtifactsToModels(getProductJsonByVersion(version), - version); - proceedDataCache.getVersions().add(version); - proceedDataCache.getProductArtifactWithVersionReleased().put(version, productArtifactModels); - return productArtifactModels; - } - - public List getProductMetaArtifacts(String productId) { - Product productInfo = productRepository.findById(productId).orElse(new Product()); - String fullRepoName = productInfo.getRepositoryName(); - if (StringUtils.isNotEmpty(fullRepoName)) { - repoName = getRepoNameFromMarketRepo(fullRepoName); + if (isNewVersionDetected) { + mavenArtifactVersionRepository.save(cache); } - return Optional.ofNullable(productInfo.getArtifacts()).orElse(new ArrayList<>()); + return results; } - public void sanitizeMetaArtifactBeforeHandle() { - artifactsFromMeta.remove(metaProductArtifact); - artifactsFromMeta.forEach(artifact -> { - List archivedArtifacts = new ArrayList<>( - Optional.ofNullable(artifact.getArchivedArtifacts()).orElse(Collections.emptyList()).stream() - .sorted(new ArchivedArtifactsComparator()).toList()); - Collections.reverse(archivedArtifacts); - archivedArtifactsMap.put(artifact.getArtifactId(), archivedArtifacts); - }); + public List getArtifactsFromMeta(String productId) { + Product productInfo = productRepo.findById(productId).orElse(new Product()); + return Optional.ofNullable(productInfo.getArtifacts()).orElse(new ArrayList<>()); } - public List getVersionsFromMavenArtifacts() { + public List getPersistedVersions(String productId) { + var product = productRepo.findById(productId); Set versions = new HashSet<>(); - for (MavenArtifact artifact : artifactsFromMeta) { - versions.addAll( - getVersionsFromArtifactDetails(artifact.getRepoUrl(), artifact.getGroupId(), artifact.getArtifactId())); - Optional.ofNullable(artifact.getArchivedArtifacts()).orElse(Collections.emptyList()).forEach( - archivedArtifact -> versions.addAll( - getVersionsFromArtifactDetails(artifact.getRepoUrl(), archivedArtifact.getGroupId(), - archivedArtifact.getArtifactId()))); + if (product.isPresent()) { + versions.addAll(product.get().getReleasedVersions()); } - List versionList = new ArrayList<>(versions); - versionList.sort(new LatestVersionComparator()); - return versionList; - } - - @Override - public List getVersionsFromArtifactDetails(String repoUrl, String groupId, String artifactID) { - List versions = new ArrayList<>(); - String baseUrl = buildMavenMetadataUrlFromArtifact(repoUrl, groupId, artifactID); - if (StringUtils.isNotBlank(baseUrl)) { - versions.addAll(XmlReaderUtils.readXMLFromUrl(baseUrl)); + if (CollectionUtils.isEmpty(versions)) { + versions.addAll(productContentRepo.findTagsByProductId(productId)); + versions = versions.stream().map(VersionUtils::convertTagToVersion).collect(Collectors.toSet()); } - return versions; + return new ArrayList<>(versions); } - @Override - public String buildMavenMetadataUrlFromArtifact(String repoUrl, String groupId, String artifactID) { - if (StringUtils.isAnyBlank(groupId, artifactID)) { - return StringUtils.EMPTY; + public List getMavenArtifactsFromProductJsonByVersion(String version, String productId) { + ProductJsonContent productJson = productJsonRepo.findByProductIdAndVersion(productId, version); + if (Objects.isNull(productJson) || StringUtils.isBlank(productJson.getContent())) { + return new ArrayList<>(); } - repoUrl = Optional.ofNullable(repoUrl).orElse(MavenConstants.DEFAULT_IVY_MAVEN_BASE_URL); - groupId = groupId.replace(CommonConstants.DOT_SEPARATOR, CommonConstants.SLASH); - return String.format(MavenConstants.METADATA_URL_FORMAT, repoUrl, groupId, artifactID); - } - - public List getProductJsonByVersion(String version) { - List result = new ArrayList<>(); - String versionTag = getVersionTag(version); - productJsonFilePath = buildProductJsonFilePath(); + InputStream contentStream = IOUtils.toInputStream(productJson.getContent(), StandardCharsets.UTF_8); try { - GHContent productJsonContent = gitHubService.getContentFromGHRepoAndTag(repoName, productJsonFilePath, - versionTag); - if (Objects.isNull(productJsonContent)) { - return result; - } - result = gitHubService.convertProductJsonToMavenProductInfo(productJsonContent); + return gitHubService.extractMavenArtifactsFromContentStream(contentStream); } catch (IOException e) { - log.warn("Can not get the product.json from repo {} by path in {} version {}", repoName, productJsonFilePath, - versionTag); + log.error("Can not get maven artifacts from Product.json of {} - version {}:{}", productId, version, + e.getMessage()); + return new ArrayList<>(); } - return result; - } - - public String getVersionTag(String version) { - String versionTag = "v" + version; - if (NonStandardProduct.PORTAL.getId().equals(productId)) { - versionTag = version; - } - return versionTag; - } - - public String buildProductJsonFilePath() { - String pathToProductFolderFromTagContent = metaProductArtifact.getArtifactId(); - if (NonStandardProduct.DEFAULT != NonStandardProduct.findById(productId)) { - pathToProductFolderFromTagContent = GitHubUtils.getNonStandardProductFilePath(productId); - } - productJsonFilePath = String.format(GitHubConstants.PRODUCT_JSON_FILE_PATH_FORMAT, - pathToProductFolderFromTagContent); - return productJsonFilePath; } - public MavenArtifactModel convertMavenArtifactToModel(MavenArtifact artifact, String version) { + public MavenArtifactModel convertMavenArtifactToModel(MavenArtifact artifact, String version, + List archivedArtifacts) { String artifactName = artifact.getName(); if (StringUtils.isBlank(artifactName)) { artifactName = GitHubUtils.convertArtifactIdToName(artifact.getArtifactId()); } - artifact.setType(Optional.ofNullable(artifact.getType()).orElse("iar")); + artifact.setType(StringUtils.defaultIfBlank(artifact.getType(), ProductJsonConstants.DEFAULT_PRODUCT_TYPE)); artifactName = String.format(MavenConstants.ARTIFACT_NAME_FORMAT, artifactName, artifact.getType()); - return new MavenArtifactModel(artifactName, buildDownloadUrlFromArtifactAndVersion(artifact, version), - artifact.getIsProductArtifact()); + return new MavenArtifactModel(artifactName, + buildDownloadUrlFromArtifactAndVersion(artifact, version, archivedArtifacts), artifact.getIsProductArtifact()); } - public List convertMavenArtifactsToModels(List artifacts, String version) { + public List convertArtifactsToModels(List artifacts, String version, + Map> archivedArtifactsMap) { List results = new ArrayList<>(); if (!CollectionUtils.isEmpty(artifacts)) { for (MavenArtifact artifact : artifacts) { - MavenArtifactModel mavenArtifactModel = convertMavenArtifactToModel(artifact, version); + MavenArtifactModel mavenArtifactModel = convertMavenArtifactToModel(artifact, version, + archivedArtifactsMap.get(artifact.getArtifactId())); results.add(mavenArtifactModel); } } return results; } - public String buildDownloadUrlFromArtifactAndVersion(MavenArtifact artifact, String version) { + public String buildDownloadUrlFromArtifactAndVersion(MavenArtifact artifact, String version, + List archivedArtifacts) { String groupIdByVersion = artifact.getGroupId(); String artifactIdByVersion = artifact.getArtifactId(); - String repoUrl = Optional.ofNullable(artifact.getRepoUrl()).orElse(MavenConstants.DEFAULT_IVY_MAVEN_BASE_URL); - ArchivedArtifact archivedArtifactBestMatchVersion = findArchivedArtifactInfoBestMatchWithVersion( - artifact.getArtifactId(), version); + String repoUrl = StringUtils.defaultIfBlank(artifact.getRepoUrl(), MavenConstants.DEFAULT_IVY_MAVEN_BASE_URL); + ArchivedArtifact archivedArtifactBestMatchVersion = findArchivedArtifactInfoBestMatchWithVersion(version, + archivedArtifacts); if (Objects.nonNull(archivedArtifactBestMatchVersion)) { groupIdByVersion = archivedArtifactBestMatchVersion.getGroupId(); artifactIdByVersion = archivedArtifactBestMatchVersion.getArtifactId(); } groupIdByVersion = groupIdByVersion.replace(CommonConstants.DOT_SEPARATOR, CommonConstants.SLASH); - return String.format(MavenConstants.ARTIFACT_DOWNLOAD_URL_FORMAT, repoUrl, groupIdByVersion, artifactIdByVersion, - version, artifactIdByVersion, version, artifact.getType()); + String artifactFileName = String.format(MavenConstants.ARTIFACT_FILE_NAME_FORMAT, artifactIdByVersion, version, + artifact.getType()); + return String.join(CommonConstants.SLASH, repoUrl, groupIdByVersion, artifactIdByVersion, version, + artifactFileName); } - public ArchivedArtifact findArchivedArtifactInfoBestMatchWithVersion(String artifactId, String version) { - List archivedArtifacts = archivedArtifactsMap.get(artifactId); - + public ArchivedArtifact findArchivedArtifactInfoBestMatchWithVersion(String version, + List archivedArtifacts) { if (CollectionUtils.isEmpty(archivedArtifacts)) { return null; } - for (ArchivedArtifact archivedArtifact : archivedArtifacts) { - if (latestVersionComparator.compare(archivedArtifact.getLastVersion(), version) <= 0) { - return archivedArtifact; - } - } - return null; - } - - public String getRepoNameFromMarketRepo(String fullRepoName) { - String[] repoNamePart = fullRepoName.split("/"); - return repoNamePart[repoNamePart.length - 1]; + return archivedArtifacts.stream() + .filter(archivedArtifact -> MavenVersionComparator.compare(archivedArtifact.getLastVersion(), version) >= 0) + .findAny().orElse(null); } } 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 ea4bb7963..6164e2dc1 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 @@ -1,6 +1,7 @@ package com.axonivy.market.util; 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.constants.MavenConstants; @@ -25,7 +26,7 @@ private VersionUtils() { public static List getVersionsToDisplay(List versions, Boolean isShowDevVersion, String designerVersion) { Stream versionStream = versions.stream(); if (StringUtils.isNotBlank(designerVersion)) { - return versionStream.filter(version -> isMatchWithDesignerVersion(version, designerVersion)).toList(); + return versionStream.filter(version -> isMatchWithDesignerVersion(version, designerVersion)).sorted(new LatestVersionComparator()).toList(); } if (BooleanUtils.isTrue(isShowDevVersion)) { return versionStream.filter(version -> isOfficialVersionOrUnReleasedDevVersion(versions, version)) @@ -37,8 +38,7 @@ public static List getVersionsToDisplay(List versions, Boolean i public static String getBestMatchVersion(List versions, String designerVersion) { String bestMatchVersion = versions.stream().filter(version -> StringUtils.equals(version, designerVersion)).findAny().orElse(null); if(StringUtils.isBlank(bestMatchVersion)){ - LatestVersionComparator comparator = new LatestVersionComparator(); - bestMatchVersion = versions.stream().filter(version -> comparator.compare(version, designerVersion) > 0 && isReleasedVersion(version)).findAny().orElse(null); + bestMatchVersion = versions.stream().filter(version -> MavenVersionComparator.compare(version, designerVersion) < 0 && isReleasedVersion(version)).findAny().orElse(null); } if (StringUtils.isBlank(bestMatchVersion)) { bestMatchVersion = versions.stream().filter(VersionUtils::isReleasedVersion).findAny().orElse(CollectionUtils.firstElement(versions)); @@ -51,7 +51,9 @@ public static boolean isOfficialVersionOrUnReleasedDevVersion(List versi return true; } String bugfixVersion; - if (isSnapshotVersion(version)) { + if (!isValidFormatReleasedVersion(version)) { + return false; + } else if (isSnapshotVersion(version)) { bugfixVersion = getBugfixVersion(version.replace(MavenConstants.SNAPSHOT_RELEASE_POSTFIX, StringUtils.EMPTY)); } else { bugfixVersion = getBugfixVersion(version.split(MavenConstants.SPRINT_RELEASE_POSTFIX)[0]); @@ -69,8 +71,12 @@ public static boolean isSprintVersion(String version) { return version.contains(MavenConstants.SPRINT_RELEASE_POSTFIX); } + public static boolean isValidFormatReleasedVersion(String version) { + return StringUtils.isNumeric(version.split(MavenConstants.MAIN_VERSION_REGEX)[0]); + } + public static boolean isReleasedVersion(String version) { - return !(isSprintVersion(version) || isSnapshotVersion(version)); + return !(isSprintVersion(version) || isSnapshotVersion(version)) && isValidFormatReleasedVersion(version); } public static boolean isMatchWithDesignerVersion(String version, String designerVersion) { 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 a45e845fa..4efe92693 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 @@ -1,5 +1,6 @@ package com.axonivy.market.service.impl; +import com.axonivy.market.constants.CommonConstants; import com.axonivy.market.constants.MavenConstants; import com.axonivy.market.entity.MavenArtifactModel; import com.axonivy.market.entity.MavenArtifactVersion; @@ -8,26 +9,21 @@ import com.axonivy.market.github.model.ArchivedArtifact; import com.axonivy.market.github.model.MavenArtifact; import com.axonivy.market.github.service.GHAxonIvyProductRepoService; -import com.axonivy.market.model.MavenArtifactVersionModel; import com.axonivy.market.model.VersionAndUrlModel; import com.axonivy.market.repository.MavenArtifactVersionRepository; import com.axonivy.market.repository.ProductJsonContentRepository; +import com.axonivy.market.repository.ProductModuleContentRepository; import com.axonivy.market.repository.ProductRepository; -import com.axonivy.market.util.XmlReaderUtils; -import org.apache.commons.lang3.StringUtils; -import org.assertj.core.api.Fail; 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.kohsuke.github.GHContent; 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; -import org.springframework.test.util.ReflectionTestUtils; + import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -37,15 +33,13 @@ import java.util.Optional; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class VersionServiceImplTest { - private String repoName; private Map> archivedArtifactsMap; private List artifactsFromMeta; - private MavenArtifactVersion proceedDataCache; private MavenArtifact metaProductArtifact; @Spy @InjectMocks @@ -63,17 +57,14 @@ class VersionServiceImplTest { @Mock private ProductJsonContentRepository productJsonContentRepository; + @Mock + private ProductModuleContentRepository productModuleContentRepository; + @BeforeEach() void prepareBeforeTest() { archivedArtifactsMap = new HashMap<>(); artifactsFromMeta = new ArrayList<>(); metaProductArtifact = new MavenArtifact(); - proceedDataCache = new MavenArtifactVersion(); - repoName = StringUtils.EMPTY; - ReflectionTestUtils.setField(versionService, "archivedArtifactsMap", archivedArtifactsMap); - ReflectionTestUtils.setField(versionService, "artifactsFromMeta", artifactsFromMeta); - ReflectionTestUtils.setField(versionService, "proceedDataCache", proceedDataCache); - ReflectionTestUtils.setField(versionService, "metaProductArtifact", metaProductArtifact); } private void setUpArtifactFromMeta() { @@ -93,13 +84,12 @@ void testGetArtifactsAndVersionToDisplay() { String productId = "adobe-acrobat-sign-connector"; String targetVersion = "10.0.10"; setUpArtifactFromMeta(); - when(versionService.getProductMetaArtifacts(Mockito.anyString())).thenReturn(artifactsFromMeta); - when(versionService.getVersionsFromMavenArtifacts()).thenReturn( - List.of(targetVersion)); + when(versionService.getArtifactsFromMeta(Mockito.anyString())).thenReturn(artifactsFromMeta); + when(productModuleContentRepository.findTagsByProductId(productId)).thenReturn(List.of("v10.0.10")); when(mavenArtifactVersionRepository.findById(Mockito.anyString())).thenReturn(Optional.empty()); ArrayList artifactsInVersion = new ArrayList<>(); artifactsInVersion.add(new MavenArtifactModel()); - when(versionService.convertMavenArtifactsToModels(Mockito.anyList(), Mockito.anyString())).thenReturn( + when(versionService.convertArtifactsToModels(Mockito.anyList(), Mockito.anyString(), Mockito.any())).thenReturn( artifactsInVersion); Assertions.assertEquals(1, versionService.getArtifactsAndVersionToDisplay(productId, false, targetVersion).size()); @@ -110,218 +100,58 @@ void testGetArtifactsAndVersionToDisplay() { } @Test - void testHandleArtifactForVersionToDisplay() { - String newVersionDetected = "10.0.10"; - List result = new ArrayList<>(); - List versionsToDisplay = List.of(newVersionDetected); - ReflectionTestUtils.setField(versionService, "productId", "adobe-acrobat-connector"); - Assertions.assertTrue(versionService.handleArtifactForVersionToDisplay(versionsToDisplay, result)); - Assertions.assertEquals(1, result.size()); - Assertions.assertEquals(newVersionDetected, result.get(0).getVersion()); - Assertions.assertEquals(0, result.get(0).getArtifactsByVersion().size()); - - result = new ArrayList<>(); - ArrayList artifactsInVersion = new ArrayList<>(); - artifactsInVersion.add(new MavenArtifactModel()); - when(versionService.convertMavenArtifactsToModels(Mockito.anyList(), Mockito.anyString())).thenReturn( - artifactsInVersion); - Assertions.assertFalse(versionService.handleArtifactForVersionToDisplay(versionsToDisplay, result)); - Assertions.assertEquals(1, result.size()); - Assertions.assertEquals(1, result.get(0).getArtifactsByVersion().size()); - } - - @Test - void testGetProductMetaArtifacts() { + void testGetArtifactsFromMeta() { Product product = new Product(); MavenArtifact artifact1 = new MavenArtifact(); MavenArtifact artifact2 = new MavenArtifact(); List artifacts = List.of(artifact1, artifact2); product.setArtifacts(artifacts); when(productRepository.findById(Mockito.anyString())).thenReturn(Optional.of(product)); - List result = versionService.getProductMetaArtifacts("portal"); + List result = versionService.getArtifactsFromMeta("portal"); Assertions.assertEquals(artifacts, result); - Assertions.assertNull(versionService.getRepoName()); - - product.setRepositoryName("/market/portal"); - versionService.getProductMetaArtifacts("portal"); - Assertions.assertEquals("portal", versionService.getRepoName()); - } - - @Test - void testUpdateArtifactsInVersionWithProductArtifact() { - String version = "10.0.10"; - ReflectionTestUtils.setField(versionService, "productId", "adobe-acrobat-connector"); - MavenArtifactModel artifactModel = new MavenArtifactModel(); - List mockMavenArtifactModels = List.of(artifactModel); - when(versionService.getProductJsonByVersion(Mockito.anyString())).thenReturn(List.of(new MavenArtifact())); - when(versionService.convertMavenArtifactsToModels(Mockito.anyList(), Mockito.anyString())).thenReturn( - mockMavenArtifactModels); - Assertions.assertEquals(mockMavenArtifactModels, - versionService.updateArtifactsInVersionWithProductArtifact(version)); - Assertions.assertEquals(1, proceedDataCache.getVersions().size()); - Assertions.assertEquals(1, proceedDataCache.getProductArtifactWithVersionReleased().size()); - Assertions.assertEquals(version, proceedDataCache.getVersions().get(0)); - } - - @Test - void testSanitizeMetaArtifactBeforeHandle() { - setUpArtifactFromMeta(); - String groupId = "com.axonivy.connector.adobe.acrobat.sign"; - String archivedArtifactId1 = "adobe-acrobat-sign-connector"; - String archivedArtifactId2 = "adobe-acrobat-sign-connector"; - ArchivedArtifact archivedArtifact1 = new ArchivedArtifact("10.0.10", groupId, archivedArtifactId1); - ArchivedArtifact archivedArtifact2 = new ArchivedArtifact("10.0.20", groupId, archivedArtifactId2); - artifactsFromMeta.get(1).setArchivedArtifacts(List.of(archivedArtifact2, archivedArtifact1)); - - versionService.sanitizeMetaArtifactBeforeHandle(); - String artifactId = "adobe-acrobat-sign-connector"; - - Assertions.assertEquals(1, artifactsFromMeta.size()); - Assertions.assertEquals(1, archivedArtifactsMap.size()); - Assertions.assertEquals(2, archivedArtifactsMap.get(artifactId).size()); - Assertions.assertEquals(archivedArtifact1, archivedArtifactsMap.get(artifactId).get(0)); - } - - - @Test - void getVersionsFromMavenArtifacts() { - String repoUrl = "https://maven.axonivy.com"; - String groupId = "com.axonivy.connector.adobe.acrobat.sign"; - String artifactId = "adobe-acrobat-sign-connector"; - String archivedArtifactId = "adobe-sign-connector"; - artifactsFromMeta.add(new MavenArtifact(repoUrl, null, groupId, artifactId, null, null, null, null)); - ArrayList versionFromArtifact = new ArrayList<>(); - versionFromArtifact.add("10.0.6"); - versionFromArtifact.add("10.0.5"); - versionFromArtifact.add("10.0.4"); - - when(versionService.getVersionsFromArtifactDetails(repoUrl, groupId, artifactId)).thenReturn(versionFromArtifact); - Assertions.assertEquals(versionService.getVersionsFromMavenArtifacts(), versionFromArtifact); - - List archivedArtifacts = List.of(new ArchivedArtifact("10.0.9", groupId, archivedArtifactId)); - ArrayList versionFromArchivedArtifact = new ArrayList<>(); - versionFromArchivedArtifact.add("10.0.3"); - versionFromArchivedArtifact.add("10.0.2"); - versionFromArchivedArtifact.add("10.0.1"); - artifactsFromMeta.get(0).setArchivedArtifacts(archivedArtifacts); - when(versionService.getVersionsFromArtifactDetails(repoUrl, groupId, archivedArtifactId)).thenReturn( - versionFromArchivedArtifact); - versionFromArtifact.addAll(versionFromArchivedArtifact); - Assertions.assertEquals(versionService.getVersionsFromMavenArtifacts(), versionFromArtifact); } @Test - void testGetVersionsFromArtifactDetails() { + void testGetMavenArtifactsFromProductJsonByVersion() throws IOException { + when(productJsonContentRepository.findByProductIdAndVersion("adobe-acrobat-connector", "10.0.20")).thenReturn(null); - String repoUrl = "https://maven.axonivy.com"; - String groupId = "com.axonivy.connector.adobe.acrobat.sign"; - String artifactId = "adobe-acrobat-sign-connector"; + Assertions.assertEquals(0, + versionService.getMavenArtifactsFromProductJsonByVersion("10.0.20", "adobe-acrobat-connector").size()); - ArrayList versionFromArtifact = new ArrayList<>(); - versionFromArtifact.add("10.0.16"); - versionFromArtifact.add("10.0.18"); - versionFromArtifact.add("10.0.19"); - versionFromArtifact.add("10.0.20"); - versionFromArtifact.add("10.0.21"); - versionFromArtifact.add("10.0.22"); - versionFromArtifact.add("10.0.23"); - versionFromArtifact.add("10.0.24"); - versionFromArtifact.add("10.0.25"); - - try (MockedStatic xmlUtils = Mockito.mockStatic(XmlReaderUtils.class)) { - xmlUtils.when(() -> XmlReaderUtils.readXMLFromUrl(Mockito.anyString())).thenReturn(versionFromArtifact); - Assertions.assertEquals(versionService.getVersionsFromArtifactDetails(repoUrl, null, null), new ArrayList<>()); - Assertions.assertEquals(versionService.getVersionsFromArtifactDetails(repoUrl, groupId, artifactId), - versionFromArtifact); - } - } + String jsonContent = "{ \"installers\": [{ \"id\": \"maven-import\", \"data\": { \"repositories\": [{ \"url\": \"http://repo.url\" }], \"projects\": [], \"dependencies\": [] } }] }"; + ProductJsonContent productJson = new ProductJsonContent(); + productJson.setContent(jsonContent); + when(productJsonContentRepository.findByProductIdAndVersion("adobe-acrobat-connector", "10.0.20")).thenReturn( + productJson); - @Test - void testBuildMavenMetadataUrlFromArtifact() { - String repoUrl = "https://maven.axonivy.com"; - String groupId = "com.axonivy.connector.adobe.acrobat.sign"; - String artifactId = "adobe-acrobat-sign-connector"; - String metadataUrl = "https://maven.axonivy.com/com/axonivy/connector/adobe/acrobat/sign/adobe-acrobat-sign-connector/maven-metadata.xml"; - Assertions.assertEquals(StringUtils.EMPTY, - versionService.buildMavenMetadataUrlFromArtifact(repoUrl, null, artifactId)); - Assertions.assertEquals(StringUtils.EMPTY, versionService.buildMavenMetadataUrlFromArtifact(repoUrl, groupId, null), - StringUtils.EMPTY); - Assertions.assertEquals(metadataUrl, - versionService.buildMavenMetadataUrlFromArtifact(repoUrl, groupId, artifactId)); - } + List expectedArtifacts = new ArrayList<>(); + when(gitHubService.extractMavenArtifactsFromContentStream(Mockito.any())).thenReturn(expectedArtifacts); - @Test - void testGetProductJsonByVersion() { - String targetArtifactId = "adobe-acrobat-sign-connector"; - String targetGroupId = "com.axonivy.connector.adobe.acrobat"; - GHContent mockContent = mock(GHContent.class); - repoName = "adobe-acrobat-sign-connector"; - ReflectionTestUtils.setField(versionService, "repoName", repoName); - ReflectionTestUtils.setField(versionService, "productId", "adobe-acrobat-connector"); - MavenArtifact productArtifact = new MavenArtifact("https://maven.axonivy.com", null, targetGroupId, - targetArtifactId, "iar", null, true, null); - - metaProductArtifact.setRepoUrl("https://maven.axonivy.com"); - metaProductArtifact.setGroupId(targetGroupId); - metaProductArtifact.setArtifactId(targetArtifactId); - when(gitHubService.getContentFromGHRepoAndTag(Mockito.anyString(), Mockito.anyString(), - Mockito.anyString())).thenReturn(null); - Assertions.assertEquals(0, versionService.getProductJsonByVersion("10.0.20").size()); - - metaProductArtifact.setGroupId("com.axonivy.connector.adobe.acrobat.connector"); - when(gitHubService.getContentFromGHRepoAndTag(Mockito.anyString(), Mockito.anyString(), - Mockito.anyString())).thenReturn(mockContent); - - try { - when(gitHubService.convertProductJsonToMavenProductInfo(mockContent)).thenReturn(List.of(productArtifact)); - Assertions.assertEquals(1, versionService.getProductJsonByVersion("10.0.20").size()); - - when(gitHubService.convertProductJsonToMavenProductInfo(mockContent)).thenThrow( - new IOException("Mock IO Exception")); - Assertions.assertEquals(0, versionService.getProductJsonByVersion("10.0.20").size()); - } catch (IOException e) { - Fail.fail("Mock setup should not throw an exception"); - } - } + List actualArtifacts = versionService.getMavenArtifactsFromProductJsonByVersion("10.0.20", + "adobe-acrobat-connector"); - @Test - void testConvertMavenArtifactToModel() { - String downloadUrl = "https://maven.axonivy.com/com/axonivy/connector/adobe/acrobat/sign/adobe-acrobat-sign-connector/10.0.21/adobe-acrobat-sign-connector-10.0.21.iar"; - String artifactName = "Adobe Acrobat Sign Connector (iar)"; + Assertions.assertEquals(expectedArtifacts, actualArtifacts); + verify(productJsonContentRepository, Mockito.times(2)).findByProductIdAndVersion("adobe-acrobat-connector", + "10.0.20"); + verify(gitHubService).extractMavenArtifactsFromContentStream(Mockito.any()); - MavenArtifact targetArtifact = new MavenArtifact(null, null, "com.axonivy.connector.adobe.acrobat.sign", - "adobe-acrobat-sign-connector", null, null, null, null); - - // Assert case handle artifact without name - MavenArtifactModel result = versionService.convertMavenArtifactToModel(targetArtifact, "10.0.21"); - MavenArtifactModel expectedResult = new MavenArtifactModel(artifactName, downloadUrl, null); - Assertions.assertEquals(expectedResult.getName(), result.getName()); - Assertions.assertEquals(expectedResult.getDownloadUrl(), result.getDownloadUrl()); - - // Assert case handle artifact with name - artifactName = "Adobe Connector"; - String expectedArtifactName = "Adobe Connector (iar)"; - targetArtifact.setName(artifactName); - result = versionService.convertMavenArtifactToModel(targetArtifact, "10.0.21"); - expectedResult = new MavenArtifactModel(artifactName, downloadUrl, null); - Assertions.assertEquals(expectedArtifactName, result.getName()); - Assertions.assertEquals(expectedResult.getDownloadUrl(), result.getDownloadUrl()); } @Test - void testConvertMavenArtifactsToModels() { + void testConvertArtifactsToModels() { // Assert case param is empty - List result = versionService.convertMavenArtifactsToModels(Collections.emptyList(), "10.0.21"); + List result = versionService.convertArtifactsToModels(Collections.emptyList(), "10.0.21", + new HashMap<>()); Assertions.assertEquals(Collections.emptyList(), result); // Assert case param is null - result = versionService.convertMavenArtifactsToModels(null, "10.0.21"); + result = versionService.convertArtifactsToModels(null, "10.0.21", new HashMap<>()); Assertions.assertEquals(Collections.emptyList(), result); // Assert case param is a list with existed element MavenArtifact targetArtifact = new MavenArtifact(null, null, "com.axonivy.connector.adobe.acrobat.sign", "adobe-acrobat-sign-connector", null, null, null, null); - result = versionService.convertMavenArtifactsToModels(List.of(targetArtifact), "10.0.21"); + result = versionService.convertArtifactsToModels(List.of(targetArtifact), "10.0.21", new HashMap<>()); Assertions.assertEquals(1, result.size()); } @@ -333,12 +163,15 @@ void testBuildDownloadUrlFromArtifactAndVersion() { MavenArtifact targetArtifact = new MavenArtifact(null, null, targetGroupId, targetArtifactId, "iar", null, null, null); String targetVersion = "10.0.10"; + String artifactFileName = String.format(MavenConstants.ARTIFACT_FILE_NAME_FORMAT, targetArtifactId, targetVersion, + "iar"); // Assert case without archived artifact - String expectedResult = String.format(MavenConstants.ARTIFACT_DOWNLOAD_URL_FORMAT, + String expectedResult = String.join(CommonConstants.SLASH, MavenConstants.DEFAULT_IVY_MAVEN_BASE_URL, "com/axonivy/connector", targetArtifactId, targetVersion, - targetArtifactId, targetVersion, "iar"); - String result = versionService.buildDownloadUrlFromArtifactAndVersion(targetArtifact, targetVersion); + artifactFileName); + String result = versionService.buildDownloadUrlFromArtifactAndVersion(targetArtifact, targetVersion, + Collections.emptyList()); Assertions.assertEquals(expectedResult, result); // Assert case with artifact not match & use custom repo @@ -346,12 +179,15 @@ void testBuildDownloadUrlFromArtifactAndVersion() { "adobe-connector"); ArchivedArtifact adobeArchivedArtifactVersion8 = new ArchivedArtifact("10.0.8", "com.axonivy.adobe.sign.connector", "adobe-sign-connector"); - archivedArtifactsMap.put(targetArtifactId, List.of(adobeArchivedArtifactVersion9, adobeArchivedArtifactVersion8)); String customRepoUrl = "https://nexus.axonivy.com"; targetArtifact.setRepoUrl(customRepoUrl); - result = versionService.buildDownloadUrlFromArtifactAndVersion(targetArtifact, targetVersion); - expectedResult = String.format(MavenConstants.ARTIFACT_DOWNLOAD_URL_FORMAT, customRepoUrl, "com/axonivy/connector", - targetArtifactId, targetVersion, targetArtifactId, targetVersion, "iar"); + result = versionService.buildDownloadUrlFromArtifactAndVersion(targetArtifact, targetVersion, + List.of(adobeArchivedArtifactVersion9, adobeArchivedArtifactVersion8)); + artifactFileName = String.format(MavenConstants.ARTIFACT_FILE_NAME_FORMAT, targetArtifactId, targetVersion, + "iar"); + expectedResult = String.join(CommonConstants.SLASH, + customRepoUrl, "com/axonivy/connector", targetArtifactId, targetVersion, + artifactFileName); Assertions.assertEquals(expectedResult, result); // Assert case with artifact got matching archived artifact & use custom file @@ -359,9 +195,13 @@ void testBuildDownloadUrlFromArtifactAndVersion() { String customType = "zip"; targetArtifact.setType(customType); targetVersion = "10.0.9"; - result = versionService.buildDownloadUrlFromArtifactAndVersion(targetArtifact, "10.0.9"); - expectedResult = String.format(MavenConstants.ARTIFACT_DOWNLOAD_URL_FORMAT, customRepoUrl, - "com/axonivy/adobe/connector", "adobe-connector", targetVersion, "adobe-connector", targetVersion, customType); + result = versionService.buildDownloadUrlFromArtifactAndVersion(targetArtifact, "10.0.9", + List.of(adobeArchivedArtifactVersion9, adobeArchivedArtifactVersion8)); + artifactFileName = String.format(MavenConstants.ARTIFACT_FILE_NAME_FORMAT, "adobe-connector", targetVersion, + customType); + expectedResult = String.join(CommonConstants.SLASH, + customRepoUrl, "com/axonivy/adobe/connector", "adobe-connector", targetVersion, + artifactFileName); Assertions.assertEquals(expectedResult, result); } @@ -369,8 +209,8 @@ void testBuildDownloadUrlFromArtifactAndVersion() { void testFindArchivedArtifactInfoBestMatchWithVersion() { String targetArtifactId = "adobe-acrobat-sign-connector"; String targetVersion = "10.0.10"; - ArchivedArtifact result = versionService.findArchivedArtifactInfoBestMatchWithVersion(targetArtifactId, - targetVersion); + ArchivedArtifact result = versionService.findArchivedArtifactInfoBestMatchWithVersion( + targetVersion, Collections.emptyList()); Assertions.assertNull(result); // Assert case with target version higher than all of latest version from @@ -383,12 +223,14 @@ void testFindArchivedArtifactInfoBestMatchWithVersion() { archivedArtifacts.add(adobeArchivedArtifactVersion8); archivedArtifacts.add(adobeArchivedArtifactVersion9); archivedArtifactsMap.put(targetArtifactId, archivedArtifacts); - result = versionService.findArchivedArtifactInfoBestMatchWithVersion(targetArtifactId, targetVersion); + result = versionService.findArchivedArtifactInfoBestMatchWithVersion(targetVersion, + archivedArtifacts); Assertions.assertNull(result); // Assert case with target version less than all of latest version from archived // artifact list - result = versionService.findArchivedArtifactInfoBestMatchWithVersion(targetArtifactId, "10.0.7"); + result = versionService.findArchivedArtifactInfoBestMatchWithVersion("10.0.7", + archivedArtifacts); Assertions.assertEquals(adobeArchivedArtifactVersion8, result); // Assert case with target version is in range of archived artifact list @@ -396,26 +238,11 @@ void testFindArchivedArtifactInfoBestMatchWithVersion() { "adobe-sign-connector"); archivedArtifactsMap.get(targetArtifactId).add(adobeArchivedArtifactVersion10); - result = versionService.findArchivedArtifactInfoBestMatchWithVersion(targetArtifactId, targetVersion); + result = versionService.findArchivedArtifactInfoBestMatchWithVersion(targetVersion, + archivedArtifacts); Assertions.assertEquals(adobeArchivedArtifactVersion10, result); } - @Test - void testGetRepoNameFromMarketRepo() { - String defaultRepositoryName = "market/adobe-acrobat-connector"; - String expectedRepoName = "adobe-acrobat-connector"; - String result = versionService.getRepoNameFromMarketRepo(defaultRepositoryName); - Assertions.assertEquals(expectedRepoName, result); - - defaultRepositoryName = "market/utils/adobe-acrobat-connector"; - result = versionService.getRepoNameFromMarketRepo(defaultRepositoryName); - Assertions.assertEquals(expectedRepoName, result); - - defaultRepositoryName = "adobe-acrobat-connector"; - result = versionService.getRepoNameFromMarketRepo(defaultRepositoryName); - Assertions.assertEquals(expectedRepoName, result); - } - @Test void testGetVersionsForDesigner() { Mockito.when(productRepository.getReleasedVersionsById(anyString())) @@ -425,10 +252,10 @@ void testGetVersionsForDesigner() { Assertions.assertEquals(result.stream().map(VersionAndUrlModel::getVersion).toList(), List.of("11.3.0", "11.1.1", "11.1.0", "10.0.2")); - Assertions.assertEquals("/api/product-details/11.3.0/11.3.0/json", result.get(0).getUrl()); - Assertions.assertEquals("/api/product-details/11.3.0/11.1.1/json", result.get(1).getUrl()); - Assertions.assertEquals("/api/product-details/11.3.0/11.1.0/json", result.get(2).getUrl()); - Assertions.assertEquals("/api/product-details/11.3.0/10.0.2/json", result.get(3).getUrl()); + Assertions.assertTrue(result.get(0).getUrl().endsWith("/api/product-details/11.3.0/11.3.0/json")); + Assertions.assertTrue(result.get(1).getUrl().endsWith("/api/product-details/11.3.0/11.1.1/json")); + Assertions.assertTrue(result.get(2).getUrl().endsWith("/api/product-details/11.3.0/11.1.0/json")); + Assertions.assertTrue(result.get(3).getUrl().endsWith("/api/product-details/11.3.0/10.0.2/json")); } @Test @@ -475,4 +302,17 @@ void testGetProductJsonContentByIdAndVersion_noResult() { Assertions.assertEquals(new HashMap<>(), result); } + + @Test + void testgetPersistedVersions() { + String mockProductId = "portal"; + String mockVersion = "10.0.1"; + Product mocProduct = new Product(); + mocProduct.setId(mockProductId); + mocProduct.setReleasedVersions(List.of(mockVersion)); + when(productRepository.findById(mockProductId)).thenReturn(Optional.of(mocProduct)); + Assertions.assertEquals(1, versionService.getPersistedVersions(mockProductId).size()); + Assertions.assertEquals(mockVersion, versionService.getPersistedVersions(mockProductId).get(0)); + + } } 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 a6a59488a..a791b9730 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 @@ -99,10 +99,8 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { artifact.label = artifact.name; } }); - if (this.artifacts().length !== 0) { - this.selectedArtifactName = this.artifacts()[0].name ?? ''; - this.selectedArtifact = this.artifacts()[0].downloadUrl ?? ''; - } + this.selectedArtifactName = this.artifacts()[0]?.name ?? ''; + this.selectedArtifact = this.artifacts()[0]?.downloadUrl ?? ''; } onSelectVersionInDesigner(version: string) { From a68837da966979d0db6e85cfeb99cf4a7df76a94 Mon Sep 17 00:00:00 2001 From: Hoan Nguyen Date: Fri, 20 Sep 2024 16:41:32 +0700 Subject: [PATCH 06/20] Introduced editor config --- marketplace-service/.editorconfig | 687 ++++++++++++++++++++++++++++++ 1 file changed, 687 insertions(+) create mode 100644 marketplace-service/.editorconfig diff --git a/marketplace-service/.editorconfig b/marketplace-service/.editorconfig new file mode 100644 index 000000000..9b7bd7afe --- /dev/null +++ b/marketplace-service/.editorconfig @@ -0,0 +1,687 @@ +[*] +charset = utf-8 +end_of_line = crlf +indent_size = 4 +indent_style = space +insert_final_newline = false +max_line_length = 120 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = true +ij_smart_tabs = false +ij_visual_guides = +ij_wrap_on_typing = true + +[*.dcl] +ij_declarative_keep_indents_on_empty_lines = false + +[*.java] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 4 +ij_java_align_consecutive_assignments = false +ij_java_align_consecutive_variable_declarations = false +ij_java_align_group_field_declarations = false +ij_java_align_multiline_annotation_parameters = false +ij_java_align_multiline_array_initializer_expression = false +ij_java_align_multiline_assignment = false +ij_java_align_multiline_binary_operation = false +ij_java_align_multiline_chained_methods = false +ij_java_align_multiline_deconstruction_list_components = true +ij_java_align_multiline_extends_list = false +ij_java_align_multiline_for = true +ij_java_align_multiline_method_parentheses = false +ij_java_align_multiline_parameters = false +ij_java_align_multiline_parameters_in_calls = false +ij_java_align_multiline_parenthesized_expression = false +ij_java_align_multiline_records = true +ij_java_align_multiline_resources = true +ij_java_align_multiline_ternary_operation = false +ij_java_align_multiline_text_blocks = false +ij_java_align_multiline_throws_list = false +ij_java_align_subsequent_simple_methods = false +ij_java_align_throws_keyword = false +ij_java_align_types_in_multi_catch = true +ij_java_annotation_parameter_wrap = off +ij_java_array_initializer_new_line_after_left_brace = false +ij_java_array_initializer_right_brace_on_new_line = false +ij_java_array_initializer_wrap = off +ij_java_assert_statement_colon_on_next_line = false +ij_java_assert_statement_wrap = off +ij_java_assignment_wrap = off +ij_java_binary_operation_sign_on_next_line = false +ij_java_binary_operation_wrap = off +ij_java_blank_lines_after_anonymous_class_header = 0 +ij_java_blank_lines_after_class_header = 0 +ij_java_blank_lines_after_imports = 1 +ij_java_blank_lines_after_package = 1 +ij_java_blank_lines_around_class = 1 +ij_java_blank_lines_around_field = 0 +ij_java_blank_lines_around_field_in_interface = 0 +ij_java_blank_lines_around_initializer = 1 +ij_java_blank_lines_around_method = 1 +ij_java_blank_lines_around_method_in_interface = 1 +ij_java_blank_lines_before_class_end = 0 +ij_java_blank_lines_before_imports = 1 +ij_java_blank_lines_before_method_body = 0 +ij_java_blank_lines_before_package = 0 +ij_java_block_brace_style = end_of_line +ij_java_block_comment_add_space = false +ij_java_block_comment_at_first_column = true +ij_java_builder_methods = +ij_java_call_parameters_new_line_after_left_paren = false +ij_java_call_parameters_right_paren_on_new_line = false +ij_java_call_parameters_wrap = normal +ij_java_case_statement_on_separate_line = true +ij_java_catch_on_new_line = false +ij_java_class_annotation_wrap = split_into_lines +ij_java_class_brace_style = end_of_line +ij_java_class_count_to_use_import_on_demand = 5 +ij_java_class_names_in_javadoc = 1 +ij_java_deconstruction_list_wrap = normal +ij_java_do_not_indent_top_level_class_members = false +ij_java_do_not_wrap_after_single_annotation = false +ij_java_do_not_wrap_after_single_annotation_in_parameter = false +ij_java_do_while_brace_force = never +ij_java_doc_add_blank_line_after_description = true +ij_java_doc_add_blank_line_after_param_comments = false +ij_java_doc_add_blank_line_after_return = false +ij_java_doc_add_p_tag_on_empty_lines = true +ij_java_doc_align_exception_comments = true +ij_java_doc_align_param_comments = true +ij_java_doc_do_not_wrap_if_one_line = false +ij_java_doc_enable_formatting = true +ij_java_doc_enable_leading_asterisks = true +ij_java_doc_indent_on_continuation = false +ij_java_doc_keep_empty_lines = true +ij_java_doc_keep_empty_parameter_tag = true +ij_java_doc_keep_empty_return_tag = true +ij_java_doc_keep_empty_throws_tag = true +ij_java_doc_keep_invalid_tags = true +ij_java_doc_param_description_on_new_line = false +ij_java_doc_preserve_line_breaks = false +ij_java_doc_use_throws_not_exception_tag = true +ij_java_else_on_new_line = false +ij_java_enum_constants_wrap = off +ij_java_enum_field_annotation_wrap = off +ij_java_extends_keyword_wrap = off +ij_java_extends_list_wrap = off +ij_java_field_annotation_wrap = split_into_lines +ij_java_field_name_prefix = +ij_java_field_name_suffix = +ij_java_finally_on_new_line = false +ij_java_for_brace_force = never +ij_java_for_statement_new_line_after_left_paren = false +ij_java_for_statement_right_paren_on_new_line = false +ij_java_for_statement_wrap = off +ij_java_generate_final_locals = false +ij_java_generate_final_parameters = false +ij_java_generate_use_type_annotation_before_type = true +ij_java_if_brace_force = never +ij_java_imports_layout = *,|,javax.**,java.**,|,$* +ij_java_indent_case_from_switch = true +ij_java_insert_inner_class_imports = false +ij_java_insert_override_annotation = true +ij_java_keep_blank_lines_before_right_brace = 2 +ij_java_keep_blank_lines_between_package_declaration_and_header = 2 +ij_java_keep_blank_lines_in_code = 2 +ij_java_keep_blank_lines_in_declarations = 2 +ij_java_keep_builder_methods_indents = false +ij_java_keep_control_statement_in_one_line = true +ij_java_keep_first_column_comment = true +ij_java_keep_indents_on_empty_lines = false +ij_java_keep_line_breaks = true +ij_java_keep_multiple_expressions_in_one_line = false +ij_java_keep_simple_blocks_in_one_line = true +ij_java_keep_simple_classes_in_one_line = false +ij_java_keep_simple_lambdas_in_one_line = false +ij_java_keep_simple_methods_in_one_line = true +ij_java_label_indent_absolute = false +ij_java_label_indent_size = 0 +ij_java_lambda_brace_style = end_of_line +ij_java_layout_static_imports_separately = true +ij_java_line_comment_add_space = false +ij_java_line_comment_add_space_on_reformat = false +ij_java_line_comment_at_first_column = true +ij_java_local_variable_name_prefix = +ij_java_local_variable_name_suffix = +ij_java_method_annotation_wrap = split_into_lines +ij_java_method_brace_style = end_of_line +ij_java_method_call_chain_wrap = off +ij_java_method_parameters_new_line_after_left_paren = false +ij_java_method_parameters_right_paren_on_new_line = false +ij_java_method_parameters_wrap = normal +ij_java_modifier_list_wrap = false +ij_java_multi_catch_types_wrap = normal +ij_java_names_count_to_use_import_on_demand = 3 +ij_java_new_line_after_lparen_in_annotation = false +ij_java_new_line_after_lparen_in_deconstruction_pattern = true +ij_java_new_line_after_lparen_in_record_header = false +ij_java_new_line_when_body_is_presented = false +ij_java_packages_to_use_import_on_demand = java.awt.*,javax.swing.* +ij_java_parameter_annotation_wrap = off +ij_java_parameter_name_prefix = +ij_java_parameter_name_suffix = +ij_java_parentheses_expression_new_line_after_left_paren = false +ij_java_parentheses_expression_right_paren_on_new_line = false +ij_java_place_assignment_sign_on_next_line = false +ij_java_prefer_longer_names = true +ij_java_prefer_parameters_wrap = false +ij_java_record_components_wrap = normal +ij_java_repeat_annotations = +ij_java_repeat_synchronized = true +ij_java_replace_instanceof_and_cast = false +ij_java_replace_null_check = true +ij_java_replace_sum_lambda_with_method_ref = true +ij_java_resource_list_new_line_after_left_paren = false +ij_java_resource_list_right_paren_on_new_line = false +ij_java_resource_list_wrap = off +ij_java_rparen_on_new_line_in_annotation = false +ij_java_rparen_on_new_line_in_deconstruction_pattern = true +ij_java_rparen_on_new_line_in_record_header = false +ij_java_space_after_closing_angle_bracket_in_type_argument = false +ij_java_space_after_colon = true +ij_java_space_after_comma = true +ij_java_space_after_comma_in_type_arguments = true +ij_java_space_after_for_semicolon = true +ij_java_space_after_quest = true +ij_java_space_after_type_cast = true +ij_java_space_before_annotation_array_initializer_left_brace = false +ij_java_space_before_annotation_parameter_list = false +ij_java_space_before_array_initializer_left_brace = false +ij_java_space_before_catch_keyword = true +ij_java_space_before_catch_left_brace = true +ij_java_space_before_catch_parentheses = true +ij_java_space_before_class_left_brace = true +ij_java_space_before_colon = true +ij_java_space_before_colon_in_foreach = true +ij_java_space_before_comma = false +ij_java_space_before_deconstruction_list = false +ij_java_space_before_do_left_brace = true +ij_java_space_before_else_keyword = true +ij_java_space_before_else_left_brace = true +ij_java_space_before_finally_keyword = true +ij_java_space_before_finally_left_brace = true +ij_java_space_before_for_left_brace = true +ij_java_space_before_for_parentheses = true +ij_java_space_before_for_semicolon = false +ij_java_space_before_if_left_brace = true +ij_java_space_before_if_parentheses = true +ij_java_space_before_method_call_parentheses = false +ij_java_space_before_method_left_brace = true +ij_java_space_before_method_parentheses = false +ij_java_space_before_opening_angle_bracket_in_type_parameter = false +ij_java_space_before_quest = true +ij_java_space_before_switch_left_brace = true +ij_java_space_before_switch_parentheses = true +ij_java_space_before_synchronized_left_brace = true +ij_java_space_before_synchronized_parentheses = true +ij_java_space_before_try_left_brace = true +ij_java_space_before_try_parentheses = true +ij_java_space_before_type_parameter_list = false +ij_java_space_before_while_keyword = true +ij_java_space_before_while_left_brace = true +ij_java_space_before_while_parentheses = true +ij_java_space_inside_one_line_enum_braces = false +ij_java_space_within_empty_array_initializer_braces = false +ij_java_space_within_empty_method_call_parentheses = false +ij_java_space_within_empty_method_parentheses = false +ij_java_spaces_around_additive_operators = true +ij_java_spaces_around_annotation_eq = true +ij_java_spaces_around_assignment_operators = true +ij_java_spaces_around_bitwise_operators = true +ij_java_spaces_around_equality_operators = true +ij_java_spaces_around_lambda_arrow = true +ij_java_spaces_around_logical_operators = true +ij_java_spaces_around_method_ref_dbl_colon = false +ij_java_spaces_around_multiplicative_operators = true +ij_java_spaces_around_relational_operators = true +ij_java_spaces_around_shift_operators = true +ij_java_spaces_around_type_bounds_in_type_parameters = true +ij_java_spaces_around_unary_operator = false +ij_java_spaces_inside_block_braces_when_body_is_present = false +ij_java_spaces_within_angle_brackets = false +ij_java_spaces_within_annotation_parentheses = false +ij_java_spaces_within_array_initializer_braces = false +ij_java_spaces_within_braces = false +ij_java_spaces_within_brackets = false +ij_java_spaces_within_cast_parentheses = false +ij_java_spaces_within_catch_parentheses = false +ij_java_spaces_within_deconstruction_list = false +ij_java_spaces_within_for_parentheses = false +ij_java_spaces_within_if_parentheses = false +ij_java_spaces_within_method_call_parentheses = false +ij_java_spaces_within_method_parentheses = false +ij_java_spaces_within_parentheses = false +ij_java_spaces_within_record_header = false +ij_java_spaces_within_switch_parentheses = false +ij_java_spaces_within_synchronized_parentheses = false +ij_java_spaces_within_try_parentheses = false +ij_java_spaces_within_while_parentheses = false +ij_java_special_else_if_treatment = true +ij_java_static_field_name_prefix = +ij_java_static_field_name_suffix = +ij_java_subclass_name_prefix = +ij_java_subclass_name_suffix = Impl +ij_java_switch_expressions_wrap = normal +ij_java_ternary_operation_signs_on_next_line = false +ij_java_ternary_operation_wrap = off +ij_java_test_name_prefix = +ij_java_test_name_suffix = Test +ij_java_throws_keyword_wrap = off +ij_java_throws_list_wrap = off +ij_java_use_external_annotations = false +ij_java_use_fq_class_names = false +ij_java_use_relative_indents = false +ij_java_use_single_class_imports = true +ij_java_variable_annotation_wrap = off +ij_java_visibility = public +ij_java_while_brace_force = never +ij_java_while_on_new_line = false +ij_java_wrap_comments = false +ij_java_wrap_first_method_in_call_chain = false +ij_java_wrap_long_lines = true +ij_java_wrap_semicolon_after_call_chain = false + +[*.properties] +ij_properties_align_group_field_declarations = false +ij_properties_keep_blank_lines = false +ij_properties_key_value_delimiter = equals +ij_properties_spaces_around_key_value_delimiter = false + +[.editorconfig] +ij_editorconfig_align_group_field_declarations = false +ij_editorconfig_space_after_colon = false +ij_editorconfig_space_after_comma = true +ij_editorconfig_space_before_colon = false +ij_editorconfig_space_before_comma = false +ij_editorconfig_spaces_around_assignment_operators = true + +[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.jspx,*.pom,*.rng,*.tagx,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] +ij_xml_align_attributes = true +ij_xml_align_text = false +ij_xml_attribute_wrap = normal +ij_xml_block_comment_add_space = false +ij_xml_block_comment_at_first_column = true +ij_xml_keep_blank_lines = 2 +ij_xml_keep_indents_on_empty_lines = false +ij_xml_keep_line_breaks = true +ij_xml_keep_line_breaks_in_text = true +ij_xml_keep_whitespaces = false +ij_xml_keep_whitespaces_around_cdata = preserve +ij_xml_keep_whitespaces_inside_cdata = false +ij_xml_line_comment_at_first_column = true +ij_xml_space_after_tag_name = false +ij_xml_space_around_equals_in_attribute = false +ij_xml_space_inside_empty_tag = false +ij_xml_text_wrap = normal + +[{*.bash,*.sh,*.zsh}] +indent_size = 2 +tab_width = 2 +ij_shell_binary_ops_start_line = false +ij_shell_keep_column_alignment_padding = false +ij_shell_minify_program = false +ij_shell_redirect_followed_by_space = false +ij_shell_switch_cases_indented = false +ij_shell_use_unix_line_separator = true + +[{*.gant,*.groovy,*.gy}] +ij_groovy_align_group_field_declarations = false +ij_groovy_align_multiline_array_initializer_expression = false +ij_groovy_align_multiline_assignment = false +ij_groovy_align_multiline_binary_operation = false +ij_groovy_align_multiline_chained_methods = false +ij_groovy_align_multiline_extends_list = false +ij_groovy_align_multiline_for = true +ij_groovy_align_multiline_list_or_map = true +ij_groovy_align_multiline_method_parentheses = false +ij_groovy_align_multiline_parameters = true +ij_groovy_align_multiline_parameters_in_calls = false +ij_groovy_align_multiline_resources = true +ij_groovy_align_multiline_ternary_operation = false +ij_groovy_align_multiline_throws_list = false +ij_groovy_align_named_args_in_map = true +ij_groovy_align_throws_keyword = false +ij_groovy_array_initializer_new_line_after_left_brace = false +ij_groovy_array_initializer_right_brace_on_new_line = false +ij_groovy_array_initializer_wrap = off +ij_groovy_assert_statement_wrap = off +ij_groovy_assignment_wrap = off +ij_groovy_binary_operation_wrap = off +ij_groovy_blank_lines_after_class_header = 0 +ij_groovy_blank_lines_after_imports = 1 +ij_groovy_blank_lines_after_package = 1 +ij_groovy_blank_lines_around_class = 1 +ij_groovy_blank_lines_around_field = 0 +ij_groovy_blank_lines_around_field_in_interface = 0 +ij_groovy_blank_lines_around_method = 1 +ij_groovy_blank_lines_around_method_in_interface = 1 +ij_groovy_blank_lines_before_imports = 1 +ij_groovy_blank_lines_before_method_body = 0 +ij_groovy_blank_lines_before_package = 0 +ij_groovy_block_brace_style = end_of_line +ij_groovy_block_comment_add_space = false +ij_groovy_block_comment_at_first_column = true +ij_groovy_call_parameters_new_line_after_left_paren = false +ij_groovy_call_parameters_right_paren_on_new_line = false +ij_groovy_call_parameters_wrap = off +ij_groovy_catch_on_new_line = false +ij_groovy_class_annotation_wrap = split_into_lines +ij_groovy_class_brace_style = end_of_line +ij_groovy_class_count_to_use_import_on_demand = 5 +ij_groovy_do_while_brace_force = never +ij_groovy_else_on_new_line = false +ij_groovy_enable_groovydoc_formatting = true +ij_groovy_enum_constants_wrap = off +ij_groovy_extends_keyword_wrap = off +ij_groovy_extends_list_wrap = off +ij_groovy_field_annotation_wrap = split_into_lines +ij_groovy_finally_on_new_line = false +ij_groovy_for_brace_force = never +ij_groovy_for_statement_new_line_after_left_paren = false +ij_groovy_for_statement_right_paren_on_new_line = false +ij_groovy_for_statement_wrap = off +ij_groovy_ginq_general_clause_wrap_policy = 2 +ij_groovy_ginq_having_wrap_policy = 1 +ij_groovy_ginq_indent_having_clause = true +ij_groovy_ginq_indent_on_clause = true +ij_groovy_ginq_on_wrap_policy = 1 +ij_groovy_ginq_space_after_keyword = true +ij_groovy_if_brace_force = never +ij_groovy_import_annotation_wrap = 2 +ij_groovy_imports_layout = *,|,javax.**,java.**,|,$* +ij_groovy_indent_case_from_switch = true +ij_groovy_indent_label_blocks = true +ij_groovy_insert_inner_class_imports = false +ij_groovy_keep_blank_lines_before_right_brace = 2 +ij_groovy_keep_blank_lines_in_code = 2 +ij_groovy_keep_blank_lines_in_declarations = 2 +ij_groovy_keep_control_statement_in_one_line = true +ij_groovy_keep_first_column_comment = true +ij_groovy_keep_indents_on_empty_lines = false +ij_groovy_keep_line_breaks = true +ij_groovy_keep_multiple_expressions_in_one_line = false +ij_groovy_keep_simple_blocks_in_one_line = false +ij_groovy_keep_simple_classes_in_one_line = true +ij_groovy_keep_simple_lambdas_in_one_line = true +ij_groovy_keep_simple_methods_in_one_line = true +ij_groovy_label_indent_absolute = false +ij_groovy_label_indent_size = 0 +ij_groovy_lambda_brace_style = end_of_line +ij_groovy_layout_static_imports_separately = true +ij_groovy_line_comment_add_space = false +ij_groovy_line_comment_add_space_on_reformat = false +ij_groovy_line_comment_at_first_column = true +ij_groovy_method_annotation_wrap = split_into_lines +ij_groovy_method_brace_style = end_of_line +ij_groovy_method_call_chain_wrap = off +ij_groovy_method_parameters_new_line_after_left_paren = false +ij_groovy_method_parameters_right_paren_on_new_line = false +ij_groovy_method_parameters_wrap = off +ij_groovy_modifier_list_wrap = false +ij_groovy_names_count_to_use_import_on_demand = 3 +ij_groovy_packages_to_use_import_on_demand = java.awt.*,javax.swing.* +ij_groovy_parameter_annotation_wrap = off +ij_groovy_parentheses_expression_new_line_after_left_paren = false +ij_groovy_parentheses_expression_right_paren_on_new_line = false +ij_groovy_prefer_parameters_wrap = false +ij_groovy_resource_list_new_line_after_left_paren = false +ij_groovy_resource_list_right_paren_on_new_line = false +ij_groovy_resource_list_wrap = off +ij_groovy_space_after_assert_separator = true +ij_groovy_space_after_colon = true +ij_groovy_space_after_comma = true +ij_groovy_space_after_comma_in_type_arguments = true +ij_groovy_space_after_for_semicolon = true +ij_groovy_space_after_quest = true +ij_groovy_space_after_type_cast = true +ij_groovy_space_before_annotation_parameter_list = false +ij_groovy_space_before_array_initializer_left_brace = false +ij_groovy_space_before_assert_separator = false +ij_groovy_space_before_catch_keyword = true +ij_groovy_space_before_catch_left_brace = true +ij_groovy_space_before_catch_parentheses = true +ij_groovy_space_before_class_left_brace = true +ij_groovy_space_before_closure_left_brace = true +ij_groovy_space_before_colon = true +ij_groovy_space_before_comma = false +ij_groovy_space_before_do_left_brace = true +ij_groovy_space_before_else_keyword = true +ij_groovy_space_before_else_left_brace = true +ij_groovy_space_before_finally_keyword = true +ij_groovy_space_before_finally_left_brace = true +ij_groovy_space_before_for_left_brace = true +ij_groovy_space_before_for_parentheses = true +ij_groovy_space_before_for_semicolon = false +ij_groovy_space_before_if_left_brace = true +ij_groovy_space_before_if_parentheses = true +ij_groovy_space_before_method_call_parentheses = false +ij_groovy_space_before_method_left_brace = true +ij_groovy_space_before_method_parentheses = false +ij_groovy_space_before_quest = true +ij_groovy_space_before_record_parentheses = false +ij_groovy_space_before_switch_left_brace = true +ij_groovy_space_before_switch_parentheses = true +ij_groovy_space_before_synchronized_left_brace = true +ij_groovy_space_before_synchronized_parentheses = true +ij_groovy_space_before_try_left_brace = true +ij_groovy_space_before_try_parentheses = true +ij_groovy_space_before_while_keyword = true +ij_groovy_space_before_while_left_brace = true +ij_groovy_space_before_while_parentheses = true +ij_groovy_space_in_named_argument = true +ij_groovy_space_in_named_argument_before_colon = false +ij_groovy_space_within_empty_array_initializer_braces = false +ij_groovy_space_within_empty_method_call_parentheses = false +ij_groovy_spaces_around_additive_operators = true +ij_groovy_spaces_around_assignment_operators = true +ij_groovy_spaces_around_bitwise_operators = true +ij_groovy_spaces_around_equality_operators = true +ij_groovy_spaces_around_lambda_arrow = true +ij_groovy_spaces_around_logical_operators = true +ij_groovy_spaces_around_multiplicative_operators = true +ij_groovy_spaces_around_regex_operators = true +ij_groovy_spaces_around_relational_operators = true +ij_groovy_spaces_around_shift_operators = true +ij_groovy_spaces_within_annotation_parentheses = false +ij_groovy_spaces_within_array_initializer_braces = false +ij_groovy_spaces_within_braces = true +ij_groovy_spaces_within_brackets = false +ij_groovy_spaces_within_cast_parentheses = false +ij_groovy_spaces_within_catch_parentheses = false +ij_groovy_spaces_within_for_parentheses = false +ij_groovy_spaces_within_gstring_injection_braces = false +ij_groovy_spaces_within_if_parentheses = false +ij_groovy_spaces_within_list_or_map = false +ij_groovy_spaces_within_method_call_parentheses = false +ij_groovy_spaces_within_method_parentheses = false +ij_groovy_spaces_within_parentheses = false +ij_groovy_spaces_within_switch_parentheses = false +ij_groovy_spaces_within_synchronized_parentheses = false +ij_groovy_spaces_within_try_parentheses = false +ij_groovy_spaces_within_tuple_expression = false +ij_groovy_spaces_within_while_parentheses = false +ij_groovy_special_else_if_treatment = true +ij_groovy_ternary_operation_wrap = off +ij_groovy_throws_keyword_wrap = off +ij_groovy_throws_list_wrap = off +ij_groovy_use_flying_geese_braces = false +ij_groovy_use_fq_class_names = false +ij_groovy_use_fq_class_names_in_javadoc = true +ij_groovy_use_relative_indents = false +ij_groovy_use_single_class_imports = true +ij_groovy_variable_annotation_wrap = off +ij_groovy_while_brace_force = never +ij_groovy_while_on_new_line = false +ij_groovy_wrap_chain_calls_after_dot = false +ij_groovy_wrap_long_lines = false + +[{*.har,*.json,*.jsonc}] +indent_size = 2 +ij_json_array_wrapping = split_into_lines +ij_json_keep_blank_lines_in_code = 0 +ij_json_keep_indents_on_empty_lines = false +ij_json_keep_line_breaks = true +ij_json_keep_trailing_comma = false +ij_json_object_wrapping = split_into_lines +ij_json_property_alignment = do_not_align +ij_json_space_after_colon = true +ij_json_space_after_comma = true +ij_json_space_before_colon = false +ij_json_space_before_comma = false +ij_json_spaces_within_braces = false +ij_json_spaces_within_brackets = false +ij_json_wrap_long_lines = false + +[{*.htm,*.html,*.sht,*.shtm,*.shtml}] +ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 +ij_html_align_attributes = true +ij_html_align_text = false +ij_html_attribute_wrap = normal +ij_html_block_comment_add_space = false +ij_html_block_comment_at_first_column = true +ij_html_do_not_align_children_of_min_lines = 0 +ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p +ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot +ij_html_enforce_quotes = false +ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var +ij_html_keep_blank_lines = 2 +ij_html_keep_indents_on_empty_lines = false +ij_html_keep_line_breaks = true +ij_html_keep_line_breaks_in_text = true +ij_html_keep_whitespaces = false +ij_html_keep_whitespaces_inside = span,pre,textarea +ij_html_line_comment_at_first_column = true +ij_html_new_line_after_last_attribute = never +ij_html_new_line_before_first_attribute = never +ij_html_quote_style = double +ij_html_remove_new_line_before_tags = br +ij_html_space_after_tag_name = false +ij_html_space_around_equality_in_attribute = false +ij_html_space_inside_empty_tag = false +ij_html_text_wrap = normal + +[{*.kt,*.kts}] +ij_kotlin_align_in_columns_case_branch = false +ij_kotlin_align_multiline_binary_operation = false +ij_kotlin_align_multiline_extends_list = false +ij_kotlin_align_multiline_method_parentheses = false +ij_kotlin_align_multiline_parameters = true +ij_kotlin_align_multiline_parameters_in_calls = false +ij_kotlin_allow_trailing_comma = false +ij_kotlin_allow_trailing_comma_on_call_site = false +ij_kotlin_assignment_wrap = normal +ij_kotlin_blank_lines_after_class_header = 0 +ij_kotlin_blank_lines_around_block_when_branches = 0 +ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 +ij_kotlin_block_comment_add_space = false +ij_kotlin_block_comment_at_first_column = true +ij_kotlin_call_parameters_new_line_after_left_paren = true +ij_kotlin_call_parameters_right_paren_on_new_line = true +ij_kotlin_call_parameters_wrap = on_every_item +ij_kotlin_catch_on_new_line = false +ij_kotlin_class_annotation_wrap = split_into_lines +ij_kotlin_continuation_indent_for_chained_calls = false +ij_kotlin_continuation_indent_for_expression_bodies = false +ij_kotlin_continuation_indent_in_argument_lists = false +ij_kotlin_continuation_indent_in_elvis = false +ij_kotlin_continuation_indent_in_if_conditions = false +ij_kotlin_continuation_indent_in_parameter_lists = false +ij_kotlin_continuation_indent_in_supertype_lists = false +ij_kotlin_else_on_new_line = false +ij_kotlin_enum_constants_wrap = off +ij_kotlin_extends_list_wrap = normal +ij_kotlin_field_annotation_wrap = split_into_lines +ij_kotlin_finally_on_new_line = false +ij_kotlin_if_rparen_on_new_line = true +ij_kotlin_import_nested_classes = false +ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^ +ij_kotlin_indent_before_arrow_on_new_line = true +ij_kotlin_insert_whitespaces_in_simple_one_line_method = true +ij_kotlin_keep_blank_lines_before_right_brace = 2 +ij_kotlin_keep_blank_lines_in_code = 2 +ij_kotlin_keep_blank_lines_in_declarations = 2 +ij_kotlin_keep_first_column_comment = true +ij_kotlin_keep_indents_on_empty_lines = false +ij_kotlin_keep_line_breaks = true +ij_kotlin_lbrace_on_next_line = false +ij_kotlin_line_break_after_multiline_when_entry = true +ij_kotlin_line_comment_add_space = false +ij_kotlin_line_comment_add_space_on_reformat = false +ij_kotlin_line_comment_at_first_column = true +ij_kotlin_method_annotation_wrap = split_into_lines +ij_kotlin_method_call_chain_wrap = normal +ij_kotlin_method_parameters_new_line_after_left_paren = true +ij_kotlin_method_parameters_right_paren_on_new_line = true +ij_kotlin_method_parameters_wrap = on_every_item +ij_kotlin_name_count_to_use_star_import = 5 +ij_kotlin_name_count_to_use_star_import_for_members = 3 +ij_kotlin_packages_to_use_import_on_demand = java.util.*,kotlinx.android.synthetic.**,io.ktor.** +ij_kotlin_parameter_annotation_wrap = off +ij_kotlin_space_after_comma = true +ij_kotlin_space_after_extend_colon = true +ij_kotlin_space_after_type_colon = true +ij_kotlin_space_before_catch_parentheses = true +ij_kotlin_space_before_comma = false +ij_kotlin_space_before_extend_colon = true +ij_kotlin_space_before_for_parentheses = true +ij_kotlin_space_before_if_parentheses = true +ij_kotlin_space_before_lambda_arrow = true +ij_kotlin_space_before_type_colon = false +ij_kotlin_space_before_when_parentheses = true +ij_kotlin_space_before_while_parentheses = true +ij_kotlin_spaces_around_additive_operators = true +ij_kotlin_spaces_around_assignment_operators = true +ij_kotlin_spaces_around_equality_operators = true +ij_kotlin_spaces_around_function_type_arrow = true +ij_kotlin_spaces_around_logical_operators = true +ij_kotlin_spaces_around_multiplicative_operators = true +ij_kotlin_spaces_around_range = false +ij_kotlin_spaces_around_relational_operators = true +ij_kotlin_spaces_around_unary_operator = false +ij_kotlin_spaces_around_when_arrow = true +ij_kotlin_variable_annotation_wrap = off +ij_kotlin_while_on_new_line = false +ij_kotlin_wrap_elvis_expressions = 1 +ij_kotlin_wrap_expression_body_functions = 1 +ij_kotlin_wrap_first_method_in_call_chain = false + +[{*.markdown,*.md}] +ij_markdown_force_one_space_after_blockquote_symbol = true +ij_markdown_force_one_space_after_header_symbol = true +ij_markdown_force_one_space_after_list_bullet = true +ij_markdown_force_one_space_between_words = true +ij_markdown_format_tables = true +ij_markdown_insert_quote_arrows_on_wrap = true +ij_markdown_keep_indents_on_empty_lines = false +ij_markdown_keep_line_breaks_inside_text_blocks = true +ij_markdown_max_lines_around_block_elements = 1 +ij_markdown_max_lines_around_header = 1 +ij_markdown_max_lines_between_paragraphs = 1 +ij_markdown_min_lines_around_block_elements = 1 +ij_markdown_min_lines_around_header = 1 +ij_markdown_min_lines_between_paragraphs = 1 +ij_markdown_wrap_text_if_long = true +ij_markdown_wrap_text_inside_blockquotes = true + +[{*.toml,Cargo.lock,Cargo.toml.orig,Gopkg.lock,Pipfile,poetry.lock}] +ij_toml_keep_indents_on_empty_lines = false + +[{*.yaml,*.yml}] +indent_size = 2 +ij_yaml_align_values_properties = do_not_align +ij_yaml_autoinsert_sequence_marker = true +ij_yaml_block_mapping_on_new_line = false +ij_yaml_indent_sequence_value = true +ij_yaml_keep_indents_on_empty_lines = false +ij_yaml_keep_line_breaks = true +ij_yaml_line_comment_add_space = false +ij_yaml_line_comment_add_space_on_reformat = false +ij_yaml_line_comment_at_first_column = true +ij_yaml_sequence_on_new_line = false +ij_yaml_space_before_colon = false +ij_yaml_spaces_within_braces = true +ij_yaml_spaces_within_brackets = true From 2a902f056783cc79b834cd90ad7f5b6eeddb0811 Mon Sep 17 00:00:00 2001 From: Hoan Nguyen <83745591+nqhoan-axonivy@users.noreply.github.com> Date: Fri, 20 Sep 2024 16:57:48 +0700 Subject: [PATCH 07/20] Format code (#166) --- marketplace-service/.editorconfig | 2 +- .../ProductDetailModelAssembler.java | 21 +- .../comparator/MavenVersionComparator.java | 172 ++++----- .../axonivy/market/config/AsyncConfig.java | 22 +- .../config/MarketApiDocumentConfig.java | 9 +- .../config/MarketHeaderInterceptor.java | 4 +- .../axonivy/market/config/MongoConfig.java | 9 +- .../market/config/SchedulingConfig.java | 18 +- .../com/axonivy/market/config/WebConfig.java | 10 +- .../market/constants/MavenConstants.java | 6 +- .../market/constants/MongoDBConstants.java | 18 +- .../constants/RequestMappingConstants.java | 4 +- .../market/controller/FeedbackController.java | 59 ++-- .../market/controller/ImageController.java | 9 +- .../market/controller/OAuth2Controller.java | 36 +- .../market/controller/ProductController.java | 62 ++-- ...ProductDesignerInstallationController.java | 26 +- .../controller/ProductDetailsController.java | 78 +++-- .../criteria/ProductSearchCriteria.java | 11 +- .../java/com/axonivy/market/entity/Image.java | 6 +- .../market/entity/MavenArtifactModel.java | 4 +- .../com/axonivy/market/entity/Product.java | 3 +- .../entity/ProductDesignerInstallation.java | 40 ++- .../market/entity/ProductModuleContent.java | 7 +- .../com/axonivy/market/enums/ErrorCode.java | 3 +- .../market/enums/NonStandardProduct.java | 33 +- .../market/exceptions/ExceptionHandlers.java | 1 + .../market/factory/ProductFactory.java | 13 +- .../market/github/model/GitHubProperty.java | 5 +- .../service/GHAxonIvyProductRepoService.java | 5 +- .../market/github/service/GitHubService.java | 17 +- .../impl/GHAxonIvyMarketRepoServiceImpl.java | 3 +- .../impl/GHAxonIvyProductRepoServiceImpl.java | 39 ++- .../market/model/ProductDetailModel.java | 6 +- .../axonivy/market/model/ProductModel.java | 10 +- ...ProductDesignerInstallationRepository.java | 2 +- .../ProductJsonContentRepository.java | 2 +- .../ProductModuleContentRepository.java | 3 +- .../market/repository/ProductRepository.java | 6 +- .../repository/ProductSearchRepository.java | 1 - .../impl/CustomProductRepositoryImpl.java | 13 +- .../impl/ProductSearchRepositoryImpl.java | 26 +- .../axonivy/market/service/ImageService.java | 5 +- .../market/service/VersionService.java | 3 +- .../service/impl/FeedbackServiceImpl.java | 3 +- .../market/service/impl/ImageServiceImpl.java | 2 +- ...roductDesignerInstallationServiceImpl.java | 31 +- .../service/impl/ProductServiceImpl.java | 79 ++--- .../com/axonivy/market/util/ImageUtils.java | 16 +- .../com/axonivy/market/util/VersionUtils.java | 203 +++++------ .../java/com/axonivy/market/BaseSetup.java | 15 +- .../ProductDetailModelAssemblerTest.java | 9 +- .../controller/ProductControllerTest.java | 13 +- ...uctDesignerInstallationControllerTest.java | 34 +- .../ProductDetailsControllerTest.java | 85 ++--- .../market/factory/ProductFactoryTest.java | 12 +- .../impl/CustomProductRepositoryImplTest.java | 37 +- .../impl/ProductSearchRepositoryImplTest.java | 26 +- .../service/impl/FeedbackServiceImplTest.java | 12 +- .../GHAxonIvyProductRepoServiceImplTest.java | 296 ++++++++-------- .../service/impl/GitHubServiceImplTest.java | 4 +- .../service/impl/ImageServiceImplTest.java | 41 +-- .../service/impl/JwtServiceImplTest.java | 5 +- ...ctDesignerInstallationServiceImplTest.java | 5 +- .../service/impl/ProductServiceImplTest.java | 141 +++----- .../service/impl/SchedulingTasksTest.java | 9 +- .../service/impl/UserServiceImplTest.java | 9 +- .../service/impl/VersionServiceImplTest.java | 3 +- .../axonivy/market/util/ImageUtilsTest.java | 11 +- .../axonivy/market/util/VersionUtilsTest.java | 325 +++++++++--------- 70 files changed, 1134 insertions(+), 1124 deletions(-) diff --git a/marketplace-service/.editorconfig b/marketplace-service/.editorconfig index 9b7bd7afe..bded54b66 100644 --- a/marketplace-service/.editorconfig +++ b/marketplace-service/.editorconfig @@ -78,7 +78,7 @@ ij_java_case_statement_on_separate_line = true ij_java_catch_on_new_line = false ij_java_class_annotation_wrap = split_into_lines ij_java_class_brace_style = end_of_line -ij_java_class_count_to_use_import_on_demand = 5 +ij_java_class_count_to_use_import_on_demand = 10 ij_java_class_names_in_javadoc = 1 ij_java_deconstruction_list_wrap = normal ij_java_do_not_indent_top_level_class_members = false 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 9acbe6fc5..8a8a2a632 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 @@ -1,22 +1,21 @@ package com.axonivy.market.assembler; -import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; -import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; - -import java.util.Optional; - -import org.apache.commons.lang3.StringUtils; -import org.springframework.hateoas.Link; -import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Component; - import com.axonivy.market.constants.RequestMappingConstants; import com.axonivy.market.controller.ProductDetailsController; import com.axonivy.market.entity.Product; import com.axonivy.market.model.ProductDetailModel; import com.axonivy.market.util.ImageUtils; import com.axonivy.market.util.VersionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; @Component public class ProductDetailModelAssembler extends RepresentationModelAssemblerSupport { 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 2c49ebf23..b707019a7 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 @@ -17,106 +17,106 @@ public class MavenVersionComparator { - private static final int GREATER_THAN = 1; - private static final int EQUAL = 0; - private static final int LESS_THAN = -1; + private static final int GREATER_THAN = 1; + private static final int EQUAL = 0; + private static final int LESS_THAN = -1; - private 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 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 String findHighestMavenVersion(List versions) { + if (CollectionUtils.isEmpty(versions)) { + return null; } - public static int compare(String version, String otherVersion) { - version = stripLeadingChars(version); - otherVersion = stripLeadingChars(otherVersion); - String[] versionParts = createMainAndQualifierArray(version); - String[] otherVersionParts = createMainAndQualifierArray(otherVersion); - - // Compare main version parts - int mainComparison = compareMainVersion(versionParts[0], otherVersionParts[0]); - if (mainComparison != EQUAL) { - return mainComparison; - } - - // Compare qualifiers - String qualifier1 = getQualifierPart(versionParts); - String qualifier2 = getQualifierPart(otherVersionParts); - // Consider versions without a qualifier higher than those with qualifiers - if (qualifier1.isEmpty() && !qualifier2.isEmpty()) { - return GREATER_THAN; - } - if (!qualifier1.isEmpty() && qualifier2.isEmpty()) { - return LESS_THAN; - } - return compareQualifier(qualifier1, qualifier2); + String highestVersion = versions.get(0); + for (var version : versions) { + if (compare(version, highestVersion) > EQUAL) { + highestVersion = version; + } } - - private static String stripLeadingChars(String version) { - Pattern pattern = Pattern.compile(CommonConstants.DIGIT_REGEX); - Matcher matcher = pattern.matcher(version); - if (matcher.find()) { - return matcher.group(1); - } - return version; + return highestVersion; + } + + public static int compare(String version, String otherVersion) { + version = stripLeadingChars(version); + otherVersion = stripLeadingChars(otherVersion); + String[] versionParts = createMainAndQualifierArray(version); + String[] otherVersionParts = createMainAndQualifierArray(otherVersion); + + // Compare main version parts + int mainComparison = compareMainVersion(versionParts[0], otherVersionParts[0]); + if (mainComparison != EQUAL) { + return mainComparison; } - private static int compareMainVersion(String mainVersion, String otherMainVersion) { - String[] parts1 = mainVersion.split(MAIN_VERSION_REGEX); - String[] parts2 = otherMainVersion.split(MAIN_VERSION_REGEX); - - int length = Math.max(parts1.length, parts2.length); - for (int i = 0; i < length; i++) { - int num1 = parseToNumber(parts1, i); - int num2 = parseToNumber(parts2, i); - if (num1 != num2) { - return num1 - num2; - } - } - return EQUAL; + // Compare qualifiers + String qualifier1 = getQualifierPart(versionParts); + String qualifier2 = getQualifierPart(otherVersionParts); + // Consider versions without a qualifier higher than those with qualifiers + if (qualifier1.isEmpty() && !qualifier2.isEmpty()) { + return GREATER_THAN; } - - private static String getQualifierPart(String[] versionParts) { - return versionParts.length > 1 ? versionParts[1] : EMPTY; + if (!qualifier1.isEmpty() && qualifier2.isEmpty()) { + return LESS_THAN; } - - private static String[] createMainAndQualifierArray(String version) { - return StringUtils.defaultIfBlank(version, EMPTY).split(DASH_SEPARATOR, 2); + return compareQualifier(qualifier1, qualifier2); + } + + private static String stripLeadingChars(String version) { + Pattern pattern = Pattern.compile(CommonConstants.DIGIT_REGEX); + Matcher matcher = pattern.matcher(version); + if (matcher.find()) { + return matcher.group(1); } + return version; + } + + private static int compareMainVersion(String mainVersion, String otherMainVersion) { + String[] parts1 = mainVersion.split(MAIN_VERSION_REGEX); + String[] parts2 = otherMainVersion.split(MAIN_VERSION_REGEX); + + int length = Math.max(parts1.length, parts2.length); + for (int i = 0; i < length; i++) { + int num1 = parseToNumber(parts1, i); + int num2 = parseToNumber(parts2, i); + if (num1 != num2) { + return num1 - num2; + } + } + return EQUAL; + } + + private static String getQualifierPart(String[] versionParts) { + return versionParts.length > 1 ? versionParts[1] : EMPTY; + } - private static int parseToNumber(String[] versionParts, int index) { - if (index < versionParts.length && NumberUtils.isDigits(versionParts[index])) { - return NumberUtils.toInt(versionParts[index]); - } - return 0; + private static String[] createMainAndQualifierArray(String version) { + return StringUtils.defaultIfBlank(version, EMPTY).split(DASH_SEPARATOR, 2); + } + + private static int parseToNumber(String[] versionParts, int index) { + if (index < versionParts.length && NumberUtils.isDigits(versionParts[index])) { + return NumberUtils.toInt(versionParts[index]); } + return 0; + } - private static int compareQualifier(String qualifier1, String qualifier2) { - if (SNAPSHOT_VERSION.equals(qualifier1) && !SNAPSHOT_VERSION.equals(qualifier2)) { - return LESS_THAN; - } - if (!SNAPSHOT_VERSION.equals(qualifier1) && SNAPSHOT_VERSION.equals(qualifier2)) { - return GREATER_THAN; - } - return qualifier1.compareTo(qualifier2); + private static int compareQualifier(String qualifier1, String qualifier2) { + if (SNAPSHOT_VERSION.equals(qualifier1) && !SNAPSHOT_VERSION.equals(qualifier2)) { + return LESS_THAN; + } + if (!SNAPSHOT_VERSION.equals(qualifier1) && SNAPSHOT_VERSION.equals(qualifier2)) { + return GREATER_THAN; } + return qualifier1.compareTo(qualifier2); + } } diff --git a/marketplace-service/src/main/java/com/axonivy/market/config/AsyncConfig.java b/marketplace-service/src/main/java/com/axonivy/market/config/AsyncConfig.java index 43dcfcdd0..298cb3047 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/config/AsyncConfig.java +++ b/marketplace-service/src/main/java/com/axonivy/market/config/AsyncConfig.java @@ -9,16 +9,16 @@ @Configuration public class AsyncConfig implements AsyncConfigurer { - private static final String THREAD_NAME_PREFIX = "AC-Thread-"; + private static final String THREAD_NAME_PREFIX = "AC-Thread-"; - @Override - public Executor getAsyncExecutor() { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(5); - executor.setMaxPoolSize(10); - executor.setQueueCapacity(25); - executor.setThreadNamePrefix(THREAD_NAME_PREFIX); - executor.initialize(); - return executor; - } + @Override + public Executor getAsyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(5); + executor.setMaxPoolSize(10); + executor.setQueueCapacity(25); + executor.setThreadNamePrefix(THREAD_NAME_PREFIX); + executor.initialize(); + return executor; + } } \ No newline at end of file diff --git a/marketplace-service/src/main/java/com/axonivy/market/config/MarketApiDocumentConfig.java b/marketplace-service/src/main/java/com/axonivy/market/config/MarketApiDocumentConfig.java index 67e0dfbed..f9134cc1b 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/config/MarketApiDocumentConfig.java +++ b/marketplace-service/src/main/java/com/axonivy/market/config/MarketApiDocumentConfig.java @@ -24,17 +24,18 @@ public class MarketApiDocumentConfig { @Bean public GroupedOpenApi buildMarketCustomHeader() { return GroupedOpenApi.builder().group(DEFAULT_DOC_GROUP) - .addOpenApiCustomizer(customMarketHeaders()) - .pathsToMatch(PATH_PATTERN).build(); + .addOpenApiCustomizer(customMarketHeaders()) + .pathsToMatch(PATH_PATTERN).build(); } private OpenApiCustomizer customMarketHeaders() { return openApi -> openApi.getPaths().values().forEach((PathItem pathItem) -> { - List operations = Arrays.asList(pathItem.getPut(), pathItem.getPost(), pathItem.getPatch(), pathItem.getDelete()); + List operations = Arrays.asList(pathItem.getPut(), pathItem.getPost(), pathItem.getPatch(), + pathItem.getDelete()); for (Operation operation : operations) { if (operation != null) { Parameter headerParameter = new Parameter().in(HEADER_PARAM).schema(new StringSchema()) - .name(REQUESTED_BY).description(DEFAULT_PARAM).required(true); + .name(REQUESTED_BY).description(DEFAULT_PARAM).required(true); operation.addParametersItem(headerParameter); } } diff --git a/marketplace-service/src/main/java/com/axonivy/market/config/MarketHeaderInterceptor.java b/marketplace-service/src/main/java/com/axonivy/market/config/MarketHeaderInterceptor.java index f47d860da..9afeff698 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/config/MarketHeaderInterceptor.java +++ b/marketplace-service/src/main/java/com/axonivy/market/config/MarketHeaderInterceptor.java @@ -14,12 +14,12 @@ public class MarketHeaderInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) - throws Exception { + throws Exception { if (HttpMethod.OPTIONS.name().equalsIgnoreCase(request.getMethod())) { return true; } if (!HttpMethod.GET.name().equalsIgnoreCase(request.getMethod()) - && StringUtils.isBlank(request.getHeader(CommonConstants.REQUESTED_BY))) { + && StringUtils.isBlank(request.getHeader(CommonConstants.REQUESTED_BY))) { throw new MissingHeaderException(); } return true; diff --git a/marketplace-service/src/main/java/com/axonivy/market/config/MongoConfig.java b/marketplace-service/src/main/java/com/axonivy/market/config/MongoConfig.java index 71df8b67b..becc7dafb 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/config/MongoConfig.java +++ b/marketplace-service/src/main/java/com/axonivy/market/config/MongoConfig.java @@ -1,5 +1,9 @@ package com.axonivy.market.config; +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -13,11 +17,6 @@ import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; -import com.mongodb.ConnectionString; -import com.mongodb.MongoClientSettings; -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; - @Configuration @EnableMongoRepositories(basePackages = "com.axonivy.market.repository") @EnableMongoAuditing diff --git a/marketplace-service/src/main/java/com/axonivy/market/config/SchedulingConfig.java b/marketplace-service/src/main/java/com/axonivy/market/config/SchedulingConfig.java index 275717f54..c9006737b 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/config/SchedulingConfig.java +++ b/marketplace-service/src/main/java/com/axonivy/market/config/SchedulingConfig.java @@ -7,14 +7,14 @@ @Configuration public class SchedulingConfig { - private static final String THREAD_NAME_PREFIX = "SC-Thread-"; + private static final String THREAD_NAME_PREFIX = "SC-Thread-"; - @Bean - public ThreadPoolTaskScheduler taskScheduler() { - ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); - taskScheduler.setPoolSize(10); - taskScheduler.setThreadNamePrefix(THREAD_NAME_PREFIX); - taskScheduler.initialize(); - return taskScheduler; - } + @Bean + public ThreadPoolTaskScheduler taskScheduler() { + ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); + taskScheduler.setPoolSize(10); + taskScheduler.setThreadNamePrefix(THREAD_NAME_PREFIX); + taskScheduler.initialize(); + return taskScheduler; + } } diff --git a/marketplace-service/src/main/java/com/axonivy/market/config/WebConfig.java b/marketplace-service/src/main/java/com/axonivy/market/config/WebConfig.java index d50fe342c..619dd7225 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/config/WebConfig.java +++ b/marketplace-service/src/main/java/com/axonivy/market/config/WebConfig.java @@ -10,11 +10,11 @@ public class WebConfig implements WebMvcConfigurer { private static final String ALL_MAPPINGS = "/**"; - private static final String[] EXCLUDE_PATHS = { "/", "/swagger-ui/**", "/api-docs/**", "/api/product-details/**/json", - "/api/image/**" }; - private static final String[] ALLOWED_HEADERS = { "Accept-Language", "Content-Type", "Authorization", - "X-Requested-By", "x-requested-with", "X-Forwarded-Host", "x-xsrf-token", "x-authorization" }; - private static final String[] ALLOWED_METHODS = { "GET", "POST", "PUT", "DELETE", "OPTIONS" }; + private static final String[] EXCLUDE_PATHS = {"/", "/swagger-ui/**", "/api-docs/**", "/api/product-details/**/json", + "/api/image/**"}; + private static final String[] ALLOWED_HEADERS = {"Accept-Language", "Content-Type", "Authorization", + "X-Requested-By", "x-requested-with", "X-Forwarded-Host", "x-xsrf-token", "x-authorization"}; + private static final String[] ALLOWED_METHODS = {"GET", "POST", "PUT", "DELETE", "OPTIONS"}; private final MarketHeaderInterceptor headerInterceptor; 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 0c99d0118..2690892ce 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,9 +1,6 @@ package com.axonivy.market.constants; public class MavenConstants { - private MavenConstants() { - } - public static final String SNAPSHOT_VERSION = "SNAPSHOT"; public static final String SNAPSHOT_RELEASE_POSTFIX = "-" + SNAPSHOT_VERSION; public static final String SPRINT_RELEASE_POSTFIX = "-m"; @@ -13,4 +10,7 @@ private MavenConstants() { public static final String ARTIFACT_NAME_FORMAT = "%s (%s)"; public static final String VERSION_EXTRACT_FORMAT_FROM_METADATA_FILE = "//versions/version/text()"; public static final String MAIN_VERSION_REGEX = "\\."; + + 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 917709dd9..784984d32 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 @@ -2,15 +2,15 @@ public class MongoDBConstants { - private MongoDBConstants() { - } + public static final String ID = "_id"; + public static final String PRODUCT_COLLECTION = "Product"; + public static final String INSTALLATION_COUNT = "InstallationCount"; + public static final String SYNCHRONIZED_INSTALLATION_COUNT = "SynchronizedInstallationCount"; + public static final String PRODUCT_ID = "productId"; + public static final String DESIGNER_VERSION = "designerVersion"; + public static final String TAG = "tag"; - public static final String ID ="_id"; - public static final String PRODUCT_COLLECTION ="Product"; - public static final String INSTALLATION_COUNT = "InstallationCount"; - public static final String SYNCHRONIZED_INSTALLATION_COUNT ="SynchronizedInstallationCount"; - public static final String PRODUCT_ID = "productId"; - public static final String DESIGNER_VERSION = "designerVersion"; - public static final String TAG = "tag"; + private MongoDBConstants() { + } } diff --git a/marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java index 95105083e..c828f55d3 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java @@ -7,11 +7,12 @@ public class RequestMappingConstants { public static final String ROOT = "/"; public static final String API = ROOT + "api"; - public static final String SYNC = "sync"; public static final String PRODUCT = API + "/product"; public static final String PRODUCT_DETAILS = API + "/product-details"; public static final String PRODUCT_DESIGNER_INSTALLATION = API + "/product-designer-installation"; public static final String FEEDBACK = API + "/feedback"; + public static final String IMAGE = API + "/image"; + public static final String SYNC = "sync"; public static final String SWAGGER_URL = "/swagger-ui/index.html"; public static final String GIT_HUB_LOGIN = "/github/login"; public static final String AUTH = "/auth"; @@ -26,5 +27,4 @@ public class RequestMappingConstants { public static final String VERSIONS_IN_DESIGNER = "/{id}/designerversions"; public static final String DESIGNER_INSTALLATION_BY_ID = "/installation/{id}/designer"; public static final String CUSTOM_SORT = "custom-sort"; - public static final String IMAGE = API + "/image"; } \ No newline at end of file diff --git a/marketplace-service/src/main/java/com/axonivy/market/controller/FeedbackController.java b/marketplace-service/src/main/java/com/axonivy/market/controller/FeedbackController.java index 79668a021..baa642b83 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/controller/FeedbackController.java +++ b/marketplace-service/src/main/java/com/axonivy/market/controller/FeedbackController.java @@ -40,13 +40,8 @@ import java.net.URI; import java.util.List; -import static com.axonivy.market.constants.RequestMappingConstants.BY_ID; -import static com.axonivy.market.constants.RequestMappingConstants.FEEDBACK; -import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT_BY_ID; -import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT_RATING_BY_ID; -import static com.axonivy.market.constants.RequestParamConstants.ID; -import static com.axonivy.market.constants.RequestParamConstants.USER_ID; -import static com.axonivy.market.constants.RequestParamConstants.X_AUTHORIZATION; +import static com.axonivy.market.constants.RequestMappingConstants.*; +import static com.axonivy.market.constants.RequestParamConstants.*; @RestController @RequestMapping(FEEDBACK) @@ -68,12 +63,18 @@ public FeedbackController(FeedbackService feedbackService, JwtService jwtService } @GetMapping(PRODUCT_BY_ID) - @Operation(summary = "Find feedbacks by product id with lazy loading", description = "Get all user feedback by product id (from meta.json) with lazy loading", parameters = { - @Parameter(name = "page", description = "Page number to retrieve", in = ParameterIn.QUERY, example = "0", required = true), - @Parameter(name = "size", description = "Number of items per page", in = ParameterIn.QUERY, example = "20", required = true), - @Parameter(name = "sort", description = "Sorting criteria in the format: Sorting criteria(popularity|alphabetically|recent), Sorting order(asc|desc)", in = ParameterIn.QUERY, example = "[\"popularity\",\"asc\"]", required = true) }) + @Operation(summary = "Find feedbacks by product id with lazy loading", description = "Get all user feedback by " + + "product id (from meta.json) with lazy loading", parameters = { + @Parameter(name = "page", description = "Page number to retrieve", in = ParameterIn.QUERY, example = "0", + required = true), + @Parameter(name = "size", description = "Number of items per page", in = ParameterIn.QUERY, example = "20", + required = true), + @Parameter(name = "sort", description = "Sorting criteria in the format: Sorting criteria" + + "(popularity|alphabetically|recent), Sorting order(asc|desc)", in = ParameterIn.QUERY, example = + "[\"popularity\",\"asc\"]", required = true)}) public ResponseEntity> findFeedbacks( - @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "portal", in = ParameterIn.PATH) String productId, + @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "portal", in = + ParameterIn.PATH) String productId, @ParameterObject Pageable pageable) { Page results = feedbackService.findFeedbacks(productId, pageable); if (results.isEmpty()) { @@ -85,29 +86,37 @@ public ResponseEntity> findFeedbacks( } @GetMapping(BY_ID) - @Operation(summary = "Find all feedbacks by product id", description = "Get all feedbacks by product id(from meta.json) which is used in mobile viewport.") + @Operation(summary = "Find all feedbacks by product id", description = "Get all feedbacks by product id(from meta" + + ".json) which is used in mobile viewport.") public ResponseEntity findFeedback( - @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "portal", in = ParameterIn.PATH) String id) { + @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "portal", in = + ParameterIn.PATH) String id) { Feedback feedback = feedbackService.findFeedback(id); return ResponseEntity.ok(feedbackModelAssembler.toModel(feedback)); } @GetMapping() - @Operation(summary = "Find all feedbacks by user id and product id", description = "Get current user feedback on target product.") + @Operation(summary = "Find all feedbacks by user id and product id", description = "Get current user feedback on " + + "target product.") public ResponseEntity findFeedbackByUserIdAndProductId( - @RequestParam(USER_ID) @Parameter(description = "Id of current user from DB", example = "1234", in = ParameterIn.QUERY) String userId, - @RequestParam("productId") @Parameter(description = "Product id (from meta.json)", example = "portal", in = ParameterIn.QUERY) String productId) { + @RequestParam(USER_ID) @Parameter(description = "Id of current user from DB", example = "1234", in = + ParameterIn.QUERY) String userId, + @RequestParam("productId") @Parameter(description = "Product id (from meta.json)", example = "portal", in = + ParameterIn.QUERY) String productId) { Feedback feedback = feedbackService.findFeedbackByUserIdAndProductId(userId, productId); return ResponseEntity.ok(feedbackModelAssembler.toModel(feedback)); } @PostMapping - @Operation(summary = "Create user feedback", description = "Save user feedback of product with their token from Github account.") - @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Example request body for feedback", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = FeedbackModelRequest.class))) - @ApiResponses(value = { @ApiResponse(responseCode = "201", description = "Successfully created user feedback"), - @ApiResponse(responseCode = "401", description = "Unauthorized request") }) + @Operation(summary = "Create user feedback", description = "Save user feedback of product with their token from " + + "Github account.") + @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Example request body for feedback", content = + @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = FeedbackModelRequest.class))) + @ApiResponses(value = {@ApiResponse(responseCode = "201", description = "Successfully created user feedback"), + @ApiResponse(responseCode = "401", description = "Unauthorized request")}) public ResponseEntity createFeedback(@RequestBody @Valid FeedbackModelRequest feedbackRequest, - @RequestHeader(value = X_AUTHORIZATION) @Parameter(description = "JWT Bearer token", example = "Bearer 123456", in = ParameterIn.HEADER) String bearerToken) { + @RequestHeader(value = X_AUTHORIZATION) @Parameter(description = "JWT Bearer token", example = "Bearer 123456", + in = ParameterIn.HEADER) String bearerToken) { String token = null; if (bearerToken != null && bearerToken.startsWith(CommonConstants.BEARER)) { token = bearerToken.substring(CommonConstants.BEARER.length()).trim(); // Remove "Bearer " prefix @@ -127,10 +136,12 @@ public ResponseEntity createFeedback(@RequestBody @Valid FeedbackModelRequ return ResponseEntity.created(location).build(); } - @Operation(summary = "Find rating information of product by its id.", description = "Get overall rating of product by its id.") + @Operation(summary = "Find rating information of product by its id.", description = "Get overall rating of product " + + "by its id.") @GetMapping(PRODUCT_RATING_BY_ID) public ResponseEntity> getProductRating( - @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "portal", in = ParameterIn.PATH) String productId) { + @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "portal", in = + ParameterIn.PATH) String productId) { return ResponseEntity.ok(feedbackService.getProductRatingById(productId)); } diff --git a/marketplace-service/src/main/java/com/axonivy/market/controller/ImageController.java b/marketplace-service/src/main/java/com/axonivy/market/controller/ImageController.java index 5264d7e40..73444c5fe 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/controller/ImageController.java +++ b/marketplace-service/src/main/java/com/axonivy/market/controller/ImageController.java @@ -33,12 +33,15 @@ public ImageController(ImageService imageService) { } @GetMapping(BY_ID) - @Operation(summary = "Get the image content by id", description = "Collect the byte[] of image with contentType in header is PNG") - @ApiResponse(responseCode = "200", description = "Image found and returned", content = @Content(mediaType = MediaType.IMAGE_PNG_VALUE, schema = @Schema(implementation = Image.class))) + @Operation(summary = "Get the image content by id", description = "Collect the byte[] of image with contentType in " + + "header is PNG") + @ApiResponse(responseCode = "200", description = "Image found and returned", content = @Content(mediaType = + MediaType.IMAGE_PNG_VALUE, schema = @Schema(implementation = Image.class))) @ApiResponse(responseCode = "404", description = "Image not found") @ApiResponse(responseCode = "204", description = "No content (image empty)") public ResponseEntity findImageById( - @PathVariable(ID) @Parameter(description = "The image id", example = "66e7efc8a24f36158df06fc7", in = ParameterIn.PATH) String id) { + @PathVariable(ID) @Parameter(description = "The image id", example = "66e7efc8a24f36158df06fc7", in = + ParameterIn.PATH) String id) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.IMAGE_PNG); byte[] imageData = imageService.readImage(id); diff --git a/marketplace-service/src/main/java/com/axonivy/market/controller/OAuth2Controller.java b/marketplace-service/src/main/java/com/axonivy/market/controller/OAuth2Controller.java index afc4aba6d..ed9b515ac 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/controller/OAuth2Controller.java +++ b/marketplace-service/src/main/java/com/axonivy/market/controller/OAuth2Controller.java @@ -1,14 +1,14 @@ package com.axonivy.market.controller; -import static com.axonivy.market.constants.RequestMappingConstants.AUTH; -import static com.axonivy.market.constants.RequestMappingConstants.GIT_HUB_LOGIN; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - import com.axonivy.market.constants.CommonConstants; +import com.axonivy.market.constants.GitHubConstants; +import com.axonivy.market.entity.User; import com.axonivy.market.exceptions.model.Oauth2ExchangeCodeException; +import com.axonivy.market.github.model.GitHubAccessTokenResponse; +import com.axonivy.market.github.model.GitHubProperty; +import com.axonivy.market.github.service.GitHubService; +import com.axonivy.market.model.Oauth2AuthorizationCode; +import com.axonivy.market.service.JwtService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -22,13 +22,12 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.axonivy.market.constants.GitHubConstants; -import com.axonivy.market.entity.User; -import com.axonivy.market.github.model.GitHubAccessTokenResponse; -import com.axonivy.market.github.model.GitHubProperty; -import com.axonivy.market.github.service.GitHubService; -import com.axonivy.market.model.Oauth2AuthorizationCode; -import com.axonivy.market.service.JwtService; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static com.axonivy.market.constants.RequestMappingConstants.AUTH; +import static com.axonivy.market.constants.RequestMappingConstants.GIT_HUB_LOGIN; @RestController @RequestMapping(AUTH) @@ -49,13 +48,16 @@ public OAuth2Controller(GitHubService gitHubService, JwtService jwtService, GitH @PostMapping(GIT_HUB_LOGIN) @Operation(description = "Get rating authentication token") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Successfully login to GitHub provider", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Map.class))), + @ApiResponse(responseCode = "200", description = "Successfully login to GitHub provider", content = + @Content(mediaType = "application/json", schema = @Schema(implementation = Map.class))), @ApiResponse(responseCode = "400", description = "Bad Request")}) - @io.swagger.v3.oas.annotations.parameters.RequestBody(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = Oauth2AuthorizationCode.class))) + @io.swagger.v3.oas.annotations.parameters.RequestBody(content = @Content(mediaType = + MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = Oauth2AuthorizationCode.class))) public ResponseEntity> gitHubLogin(@RequestBody Oauth2AuthorizationCode oauth2AuthorizationCode) { String accessToken; try { - GitHubAccessTokenResponse tokenResponse = gitHubService.getAccessToken(oauth2AuthorizationCode.getCode(), gitHubProperty); + GitHubAccessTokenResponse tokenResponse = gitHubService.getAccessToken(oauth2AuthorizationCode.getCode(), + gitHubProperty); accessToken = tokenResponse.getAccessToken(); User user = gitHubService.getAndUpdateUser(accessToken); String jwtToken = jwtService.generateToken(user); 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 483f97b41..2af26b2eb 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 @@ -1,9 +1,5 @@ package com.axonivy.market.controller; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.enums.ParameterIn; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.tags.Tag; import com.axonivy.market.assembler.ProductModelAssembler; import com.axonivy.market.constants.CommonConstants; import com.axonivy.market.constants.GitHubConstants; @@ -15,8 +11,11 @@ import com.axonivy.market.model.ProductModel; import com.axonivy.market.service.ProductService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; - import org.apache.commons.lang3.time.StopWatch; import org.springdoc.core.annotations.ParameterObject; import org.springframework.data.domain.Page; @@ -35,14 +34,8 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import static com.axonivy.market.constants.RequestMappingConstants.CUSTOM_SORT; -import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT; -import static com.axonivy.market.constants.RequestMappingConstants.SYNC; -import static com.axonivy.market.constants.RequestParamConstants.KEYWORD; -import static com.axonivy.market.constants.RequestParamConstants.LANGUAGE; -import static com.axonivy.market.constants.RequestParamConstants.RESET_SYNC; -import static com.axonivy.market.constants.RequestParamConstants.TYPE; -import static com.axonivy.market.constants.RequestParamConstants.IS_REST_CLIENT; +import static com.axonivy.market.constants.RequestMappingConstants.*; +import static com.axonivy.market.constants.RequestParamConstants.*; import static org.springframework.http.HttpHeaders.AUTHORIZATION; @RestController @@ -62,17 +55,33 @@ public ProductController(ProductService productService, GitHubService gitHubServ this.pagedResourcesAssembler = pagedResourcesAssembler; } + public static String getBearerToken(String authorizationHeader) { + String token = null; + if (authorizationHeader.startsWith(CommonConstants.BEARER)) { + token = authorizationHeader.substring(CommonConstants.BEARER.length()).trim(); // Remove "Bearer " prefix + } + return token; + } + @GetMapping() - @Operation(summary = "Retrieve a paginated list of all products, optionally filtered by type, keyword, and language", description = "By default, the system finds products with type 'all'", parameters = { - @Parameter(name = "page", description = "Page number to retrieve", in = ParameterIn.QUERY, example = "0", required = true), - @Parameter(name = "size", description = "Number of items per page", in = ParameterIn.QUERY, example = "20", required = true), - @Parameter(name = "sort", description = "Sorting criteria in the format: Sorting criteria(popularity|alphabetically|recent), Sorting order(asc|desc)", + @Operation(summary = "Retrieve a paginated list of all products, optionally filtered by type, keyword, and " + + "language", description = "By default, the system finds products with type 'all'", parameters = { + @Parameter(name = "page", description = "Page number to retrieve", in = ParameterIn.QUERY, example = "0", + required = true), + @Parameter(name = "size", description = "Number of items per page", in = ParameterIn.QUERY, example = "20", + required = true), + @Parameter(name = "sort", description = "Sorting criteria in the format: Sorting criteria" + + "(popularity|alphabetically|recent), Sorting order(asc|desc)", in = ParameterIn.QUERY, example = "[\"popularity\",\"asc\"]", required = true)}) public ResponseEntity> findProducts( - @RequestParam(name = TYPE) @Parameter(description = "Type of product.", in = ParameterIn.QUERY, schema = @Schema(type = "string", allowableValues = {"all", "connectors", "utilities", "solutions", "demos"})) String type, - @RequestParam(required = false, name = KEYWORD) @Parameter(description = "Keyword that exist in product's name or short description", example = "connector", in = ParameterIn.QUERY) String keyword, - @RequestParam(name = LANGUAGE) @Parameter(description = "Language of product short description", in = ParameterIn.QUERY, schema = @Schema(allowableValues = {"en", "de"})) String language, - @RequestParam(name = IS_REST_CLIENT) @Parameter(description = "Option to render the website in the REST Client Editor of Designer", in = ParameterIn.QUERY) Boolean isRESTClient, + @RequestParam(name = TYPE) @Parameter(description = "Type of product.", in = ParameterIn.QUERY, schema = + @Schema(type = "string", allowableValues = {"all", "connectors", "utilities", "solutions", "demos"})) String type, + @RequestParam(required = false, name = KEYWORD) @Parameter(description = "Keyword that exist in product's name " + + "or short description", example = "connector", in = ParameterIn.QUERY) String keyword, + @RequestParam(name = LANGUAGE) @Parameter(description = "Language of product short description", in = + ParameterIn.QUERY, schema = @Schema(allowableValues = {"en", "de"})) String language, + @RequestParam(name = IS_REST_CLIENT) @Parameter(description = "Option to render the website in the REST Client " + + "Editor of Designer", in = ParameterIn.QUERY) Boolean isRESTClient, @ParameterObject Pageable pageable) { Page results = productService.findProducts(type, keyword, language, isRESTClient, pageable); if (results.isEmpty()) { @@ -123,15 +132,8 @@ public ResponseEntity createCustomSortProducts( @SuppressWarnings("unchecked") private ResponseEntity> generateEmptyPagedModel() { - var emptyPagedModel = (PagedModel) pagedResourcesAssembler.toEmptyModel(Page.empty(), ProductModel.class); + var emptyPagedModel = (PagedModel) pagedResourcesAssembler.toEmptyModel(Page.empty(), + ProductModel.class); return new ResponseEntity<>(emptyPagedModel, HttpStatus.OK); } - - public static String getBearerToken(String authorizationHeader) { - String token = null; - if (authorizationHeader.startsWith(CommonConstants.BEARER)) { - token = authorizationHeader.substring(CommonConstants.BEARER.length()).trim(); // Remove "Bearer " prefix - } - return token; - } } diff --git a/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDesignerInstallationController.java b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDesignerInstallationController.java index 301cb7439..24118bfcc 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDesignerInstallationController.java +++ b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDesignerInstallationController.java @@ -21,18 +21,22 @@ @RestController @RequestMapping(PRODUCT_DESIGNER_INSTALLATION) -@Tag(name = "Product Designer Installation Controllers", description = "API collection to get designer installation count.") +@Tag(name = "Product Designer Installation Controllers", description = "API collection to get designer installation " + + "count.") public class ProductDesignerInstallationController { - private final ProductDesignerInstallationService productDesignerInstallationService; + private final ProductDesignerInstallationService productDesignerInstallationService; - public ProductDesignerInstallationController(ProductDesignerInstallationService productDesignerInstallationService) { - this.productDesignerInstallationService = productDesignerInstallationService; - } + public ProductDesignerInstallationController(ProductDesignerInstallationService productDesignerInstallationService) { + this.productDesignerInstallationService = productDesignerInstallationService; + } - @GetMapping(DESIGNER_INSTALLATION_BY_ID) - @Operation(summary = "Get designer installation count by product id.", description = "get designer installation count by product id") - public ResponseEntity> getProductDesignerInstallationByProductId(@PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "adobe-acrobat-connector", in = ParameterIn.PATH) String productId) { - List models = productDesignerInstallationService.findByProductId(productId); - return new ResponseEntity<>(models, HttpStatus.OK); - } + @GetMapping(DESIGNER_INSTALLATION_BY_ID) + @Operation(summary = "Get designer installation count by product id.", description = "get designer installation " + + "count by product id") + public ResponseEntity> getProductDesignerInstallationByProductId( + @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "adobe-acrobat-connector", + in = ParameterIn.PATH) String productId) { + List models = productDesignerInstallationService.findByProductId(productId); + return new ResponseEntity<>(models, HttpStatus.OK); + } } diff --git a/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDetailsController.java b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDetailsController.java index 5b68520bd..88220c64a 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDetailsController.java +++ b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDetailsController.java @@ -1,21 +1,12 @@ package com.axonivy.market.controller; -import static com.axonivy.market.constants.RequestParamConstants.DESIGNER_VERSION; -import static com.axonivy.market.constants.RequestParamConstants.ID; -import static com.axonivy.market.constants.RequestParamConstants.SHOW_DEV_VERSION; -import static com.axonivy.market.constants.RequestParamConstants.VERSION; -import static com.axonivy.market.constants.RequestMappingConstants.BEST_MATCH_BY_ID_AND_VERSION; -import static com.axonivy.market.constants.RequestMappingConstants.BY_ID; -import static com.axonivy.market.constants.RequestMappingConstants.BY_ID_AND_VERSION; -import static com.axonivy.market.constants.RequestMappingConstants.INSTALLATION_COUNT_BY_ID; -import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT_DETAILS; -import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT_JSON_CONTENT_BY_PRODUCT_ID_AND_VERSION; -import static com.axonivy.market.constants.RequestMappingConstants.VERSIONS_BY_ID; -import static com.axonivy.market.constants.RequestMappingConstants.VERSIONS_IN_DESIGNER; -import java.util.List; -import java.util.Map; - +import com.axonivy.market.assembler.ProductDetailModelAssembler; +import com.axonivy.market.model.MavenArtifactVersionModel; +import com.axonivy.market.model.ProductDetailModel; import com.axonivy.market.model.VersionAndUrlModel; +import com.axonivy.market.service.ProductService; +import com.axonivy.market.service.VersionService; +import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; @@ -28,13 +19,11 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import com.axonivy.market.assembler.ProductDetailModelAssembler; -import com.axonivy.market.model.MavenArtifactVersionModel; -import com.axonivy.market.model.ProductDetailModel; -import com.axonivy.market.service.ProductService; -import com.axonivy.market.service.VersionService; +import java.util.List; +import java.util.Map; -import io.swagger.v3.oas.annotations.Operation; +import static com.axonivy.market.constants.RequestMappingConstants.*; +import static com.axonivy.market.constants.RequestParamConstants.*; @RestController @RequestMapping(PRODUCT_DETAILS) @@ -52,28 +41,37 @@ public ProductDetailsController(VersionService versionService, ProductService pr } @GetMapping(BY_ID_AND_VERSION) - @Operation(summary = "Find product detail by product id and release version.", description = "get product detail by it product id and release version") + @Operation(summary = "Find product detail by product id and release version.", description = "get product detail by" + + " it product id and release version") public ResponseEntity findProductDetailsByVersion( - @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", in = ParameterIn.PATH) String id, - @PathVariable(VERSION) @Parameter(description = "Release version (from maven metadata.xml)", example = "10.0.20", in = ParameterIn.PATH) String version) { + @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", + in = ParameterIn.PATH) String id, + @PathVariable(VERSION) @Parameter(description = "Release version (from maven metadata.xml)", example = "10.0" + + ".20", in = ParameterIn.PATH) String version) { var productDetail = productService.fetchProductDetailByIdAndVersion(id, version); return new ResponseEntity<>(detailModelAssembler.toModel(productDetail, version, BY_ID_AND_VERSION), HttpStatus.OK); } @GetMapping(BEST_MATCH_BY_ID_AND_VERSION) - @Operation(summary = "Find best match product detail by product id and version.", description = "get product detail by it product id and version") + @Operation(summary = "Find best match product detail by product id and version.", description = "get product detail" + + " by it product id and version") public ResponseEntity findBestMatchProductDetailsByVersion( - @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", in = ParameterIn.PATH) String id, + @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", + in = ParameterIn.PATH) String id, @PathVariable(VERSION) @Parameter(description = "Version", example = "10.0.20", in = ParameterIn.PATH) String version) { - var productDetail = productService.fetchBestMatchProductDetail(id,version); - return new ResponseEntity<>(detailModelAssembler.toModel(productDetail, version, BEST_MATCH_BY_ID_AND_VERSION), HttpStatus.OK); + var productDetail = productService.fetchBestMatchProductDetail(id, version); + return new ResponseEntity<>(detailModelAssembler.toModel(productDetail, version, BEST_MATCH_BY_ID_AND_VERSION), + HttpStatus.OK); } @PutMapping(INSTALLATION_COUNT_BY_ID) - @Operation(summary = "Update installation count of product", description = "By default, increase installation count when click download product files by users") + @Operation(summary = "Update installation count of product", description = "By default, increase installation count" + + " when click download product files by users") public ResponseEntity syncInstallationCount( - @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", in = ParameterIn.PATH) String productId, - @RequestParam(name = DESIGNER_VERSION, required = false) @Parameter(in = ParameterIn.QUERY, example = "v10.0.20") String designerVersion) { + @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", + in = ParameterIn.PATH) String productId, + @RequestParam(name = DESIGNER_VERSION, required = false) @Parameter(in = ParameterIn.QUERY, example = "v10.0" + + ".20") String designerVersion) { int result = productService.updateInstallationCountForProduct(productId, designerVersion); return new ResponseEntity<>(result, HttpStatus.OK); } @@ -81,23 +79,28 @@ public ResponseEntity syncInstallationCount( @GetMapping(BY_ID) @Operation(summary = "get product detail by ID", description = "Return product detail by product id (from meta.json)") public ResponseEntity findProductDetails( - @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", in = ParameterIn.PATH) String id) { + @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", + in = ParameterIn.PATH) String id) { var productDetail = productService.fetchProductDetail(id); return new ResponseEntity<>(detailModelAssembler.toModel(productDetail, BY_ID), HttpStatus.OK); } @GetMapping(VERSIONS_BY_ID) public ResponseEntity> findProductVersionsById( - @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "adobe-acrobat-connector", in = ParameterIn.PATH) String id, - @RequestParam(SHOW_DEV_VERSION) @Parameter(description = "Option to get Dev Version (Snapshot/ sprint release)", in = ParameterIn.QUERY) boolean isShowDevVersion, - @RequestParam(name = DESIGNER_VERSION, required = false) @Parameter(in = ParameterIn.QUERY, example = "v10.0.20") String designerVersion) { + @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "adobe-acrobat-connector", + in = ParameterIn.PATH) String id, + @RequestParam(SHOW_DEV_VERSION) @Parameter(description = "Option to get Dev Version (Snapshot/ sprint release)" + , in = ParameterIn.QUERY) boolean isShowDevVersion, + @RequestParam(name = DESIGNER_VERSION, required = false) @Parameter(in = ParameterIn.QUERY, example = "v10.0" + + ".20") String designerVersion) { List models = versionService.getArtifactsAndVersionToDisplay(id, isShowDevVersion, designerVersion); return new ResponseEntity<>(models, HttpStatus.OK); } @GetMapping(PRODUCT_JSON_CONTENT_BY_PRODUCT_ID_AND_VERSION) - @Operation(summary = "Get product json content for designer to install", description = "When we click install in designer, this API will send content of product json for installing in Ivy designer") + @Operation(summary = "Get product json content for designer to install", description = "When we click install in " + + "designer, this API will send content of product json for installing in Ivy designer") public ResponseEntity> findProductJsonContent(@PathVariable(ID) String productId, @PathVariable(VERSION) String version) { Map productJsonContent = versionService.getProductJsonContentByIdAndVersion(productId, version); @@ -105,7 +108,8 @@ public ResponseEntity> findProductJsonContent(@PathVariable( } @GetMapping(VERSIONS_IN_DESIGNER) - @Operation(summary = "Get the list of released version in product", description = "Collect the released versions in product for ivy designer") + @Operation(summary = "Get the list of released version in product", description = "Collect the released versions in" + + " product for ivy designer") public ResponseEntity> findVersionsForDesigner(@PathVariable(ID) String id) { List versionList = versionService.getVersionsForDesigner(id); return new ResponseEntity<>(versionList, HttpStatus.OK); diff --git a/marketplace-service/src/main/java/com/axonivy/market/criteria/ProductSearchCriteria.java b/marketplace-service/src/main/java/com/axonivy/market/criteria/ProductSearchCriteria.java index 60ed02595..d20b243c8 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/criteria/ProductSearchCriteria.java +++ b/marketplace-service/src/main/java/com/axonivy/market/criteria/ProductSearchCriteria.java @@ -1,18 +1,17 @@ package com.axonivy.market.criteria; -import static com.axonivy.market.enums.DocumentField.NAMES; -import static com.axonivy.market.enums.DocumentField.SHORT_DESCRIPTIONS; - -import java.util.List; - import com.axonivy.market.enums.DocumentField; import com.axonivy.market.enums.Language; import com.axonivy.market.enums.TypeOption; - import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.List; + +import static com.axonivy.market.enums.DocumentField.NAMES; +import static com.axonivy.market.enums.DocumentField.SHORT_DESCRIPTIONS; + @Data @NoArgsConstructor @AllArgsConstructor diff --git a/marketplace-service/src/main/java/com/axonivy/market/entity/Image.java b/marketplace-service/src/main/java/com/axonivy/market/entity/Image.java index 85e6842ef..17fa50356 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/entity/Image.java +++ b/marketplace-service/src/main/java/com/axonivy/market/entity/Image.java @@ -23,9 +23,11 @@ public class Image { private String id; @Schema(description = "Product id", example = "jira-connector") private String productId; - @Schema(description = "The download url from github", example = "https://raw.githubusercontent.comamazon-comprehend/logo.png") + @Schema(description = "The download url from github", example = "https://raw.githubusercontent" + + ".comamazon-comprehend/logo.png") private String imageUrl; - @Schema(description = "The image content as binary type", example = "Binary(Buffer.from(\"89504e470d0a1a0a0000000d\", \"hex\"), 0)") + @Schema(description = "The image content as binary type", example = "Binary(Buffer.from" + + "(\"89504e470d0a1a0a0000000d\", \"hex\"), 0)") private Binary imageData; @Schema(description = "The SHA from github", example = "93b1e2f1595d3a85e51b01") private String sha; diff --git a/marketplace-service/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java b/marketplace-service/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java index e1bb99383..87b386e7e 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java +++ b/marketplace-service/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java @@ -20,7 +20,9 @@ public class MavenArtifactModel implements Serializable { private static final long serialVersionUID = 1L; @Schema(description = "Display name and type of artifact", example = "Adobe Acrobat Sign Connector (.iar)") private String name; - @Schema(description = "Artifact download url", example = "https://maven.axonivy.com/com/axonivy/connector/adobe/acrobat/sign/adobe-acrobat-sign-connector/10.0.25/adobe-acrobat-sign-connector-10.0.25.iar") + @Schema(description = "Artifact download url", example = "https://maven.axonivy" + + ".com/com/axonivy/connector/adobe/acrobat/sign/adobe-acrobat-sign-connector/10.0" + + ".25/adobe-acrobat-sign-connector-10.0.25.iar") private String downloadUrl; @Transient private Boolean isProductArtifact; 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 00db4d4e4..8d648dffe 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 @@ -1,6 +1,5 @@ package com.axonivy.market.entity; -import static com.axonivy.market.constants.EntityConstants.PRODUCT; import com.axonivy.market.github.model.MavenArtifact; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; @@ -21,6 +20,8 @@ import java.util.List; import java.util.Map; +import static com.axonivy.market.constants.EntityConstants.PRODUCT; + @Getter @Setter @AllArgsConstructor diff --git a/marketplace-service/src/main/java/com/axonivy/market/entity/ProductDesignerInstallation.java b/marketplace-service/src/main/java/com/axonivy/market/entity/ProductDesignerInstallation.java index 127a92ead..e558f0623 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/entity/ProductDesignerInstallation.java +++ b/marketplace-service/src/main/java/com/axonivy/market/entity/ProductDesignerInstallation.java @@ -1,6 +1,10 @@ package com.axonivy.market.entity; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.springframework.data.annotation.Id; @@ -18,24 +22,24 @@ @Builder @Document(PRODUCT_DESIGNER_INSTALLATION) public class ProductDesignerInstallation implements Serializable { - @Serial - private static final long serialVersionUID = 1L; - @Id - private String id; - private String productId; - private String designerVersion; - private int installationCount; + @Serial + private static final long serialVersionUID = 1L; + @Id + private String id; + private String productId; + private String designerVersion; + private int installationCount; - @Override - public int hashCode() { - return new HashCodeBuilder().append(productId).hashCode(); - } + @Override + public int hashCode() { + return new HashCodeBuilder().append(productId).hashCode(); + } - @Override - public boolean equals(Object obj) { - if (obj == null || this.getClass() != obj.getClass()) { - return false; - } - return new EqualsBuilder().append(productId, ((ProductDesignerInstallation) obj).getProductId()).isEquals(); + @Override + public boolean equals(Object obj) { + if (obj == null || this.getClass() != obj.getClass()) { + return false; } + return new EqualsBuilder().append(productId, ((ProductDesignerInstallation) obj).getProductId()).isEquals(); + } } diff --git a/marketplace-service/src/main/java/com/axonivy/market/entity/ProductModuleContent.java b/marketplace-service/src/main/java/com/axonivy/market/entity/ProductModuleContent.java index 08762f4ed..c05b21515 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 @@ -24,15 +24,16 @@ @Builder @Document(PRODUCT_MODULE_CONTENT) public class ProductModuleContent implements Serializable { - @Id - private String id; @Serial private static final long serialVersionUID = 1L; + @Id + private String id; @Schema(description = "product Id (from meta.json)", example = "portal") private String productId; @Schema(description = "Target release tag", example = "v10.0.25") private String tag; - @Schema(description = "Product detail description content ", example = "{ \"de\": \"E-Sign-Konnektor\", \"en\": \"E-sign connector\" }") + @Schema(description = "Product detail description content ", example = "{ \"de\": \"E-Sign-Konnektor\", \"en\": " + + "\"E-sign connector\" }") private Map description; @Schema(description = "Setup tab content", example = "{ \"de\": \"Setup\", \"en\": \"Setup\" ") private Map setup; diff --git a/marketplace-service/src/main/java/com/axonivy/market/enums/ErrorCode.java b/marketplace-service/src/main/java/com/axonivy/market/enums/ErrorCode.java index fa939c58c..11dd1fe73 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/enums/ErrorCode.java +++ b/marketplace-service/src/main/java/com/axonivy/market/enums/ErrorCode.java @@ -18,7 +18,8 @@ public enum ErrorCode { GH_FILE_STATUS_INVALID("0201", "GIT_HUB_FILE_STATUS_INVALID"), GH_FILE_TYPE_INVALID("0202", "GIT_HUB_FILE_TYPE_INVALID"), USER_NOT_FOUND("2103", "USER_NOT_FOUND"), GITHUB_USER_NOT_FOUND("2204", "GITHUB_USER_NOT_FOUND"), GITHUB_USER_UNAUTHORIZED("2205", "GITHUB_USER_UNAUTHORIZED"), - FEEDBACK_NOT_FOUND("3103", "FEEDBACK_NOT_FOUND"), NO_FEEDBACK_OF_USER_FOR_PRODUCT("3103", "NO_FEEDBACK_OF_USER_FOR_PRODUCT"), + FEEDBACK_NOT_FOUND("3103", "FEEDBACK_NOT_FOUND"), NO_FEEDBACK_OF_USER_FOR_PRODUCT("3103", + "NO_FEEDBACK_OF_USER_FOR_PRODUCT"), ARGUMENT_BAD_REQUEST("4000", "ARGUMENT_BAD_REQUEST"); String code; 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 index 4c914204a..4568b4925 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/enums/NonStandardProduct.java +++ b/marketplace-service/src/main/java/com/axonivy/market/enums/NonStandardProduct.java @@ -15,14 +15,21 @@ 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"), + 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"), + 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"), @@ -36,16 +43,18 @@ public enum NonStandardProduct { 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())); + NON_STANDARD_PRODUCT_MAP = Arrays.stream(NonStandardProduct.values()).collect( + Collectors.toMap(NonStandardProduct::getId, Function.identity())); } + private final String id; + private final boolean isVersionTagNumberOnly; + private final String pathToImageFolder; + private final String pathToProductFolder; + public static NonStandardProduct findById(String id) { return NON_STANDARD_PRODUCT_MAP.getOrDefault(id, DEFAULT); } diff --git a/marketplace-service/src/main/java/com/axonivy/market/exceptions/ExceptionHandlers.java b/marketplace-service/src/main/java/com/axonivy/market/exceptions/ExceptionHandlers.java index 24738b39a..900134acc 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/exceptions/ExceptionHandlers.java +++ b/marketplace-service/src/main/java/com/axonivy/market/exceptions/ExceptionHandlers.java @@ -84,6 +84,7 @@ public ResponseEntity handleOauth2ExchangeCodeException( errorMessage.setMessageDetails(oauth2ExchangeCodeException.getErrorDescription()); return new ResponseEntity<>(errorMessage, HttpStatus.BAD_REQUEST); } + @ExceptionHandler(UnauthorizedException.class) public ResponseEntity handleUnauthorizedException( UnauthorizedException unauthorizedException) { 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 a1ee2fdce..5e3f359e1 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 @@ -1,10 +1,5 @@ package com.axonivy.market.factory; -import static com.axonivy.market.constants.CommonConstants.SLASH; -import static com.axonivy.market.constants.MetaConstants.DEFAULT_VENDOR_NAME; -import static com.axonivy.market.constants.MetaConstants.DEFAULT_VENDOR_URL; -import static com.axonivy.market.constants.MetaConstants.META_FILE; -import static org.apache.commons.lang3.StringUtils.EMPTY; import com.axonivy.market.constants.CommonConstants; import com.axonivy.market.entity.Product; import com.axonivy.market.entity.ProductJsonContent; @@ -19,12 +14,17 @@ import org.apache.commons.lang3.StringUtils; import org.kohsuke.github.GHContent; import org.springframework.util.CollectionUtils; + import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import static com.axonivy.market.constants.CommonConstants.SLASH; +import static com.axonivy.market.constants.MetaConstants.*; +import static org.apache.commons.lang3.StringUtils.EMPTY; + @Log4j2 @NoArgsConstructor(access = AccessLevel.PRIVATE) public class ProductFactory { @@ -125,7 +125,8 @@ public static void mappingIdForProductModuleContent(ProductModuleContent content public static void mappingIdForProductJsonContent(ProductJsonContent content) { if (StringUtils.isNotBlank(content.getProductId()) && StringUtils.isNotBlank(content.getVersion())) { - content.setId(String.format(CommonConstants.ID_WITH_NUMBER_PATTERN, content.getProductId(), 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/model/GitHubProperty.java b/marketplace-service/src/main/java/com/axonivy/market/github/model/GitHubProperty.java index 3c9c52c59..d450eac61 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/github/model/GitHubProperty.java +++ b/marketplace-service/src/main/java/com/axonivy/market/github/model/GitHubProperty.java @@ -1,12 +1,11 @@ package com.axonivy.market.github.model; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; - import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; @Getter @Setter 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 1a846a105..1aaed75d2 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 @@ -12,7 +12,7 @@ import java.util.List; public interface GHAxonIvyProductRepoService { - + List extractMavenArtifactsFromContentStream(InputStream contentStream) throws IOException; GHContent getContentFromGHRepoAndTag(String repoName, String filePath, String tagVersion); @@ -23,5 +23,6 @@ public interface GHAxonIvyProductRepoService { List convertProductJsonToMavenProductInfo(GHContent content) throws IOException; - void extractReadMeFileFromContents(Product product, List contents, ProductModuleContent productModuleContent); + void extractReadMeFileFromContents(Product product, List contents, + ProductModuleContent productModuleContent); } diff --git a/marketplace-service/src/main/java/com/axonivy/market/github/service/GitHubService.java b/marketplace-service/src/main/java/com/axonivy/market/github/service/GitHubService.java index a5996d18b..a31309e4a 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/github/service/GitHubService.java +++ b/marketplace-service/src/main/java/com/axonivy/market/github/service/GitHubService.java @@ -1,20 +1,19 @@ package com.axonivy.market.github.service; -import java.io.IOException; -import java.util.List; - -import org.kohsuke.github.GHContent; -import org.kohsuke.github.GHOrganization; -import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GitHub; -import org.kohsuke.github.GHTag; - import com.axonivy.market.entity.User; import com.axonivy.market.exceptions.model.MissingHeaderException; import com.axonivy.market.exceptions.model.Oauth2ExchangeCodeException; import com.axonivy.market.exceptions.model.UnauthorizedException; import com.axonivy.market.github.model.GitHubAccessTokenResponse; import com.axonivy.market.github.model.GitHubProperty; +import org.kohsuke.github.GHContent; +import org.kohsuke.github.GHOrganization; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHTag; +import org.kohsuke.github.GitHub; + +import java.io.IOException; +import java.util.List; public interface GitHubService { diff --git a/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java index db2053337..e59833d5e 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java @@ -29,10 +29,9 @@ @Service public class GHAxonIvyMarketRepoServiceImpl implements GHAxonIvyMarketRepoService { private static final LocalDateTime INITIAL_COMMIT_DATE = LocalDateTime.of(2020, 10, 30, 0, 0); + private final GitHubService gitHubService; private GHOrganization organization; private GHRepository repository; - - private final GitHubService gitHubService; @Value("${market.github.market.branch}") private String marketRepoBranch; 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 edb0e1155..77e15aca2 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 @@ -6,8 +6,8 @@ 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.entity.ProductJsonContent; +import com.axonivy.market.entity.ProductModuleContent; import com.axonivy.market.enums.Language; import com.axonivy.market.enums.NonStandardProduct; import com.axonivy.market.factory.ProductFactory; @@ -51,13 +51,6 @@ @Log4j2 @Service public class GHAxonIvyProductRepoServiceImpl implements GHAxonIvyProductRepoService { - private GHOrganization organization; - private final GitHubService gitHubService; - private final ImageService imageService; - private final ProductJsonContentRepository productJsonContentRepository; - private String repoUrl; - private static final ObjectMapper objectMapper = new ObjectMapper(); - private static final String HASH = "#"; public static final String DEMO_SETUP_TITLE = "(?i)## Demo|## Setup"; public static final String IMAGE_EXTENSION = "(.*?).(jpeg|jpg|png|gif)"; public static final String README_IMAGE_FORMAT = "\\(([^)]*?%s[^)]*?)\\)"; @@ -65,6 +58,13 @@ public class GHAxonIvyProductRepoServiceImpl implements GHAxonIvyProductRepoServ public static final String DESCRIPTION = "description"; public static final String DEMO = "demo"; public static final String SETUP = "setup"; + private static final ObjectMapper objectMapper = new ObjectMapper(); + private static final String HASH = "#"; + private final GitHubService gitHubService; + private final ImageService imageService; + private final ProductJsonContentRepository productJsonContentRepository; + private GHOrganization organization; + private String repoUrl; public GHAxonIvyProductRepoServiceImpl(GitHubService gitHubService, ImageService imageService, ProductJsonContentRepository productJsonContentRepository) { @@ -73,6 +73,11 @@ public GHAxonIvyProductRepoServiceImpl(GitHubService gitHubService, ImageService this.productJsonContentRepository = productJsonContentRepository; } + 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 public List convertProductJsonToMavenProductInfo(GHContent content) throws IOException { InputStream contentStream = extractedContentStream(content); @@ -178,7 +183,7 @@ public ProductModuleContent getReadmeAndProductContentsFromTag(Product product, productModuleContent.setProductId(product.getId()); productModuleContent.setTag(tag); ProductFactory.mappingIdForProductModuleContent(productModuleContent); - updateDependencyContentsFromProductJson(productModuleContent, contents , product); + updateDependencyContentsFromProductJson(productModuleContent, contents, product); extractReadMeFileFromContents(product, contents, productModuleContent); } catch (Exception e) { log.error("Cannot get product.json content {}", e.getMessage()); @@ -187,7 +192,8 @@ public ProductModuleContent getReadmeAndProductContentsFromTag(Product product, return productModuleContent; } - public void extractReadMeFileFromContents(Product product, List contents, ProductModuleContent productModuleContent) { + public void extractReadMeFileFromContents(Product product, List contents, + ProductModuleContent productModuleContent) { try { List readmeFiles = contents.stream().filter(GHContent::isFile) .filter(content -> content.getName().startsWith(ReadmeConstants.README_FILE_NAME)).toList(); @@ -211,7 +217,8 @@ public void extractReadMeFileFromContents(Product product, List conte } /** - * MARP-810: Sabine requires that content in other languages, which has not been translated, be left empty and replaced with English content. + * MARP-810: Sabine requires that content in other languages, which has not been translated, be left empty and + * replaced with English content. */ public Map replaceEmptyContentsWithEnContent(Map map) { String enValue = map.get(Language.EN.getValue()); @@ -271,11 +278,6 @@ public String extractProductJsonContent(GHContent ghContent, String tag) { } } - private static GHContent getProductJsonFile(List contents) { - return contents.stream().filter(GHContent::isFile) - .filter(content -> ProductJsonConstants.PRODUCT_JSON_FILE.equals(content.getName())).findFirst().orElse(null); - } - public String updateImagesWithDownloadUrl(Product product, List contents, String readmeContents) { List imagesAtRootFolder = contents.stream().filter(GHContent::isFile) @@ -316,7 +318,7 @@ private List getImagesFromImageFolder(Product product, List> moduleContents, String readmeContents, + public void getExtractedPartsOfReadme(Map> moduleContents, String readmeContents, String locale) { String[] parts = readmeContents.split(DEMO_SETUP_TITLE); int demoIndex = readmeContents.indexOf(ReadmeConstants.DEMO_PART); @@ -348,7 +350,8 @@ public void getExtractedPartsOfReadme(Map> moduleConte addLocaleContent(moduleContents, SETUP, setup.trim(), locale); } - private void addLocaleContent(Map> moduleContents, String type, String content, String locale) { + private void addLocaleContent(Map> moduleContents, String type, String content, + String locale) { moduleContents.computeIfAbsent(type, key -> new HashMap<>()).put(locale, content); } 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 061e1fa63..11d2e36a1 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 @@ -20,9 +20,11 @@ public class ProductDetailModel extends ProductModel { private String newestReleaseVersion; @Schema(description = "Product cost", example = "Free") private String cost; - @Schema(description = "Source repository url", example = "https://github.com/axonivy-market/adobe-acrobat-sign-connector") + @Schema(description = "Source repository url", example = "https://github.com/axonivy-market/adobe-acrobat-sign" + + "-connector") private String sourceUrl; - @Schema(description = "Status badge url", example = "https://github.com/axonivy-market/adobe-acrobat-sign-connector/actions/workflows/ci.yml/badge.svg") + @Schema(description = "Status badge url", example = "https://github.com/axonivy-market/adobe-acrobat-sign-connector" + + "/actions/workflows/ci.yml/badge.svg") private String statusBadgeUrl; @Schema(description = "Default language", example = "English") private String language; diff --git a/marketplace-service/src/main/java/com/axonivy/market/model/ProductModel.java b/marketplace-service/src/main/java/com/axonivy/market/model/ProductModel.java index 74fbe9471..6e2b5e4cd 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/model/ProductModel.java +++ b/marketplace-service/src/main/java/com/axonivy/market/model/ProductModel.java @@ -22,11 +22,15 @@ public class ProductModel extends RepresentationModel { @Schema(description = "Product id", example = "jira-connector") private String id; - @Schema(description = "Product name by locale", example = "{ \"de\": \"Atlassian Jira\", \"en\": \"Atlassian Jira\" }") + @Schema(description = "Product name by locale", example = "{ \"de\": \"Atlassian Jira\", \"en\": \"Atlassian Jira\"" + + " }") private Map names; - @Schema(description = "Product's short descriptions by locale", example = "{ \"de\": \"Nutze den Jira Connector von Atlassian, um Jira-Tickets direkt von der Axon Ivy Plattform aus zu verfolgen.\", \"en\": \"Atlassian's Jira connector lets you track issues directly from the Axon Ivy platform\" }") + @Schema(description = "Product's short descriptions by locale", example = "{ \"de\": \"Nutze den Jira Connector von" + + " Atlassian, um Jira-Tickets direkt von der Axon Ivy Plattform aus zu verfolgen.\", \"en\": \"Atlassian's " + + "Jira connector lets you track issues directly from the Axon Ivy platform\" }") private Map shortDescriptions; - @Schema(description = "Product's logo url", example = "https://raw.githubusercontent.com/axonivy-market/market/feature/MARP-463-Multilingualism-for-Website/market/connector/jira/logo.png") + @Schema(description = "Product's logo url", example = "https://raw.githubusercontent" + + ".com/axonivy-market/market/feature/MARP-463-Multilingualism-for-Website/market/connector/jira/logo.png") private String logoUrl; @Schema(description = "Type of product", example = "connector") private String type; diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductDesignerInstallationRepository.java b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductDesignerInstallationRepository.java index 2b5789521..7d007f64f 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductDesignerInstallationRepository.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductDesignerInstallationRepository.java @@ -10,5 +10,5 @@ @Repository public interface ProductDesignerInstallationRepository extends MongoRepository { - List findByProductId(String productId, Sort sort); + List findByProductId(String productId, Sort sort); } diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductJsonContentRepository.java b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductJsonContentRepository.java index 9d8dbfc7a..46410b55f 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductJsonContentRepository.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductJsonContentRepository.java @@ -7,5 +7,5 @@ @Repository public interface ProductJsonContentRepository extends MongoRepository { - ProductJsonContent findByProductIdAndVersion(String productId , String version); + ProductJsonContent findByProductIdAndVersion(String productId, String version); } 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 123a30c1c..1436e6d90 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 @@ -5,6 +5,7 @@ import org.springframework.stereotype.Repository; @Repository -public interface ProductModuleContentRepository extends MongoRepository, CustomProductModuleContentRepository { +public interface ProductModuleContentRepository extends MongoRepository, + CustomProductModuleContentRepository { ProductModuleContent findByTagAndProductId(String tag, String productId); } diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductRepository.java b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductRepository.java index 946a87bc0..4fe090d01 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductRepository.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductRepository.java @@ -1,12 +1,12 @@ package com.axonivy.market.repository; +import com.axonivy.market.entity.Product; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; -import com.axonivy.market.entity.Product; - @Repository -public interface ProductRepository extends MongoRepository, ProductSearchRepository, CustomProductRepository { +public interface ProductRepository extends MongoRepository, ProductSearchRepository, + CustomProductRepository { Product findByLogoUrl(String logoUrl); diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductSearchRepository.java b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductSearchRepository.java index 976a8b64f..8f4b238f0 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductSearchRepository.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductSearchRepository.java @@ -2,7 +2,6 @@ import com.axonivy.market.criteria.ProductSearchCriteria; import com.axonivy.market.entity.Product; - import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; 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 a91d50200..6aa5418ec 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/impl/CustomProductRepositoryImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/impl/CustomProductRepositoryImpl.java @@ -3,8 +3,8 @@ import com.axonivy.market.constants.EntityConstants; import com.axonivy.market.constants.MongoDBConstants; import com.axonivy.market.entity.Product; -import com.axonivy.market.entity.ProductModuleContent; import com.axonivy.market.entity.ProductDesignerInstallation; +import com.axonivy.market.entity.ProductModuleContent; import com.axonivy.market.repository.CustomProductRepository; import com.axonivy.market.repository.CustomRepository; import com.axonivy.market.repository.ProductModuleContentRepository; @@ -29,7 +29,7 @@ public class CustomProductRepositoryImpl extends CustomRepository implements Cus public CustomProductRepositoryImpl(MongoTemplate mongoTemplate, ProductModuleContentRepository contentRepository) { this.mongoTemplate = mongoTemplate; - this.contentRepository = contentRepository; + this.contentRepository = contentRepository; } @@ -42,7 +42,7 @@ public Product queryProductByAggregation(Aggregation aggregation) { public Product getProductByIdAndTag(String id, String tag) { Product result = findProductById(id); if (!Objects.isNull(result)) { - ProductModuleContent content = contentRepository.findByTagAndProductId(tag,id); + ProductModuleContent content = contentRepository.findByTagAndProductId(tag, id); result.setProductModuleContent(content); } return result; @@ -75,7 +75,8 @@ public List getReleasedVersionsById(String id) { } public int updateInitialCount(String productId, int initialCount) { - Update update = new Update().inc(MongoDBConstants.INSTALLATION_COUNT, initialCount).set(MongoDBConstants.SYNCHRONIZED_INSTALLATION_COUNT, true); + 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); } @@ -93,11 +94,11 @@ public int increaseInstallationCount(String productId) { public void increaseInstallationCountForProductByDesignerVersion(String productId, String designerVersion) { Update update = new Update().inc(MongoDBConstants.INSTALLATION_COUNT, 1); mongoTemplate.upsert(createQueryByProductIdAndDesignerVersion(productId, designerVersion), - update, ProductDesignerInstallation.class); + update, ProductDesignerInstallation.class); } private Query createQueryByProductIdAndDesignerVersion(String productId, String designerVersion) { return new Query(Criteria.where(MongoDBConstants.PRODUCT_ID).is(productId) - .andOperator(Criteria.where(MongoDBConstants.DESIGNER_VERSION).is(designerVersion))); + .andOperator(Criteria.where(MongoDBConstants.DESIGNER_VERSION).is(designerVersion))); } } diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImpl.java b/marketplace-service/src/main/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImpl.java index 68a86856f..627cb2ed5 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImpl.java @@ -1,11 +1,11 @@ package com.axonivy.market.repository.impl; -import static com.axonivy.market.enums.DocumentField.LISTED; -import static com.axonivy.market.enums.DocumentField.TYPE; - -import java.util.ArrayList; -import java.util.List; - +import com.axonivy.market.criteria.ProductSearchCriteria; +import com.axonivy.market.entity.Product; +import com.axonivy.market.enums.DocumentField; +import com.axonivy.market.enums.Language; +import com.axonivy.market.enums.TypeOption; +import com.axonivy.market.repository.ProductSearchRepository; import org.apache.commons.lang3.StringUtils; import org.bson.BsonRegularExpression; import org.springframework.data.domain.Page; @@ -16,12 +16,11 @@ import org.springframework.data.mongodb.core.query.Query; import org.springframework.util.CollectionUtils; -import com.axonivy.market.criteria.ProductSearchCriteria; -import com.axonivy.market.entity.Product; -import com.axonivy.market.enums.DocumentField; -import com.axonivy.market.enums.Language; -import com.axonivy.market.enums.TypeOption; -import com.axonivy.market.repository.ProductSearchRepository; +import java.util.ArrayList; +import java.util.List; + +import static com.axonivy.market.enums.DocumentField.LISTED; +import static com.axonivy.market.enums.DocumentField.TYPE; public class ProductSearchRepositoryImpl implements ProductSearchRepository { @@ -103,7 +102,8 @@ private Criteria createQueryByKeywordRegex(ProductSearchCriteria searchCriteria) for (var property : filterProperties) { Criteria filterByKeywordCriteria; if (property.isLocalizedSupport()) { - filterByKeywordCriteria = Criteria.where(LOCALIZE_SEARCH_PATTERN.formatted(property.getFieldName(), language.getValue())); + filterByKeywordCriteria = Criteria.where( + LOCALIZE_SEARCH_PATTERN.formatted(property.getFieldName(), language.getValue())); } else { filterByKeywordCriteria = Criteria.where(property.getFieldName()); } 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 f87dbf830..96da2ae41 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 @@ -1,10 +1,9 @@ package com.axonivy.market.service; -import org.bson.types.Binary; -import org.kohsuke.github.GHContent; - import com.axonivy.market.entity.Image; import com.axonivy.market.entity.Product; +import org.bson.types.Binary; +import org.kohsuke.github.GHContent; public interface ImageService { Binary getImageBinary(GHContent ghContent); 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 b06fd5344..72e090f93 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 @@ -2,6 +2,7 @@ import com.axonivy.market.model.MavenArtifactVersionModel; import com.axonivy.market.model.VersionAndUrlModel; + import java.util.List; import java.util.Map; @@ -10,7 +11,7 @@ public interface VersionService { List getArtifactsAndVersionToDisplay(String productId, Boolean isShowDevVersion, String designerVersion); - Map getProductJsonContentByIdAndVersion(String name , String version); + Map getProductJsonContentByIdAndVersion(String name, String version); List getVersionsForDesigner(String productId); } \ No newline at end of file diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/impl/FeedbackServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/service/impl/FeedbackServiceImpl.java index 3d648c144..3ae807b26 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/impl/FeedbackServiceImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/impl/FeedbackServiceImpl.java @@ -47,7 +47,8 @@ public Feedback findFeedback(String id) throws NotFoundException { } @Override - public Feedback findFeedbackByUserIdAndProductId(String userId, String productId) throws NotFoundException, NoContentException { + public Feedback findFeedbackByUserIdAndProductId(String userId, + String productId) throws NotFoundException, NoContentException { if (StringUtils.isNotBlank(userId)) { validateUserExists(userId); } 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 1b589872f..98148550f 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 @@ -39,7 +39,7 @@ public Binary getImageBinary(GHContent ghContent) { @Override public Image mappingImageFromGHContent(Product product, GHContent ghContent, boolean isLogo) { if (ObjectUtils.isEmpty(ghContent)) { - log.info("There is missing for image content for product {}" , product.getId()); + log.info("There is missing for image content for product {}", product.getId()); return null; } diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductDesignerInstallationServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductDesignerInstallationServiceImpl.java index c95241e08..78cd4c70d 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductDesignerInstallationServiceImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductDesignerInstallationServiceImpl.java @@ -15,21 +15,24 @@ @Log4j2 @Service public class ProductDesignerInstallationServiceImpl implements ProductDesignerInstallationService { - private final ProductDesignerInstallationRepository productDesignerInstallationRepository; + private final ProductDesignerInstallationRepository productDesignerInstallationRepository; - public ProductDesignerInstallationServiceImpl(ProductDesignerInstallationRepository productDesignerInstallationRepository) { - this.productDesignerInstallationRepository = productDesignerInstallationRepository; - } + public ProductDesignerInstallationServiceImpl( + ProductDesignerInstallationRepository productDesignerInstallationRepository) { + this.productDesignerInstallationRepository = productDesignerInstallationRepository; + } - @Override - public List findByProductId(String productId) { - List designerInstallations = new ArrayList<>(); - List productDesignerInstallations = - productDesignerInstallationRepository.findByProductId(productId, Sort.by(Sort.Direction.DESC, MongoDBConstants.DESIGNER_VERSION)); - for (ProductDesignerInstallation productDesignerInstallation : productDesignerInstallations) { - DesignerInstallation designerInstallation = new DesignerInstallation(productDesignerInstallation.getDesignerVersion(), productDesignerInstallation.getInstallationCount()); - designerInstallations.add(designerInstallation); - } - return designerInstallations; + @Override + public List findByProductId(String productId) { + List designerInstallations = new ArrayList<>(); + List productDesignerInstallations = + productDesignerInstallationRepository.findByProductId(productId, + Sort.by(Sort.Direction.DESC, MongoDBConstants.DESIGNER_VERSION)); + for (ProductDesignerInstallation productDesignerInstallation : productDesignerInstallations) { + DesignerInstallation designerInstallation = new DesignerInstallation( + productDesignerInstallation.getDesignerVersion(), productDesignerInstallation.getInstallationCount()); + designerInstallations.add(designerInstallation); } + return designerInstallations; + } } diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java index 6c2c7ffb9..27583f967 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 @@ -77,6 +77,8 @@ @Service public class ProductServiceImpl implements ProductService { + public static final String NON_NUMERIC_CHAR = "[^0-9.]"; + private static final String INITIAL_VERSION = "1.0"; private final ProductRepository productRepository; private final ProductModuleContentRepository productModuleContentRepository; private final GHAxonIvyMarketRepoService axonIvyMarketRepoService; @@ -85,24 +87,17 @@ public class ProductServiceImpl implements ProductService { private final GitHubService gitHubService; private final ProductCustomSortRepository productCustomSortRepository; private final ImageRepository imageRepository; - private final ImageService imageService; private final MongoTemplate mongoTemplate; - + private final ObjectMapper mapper = new ObjectMapper(); + private final SecureRandom random = new SecureRandom(); private GHCommit lastGHCommit; private GitHubRepoMeta marketRepoMeta; - private final ObjectMapper mapper = new ObjectMapper(); - @Value("${synchronized.installation.counts.path}") private String installationCountPath; - @Value("${market.github.market.branch}") 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, ProductModuleContentRepository productModuleContentRepository, GHAxonIvyMarketRepoService axonIvyMarketRepoService, GHAxonIvyProductRepoService axonIvyProductRepoService, @@ -121,6 +116,10 @@ public ProductServiceImpl(ProductRepository productRepository, this.mongoTemplate = mongoTemplate; } + private static Predicate filterNonPersistGhTagName(List currentTags) { + return tag -> !currentTags.contains(tag.getName()); + } + @Override public Page findProducts(String type, String keyword, String language, Boolean isRESTClient, Pageable pageable) { @@ -155,8 +154,8 @@ public boolean syncLatestDataFromMarketRepo() { @Override public int updateInstallationCountForProduct(String key, String designerVersion) { - Product product= productRepository.getProductById(key); - if (Objects.isNull(product)){ + Product product = productRepository.getProductById(key); + if (Objects.isNull(product)) { return 0; } @@ -228,7 +227,7 @@ private void updateLatestChangeToProductsFromGithubRepo() { GHContent fileContent; try { fileContent = gitHubService.getGHContent(axonIvyMarketRepoService.getRepository(), file.getFileName(), - marketRepoBranch); + marketRepoBranch); } catch (IOException e) { log.error("Get GHContent failed: ", e); continue; @@ -245,35 +244,31 @@ private void updateLatestChangeToProductsFromGithubRepo() { }); } - private static Predicate filterNonPersistGhTagName(List currentTags) { - return tag -> !currentTags.contains(tag.getName()); - } - private void modifyProductLogo(String parentPath, GitHubFile file, Product product, GHContent fileContent) { Product result; switch (file.getStatus()) { - case MODIFIED, ADDED: - var searchCriteria = new ProductSearchCriteria(); - searchCriteria.setKeyword(parentPath); - searchCriteria.setFields(List.of(MARKET_DIRECTORY)); - result = productRepository.findByCriteria(searchCriteria); - if (result != null) { - Optional.ofNullable(imageService.mappingImageFromGHContent(result, fileContent, true)).ifPresent(image -> { - imageRepository.deleteById(result.getLogoId()); - result.setLogoId(image.getId()); - productRepository.save(result); - }); - } - break; - case REMOVED: - result = productRepository.findByLogoId(product.getLogoId()); - if (result != null) { - imageRepository.deleteAllByProductId(result.getId()); - productRepository.deleteById(result.getId()); - } - break; - default: - break; + case MODIFIED, ADDED: + var searchCriteria = new ProductSearchCriteria(); + searchCriteria.setKeyword(parentPath); + searchCriteria.setFields(List.of(MARKET_DIRECTORY)); + result = productRepository.findByCriteria(searchCriteria); + if (result != null) { + Optional.ofNullable(imageService.mappingImageFromGHContent(result, fileContent, true)).ifPresent(image -> { + imageRepository.deleteById(result.getLogoId()); + result.setLogoId(image.getId()); + productRepository.save(result); + }); + } + break; + case REMOVED: + result = productRepository.findByLogoId(product.getLogoId()); + if (result != null) { + imageRepository.deleteAllByProductId(result.getId()); + productRepository.deleteById(result.getId()); + } + break; + default: + break; } } @@ -352,7 +347,8 @@ private void updateLatestReleaseTagContentsFromProductRepo() { } } - private void updateProductContentForNonStandardProduct(Map.Entry> ghContentEntity, Product product) { + private void updateProductContentForNonStandardProduct(Map.Entry> ghContentEntity, + Product product) { ProductModuleContent initialContent = new ProductModuleContent(); initialContent.setTag(INITIAL_VERSION); initialContent.setProductId(product.getId()); @@ -496,8 +492,9 @@ public Product fetchProductDetail(String id) { public Product fetchBestMatchProductDetail(String id, String version) { List releasedVersions = productRepository.getReleasedVersionsById(id); String bestMatchVersion = VersionUtils.getBestMatchVersion(releasedVersions, version); - String bestMatchTag = VersionUtils.convertVersionToTag(id,bestMatchVersion); - Product product = StringUtils.isBlank(bestMatchTag) ? productRepository.getProductById(id) : productRepository.getProductByIdAndTag(id, bestMatchTag); + String bestMatchTag = VersionUtils.convertVersionToTag(id, bestMatchVersion); + Product product = StringUtils.isBlank(bestMatchTag) ? productRepository.getProductById( + id) : productRepository.getProductByIdAndTag(id, bestMatchTag); return Optional.ofNullable(product).map(productItem -> { updateProductInstallationCount(id, productItem); return productItem; diff --git a/marketplace-service/src/main/java/com/axonivy/market/util/ImageUtils.java b/marketplace-service/src/main/java/com/axonivy/market/util/ImageUtils.java index ddab4a2fe..608649e78 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/util/ImageUtils.java +++ b/marketplace-service/src/main/java/com/axonivy/market/util/ImageUtils.java @@ -1,8 +1,10 @@ package com.axonivy.market.util; -import static com.axonivy.market.constants.CommonConstants.IMAGE_ID_PREFIX; -import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; -import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; +import com.axonivy.market.controller.ImageController; +import com.axonivy.market.entity.ProductModuleContent; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.logging.log4j.util.Strings; +import org.springframework.hateoas.Link; import java.util.ArrayList; import java.util.List; @@ -10,11 +12,9 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import com.axonivy.market.controller.ImageController; -import org.apache.commons.lang3.ObjectUtils; -import org.apache.logging.log4j.util.Strings; -import org.springframework.hateoas.Link; -import com.axonivy.market.entity.ProductModuleContent; +import static com.axonivy.market.constants.CommonConstants.IMAGE_ID_PREFIX; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; public class ImageUtils { public static final String IMAGE_ID_FORMAT_PATTERN = "imageId-\\w+"; 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 6164e2dc1..60b146aa0 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 @@ -19,121 +19,130 @@ import java.util.stream.Stream; public class VersionUtils { - public static final String NON_NUMERIC_CHAR = "[^0-9.]"; + public static final String NON_NUMERIC_CHAR = "[^0-9.]"; - private VersionUtils() { + private VersionUtils() { + } + + public static List getVersionsToDisplay(List versions, Boolean isShowDevVersion, + String designerVersion) { + Stream versionStream = versions.stream(); + if (StringUtils.isNotBlank(designerVersion)) { + return versionStream.filter(version -> isMatchWithDesignerVersion(version, designerVersion)).sorted( + new LatestVersionComparator()).toList(); } - public static List getVersionsToDisplay(List versions, Boolean isShowDevVersion, String designerVersion) { - Stream versionStream = versions.stream(); - if (StringUtils.isNotBlank(designerVersion)) { - return versionStream.filter(version -> isMatchWithDesignerVersion(version, designerVersion)).sorted(new LatestVersionComparator()).toList(); - } - if (BooleanUtils.isTrue(isShowDevVersion)) { - return versionStream.filter(version -> isOfficialVersionOrUnReleasedDevVersion(versions, version)) - .sorted(new LatestVersionComparator()).toList(); - } - return versions.stream().filter(VersionUtils::isReleasedVersion).sorted(new LatestVersionComparator()).toList(); + if (BooleanUtils.isTrue(isShowDevVersion)) { + return versionStream.filter(version -> isOfficialVersionOrUnReleasedDevVersion(versions, version)) + .sorted(new LatestVersionComparator()).toList(); } - - public static String getBestMatchVersion(List versions, String designerVersion) { - String bestMatchVersion = versions.stream().filter(version -> StringUtils.equals(version, designerVersion)).findAny().orElse(null); - if(StringUtils.isBlank(bestMatchVersion)){ - bestMatchVersion = versions.stream().filter(version -> MavenVersionComparator.compare(version, designerVersion) < 0 && isReleasedVersion(version)).findAny().orElse(null); - } - if (StringUtils.isBlank(bestMatchVersion)) { - bestMatchVersion = versions.stream().filter(VersionUtils::isReleasedVersion).findAny().orElse(CollectionUtils.firstElement(versions)); - } - return bestMatchVersion; + return versions.stream().filter(VersionUtils::isReleasedVersion).sorted(new LatestVersionComparator()).toList(); + } + + public static String getBestMatchVersion(List versions, String designerVersion) { + String bestMatchVersion = versions.stream().filter( + version -> StringUtils.equals(version, designerVersion)).findAny().orElse(null); + if (StringUtils.isBlank(bestMatchVersion)) { + bestMatchVersion = versions.stream().filter( + version -> MavenVersionComparator.compare(version, designerVersion) < 0 && isReleasedVersion( + version)).findAny().orElse(null); } - - public static boolean isOfficialVersionOrUnReleasedDevVersion(List versions, String version) { - if (isReleasedVersion(version)) { - return true; - } - String bugfixVersion; - if (!isValidFormatReleasedVersion(version)) { - return false; - } else if (isSnapshotVersion(version)) { - bugfixVersion = getBugfixVersion(version.replace(MavenConstants.SNAPSHOT_RELEASE_POSTFIX, StringUtils.EMPTY)); - } else { - bugfixVersion = getBugfixVersion(version.split(MavenConstants.SPRINT_RELEASE_POSTFIX)[0]); - } - return versions.stream().noneMatch( - currentVersion -> !currentVersion.equals(version) && isReleasedVersion(currentVersion) && getBugfixVersion( - currentVersion).equals(bugfixVersion)); + if (StringUtils.isBlank(bestMatchVersion)) { + bestMatchVersion = versions.stream().filter(VersionUtils::isReleasedVersion).findAny().orElse( + CollectionUtils.firstElement(versions)); } + return bestMatchVersion; + } - public static boolean isSnapshotVersion(String version) { - return version.endsWith(MavenConstants.SNAPSHOT_RELEASE_POSTFIX); + public static boolean isOfficialVersionOrUnReleasedDevVersion(List versions, String version) { + if (isReleasedVersion(version)) { + return true; } - - public static boolean isSprintVersion(String version) { - return version.contains(MavenConstants.SPRINT_RELEASE_POSTFIX); + String bugfixVersion; + if (!isValidFormatReleasedVersion(version)) { + return false; + } else if (isSnapshotVersion(version)) { + bugfixVersion = getBugfixVersion(version.replace(MavenConstants.SNAPSHOT_RELEASE_POSTFIX, StringUtils.EMPTY)); + } else { + bugfixVersion = getBugfixVersion(version.split(MavenConstants.SPRINT_RELEASE_POSTFIX)[0]); } + return versions.stream().noneMatch( + currentVersion -> !currentVersion.equals(version) && isReleasedVersion(currentVersion) && getBugfixVersion( + currentVersion).equals(bugfixVersion)); + } - public static boolean isValidFormatReleasedVersion(String version) { - return StringUtils.isNumeric(version.split(MavenConstants.MAIN_VERSION_REGEX)[0]); - } + public static boolean isSnapshotVersion(String version) { + return version.endsWith(MavenConstants.SNAPSHOT_RELEASE_POSTFIX); + } - public static boolean isReleasedVersion(String version) { - return !(isSprintVersion(version) || isSnapshotVersion(version)) && isValidFormatReleasedVersion(version); - } + public static boolean isSprintVersion(String version) { + return version.contains(MavenConstants.SPRINT_RELEASE_POSTFIX); + } - public static boolean isMatchWithDesignerVersion(String version, String designerVersion) { - return isReleasedVersion(version) && version.startsWith(designerVersion); - } + public static boolean isValidFormatReleasedVersion(String version) { + return StringUtils.isNumeric(version.split(MavenConstants.MAIN_VERSION_REGEX)[0]); + } - public static String getBugfixVersion(String version) { - - if (isSnapshotVersion(version)) { - version = version.replace(MavenConstants.SNAPSHOT_RELEASE_POSTFIX, StringUtils.EMPTY); - } else if (isSprintVersion(version)) { - version = version.split(MavenConstants.SPRINT_RELEASE_POSTFIX)[0]; - } - String[] segments = version.split("\\."); - if (segments.length >= 3) { - segments[2] = segments[2].split(CommonConstants.DASH_SEPARATOR)[0]; - return segments[0] + CommonConstants.DOT_SEPARATOR + segments[1] + CommonConstants.DOT_SEPARATOR + segments[2]; - } - return version; - } + public static boolean isReleasedVersion(String version) { + return !(isSprintVersion(version) || isSnapshotVersion(version)) && isValidFormatReleasedVersion(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 boolean isMatchWithDesignerVersion(String version, String designerVersion) { + return isReleasedVersion(version) && version.startsWith(designerVersion); + } + + public static String getBugfixVersion(String version) { - public static List convertTagsToVersions (List tags){ - Objects.requireNonNull(tags); - return tags.stream().map(VersionUtils::convertTagToVersion).toList(); + if (isSnapshotVersion(version)) { + version = version.replace(MavenConstants.SNAPSHOT_RELEASE_POSTFIX, StringUtils.EMPTY); + } else if (isSprintVersion(version)) { + version = version.split(MavenConstants.SPRINT_RELEASE_POSTFIX)[0]; } + String[] segments = version.split("\\."); + if (segments.length >= 3) { + segments[2] = segments[2].split(CommonConstants.DASH_SEPARATOR)[0]; + return segments[0] + CommonConstants.DOT_SEPARATOR + segments[1] + CommonConstants.DOT_SEPARATOR + segments[2]; + } + return version; + } - 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 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 getOldestVersion(List tags) { - String result = StringUtils.EMPTY; - if (!CollectionUtils.isEmpty(tags)) { - List 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 String convertVersionToTag(String productId, String version) { + if (StringUtils.isBlank(version)) { + return version; } - 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(); + NonStandardProduct product = NonStandardProduct.findById(productId); + if (product.isVersionTagNumberOnly()) { + return version; + } + return GitHubConstants.STANDARD_TAG_PREFIX.concat(version); + } + + public static String getOldestVersion(List tags) { + String result = StringUtils.EMPTY; + if (!CollectionUtils.isEmpty(tags)) { + List 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 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(); + } } 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 35ca031e0..4413a0a02 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java +++ b/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java @@ -1,20 +1,19 @@ package com.axonivy.market; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - +import com.axonivy.market.entity.Product; import com.axonivy.market.entity.ProductDesignerInstallation; +import com.axonivy.market.enums.Language; +import com.axonivy.market.enums.SortOption; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; -import com.axonivy.market.entity.Product; -import com.axonivy.market.enums.Language; -import com.axonivy.market.enums.SortOption; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class BaseSetup { protected static final String SAMPLE_PRODUCT_ID = "amazon-comprehend"; diff --git a/marketplace-service/src/test/java/com/axonivy/market/assembler/ProductDetailModelAssemblerTest.java b/marketplace-service/src/test/java/com/axonivy/market/assembler/ProductDetailModelAssemblerTest.java index 75c2566b5..a4ceabf09 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/assembler/ProductDetailModelAssemblerTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/assembler/ProductDetailModelAssemblerTest.java @@ -46,14 +46,17 @@ void testToModelWithRequestPath() { @Test void testToModelWithRequestPathAndVersion() { - ProductDetailModel model = productDetailModelAssembler.toModel(mockProduct, VERSION, RequestMappingConstants.BY_ID_AND_VERSION); + ProductDetailModel model = productDetailModelAssembler.toModel(mockProduct, VERSION, + RequestMappingConstants.BY_ID_AND_VERSION); Assertions.assertTrue(model.getLink(SELF_RELATION).get().getHref().endsWith("/api/product-details/portal/10.0.19")); } @Test void testToModelWithRequestPathAndBestMatchVersion() { - ProductDetailModel model = productDetailModelAssembler.toModel(mockProduct, VERSION, RequestMappingConstants.BEST_MATCH_BY_ID_AND_VERSION); - Assertions.assertTrue(model.getLink(SELF_RELATION).get().getHref().endsWith("/api/product-details/portal/10.0.19/bestmatch")); + ProductDetailModel model = productDetailModelAssembler.toModel(mockProduct, VERSION, + RequestMappingConstants.BEST_MATCH_BY_ID_AND_VERSION); + Assertions.assertTrue( + model.getLink(SELF_RELATION).get().getHref().endsWith("/api/product-details/portal/10.0.19/bestmatch")); Assertions.assertTrue(model.getMetaProductJsonUrl().endsWith("/api/product-details/portal/10.0.8/json")); } } \ No newline at end of file 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 0c3e8ce39..0d5ad1678 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 @@ -32,10 +32,7 @@ import java.util.Map; import java.util.Objects; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.when; @@ -44,8 +41,10 @@ class ProductControllerTest { private static final String PRODUCT_NAME_SAMPLE = "Amazon Comprehend"; private static final String PRODUCT_NAME_DE_SAMPLE = "Amazon Comprehend DE"; - private static final String PRODUCT_DESC_SAMPLE = "Amazon Comprehend is a AI service that uses machine learning to uncover information in unstructured data."; - private static final String PRODUCT_DESC_DE_SAMPLE = "Amazon Comprehend is a AI service that uses machine learning to uncover information in unstructured data. DE"; + private static final String PRODUCT_DESC_SAMPLE = "Amazon Comprehend is a AI service that uses machine learning to " + + "uncover information in unstructured data."; + private static final String PRODUCT_DESC_DE_SAMPLE = "Amazon Comprehend is a AI service that uses machine learning " + + "to uncover information in unstructured data. DE"; private static final String AUTHORIZATION_HEADER = "Bearer valid_token"; private static final String INVALID_AUTHORIZATION_HEADER = "Bearer invalid_token"; @@ -73,7 +72,7 @@ void setup() { void testFindProductsAsEmpty() { PageRequest pageable = PageRequest.of(0, 20); Page mockProducts = new PageImpl<>(List.of(), pageable, 0); - when(service.findProducts(any(), any(), any(), any() , any())).thenReturn(mockProducts); + when(service.findProducts(any(), any(), any(), any(), any())).thenReturn(mockProducts); when(pagedResourcesAssembler.toEmptyModel(any(), any())).thenReturn(PagedModel.empty()); var result = productController.findProducts(TypeOption.ALL.getOption(), null, "en", false, pageable); assertEquals(HttpStatus.OK, result.getStatusCode()); diff --git a/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDesignerInstallationControllerTest.java b/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDesignerInstallationControllerTest.java index b8fd3f553..b4ab1cd3f 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDesignerInstallationControllerTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDesignerInstallationControllerTest.java @@ -17,23 +17,25 @@ @ExtendWith(MockitoExtension.class) class ProductDesignerInstallationControllerTest { - public static final String DESIGNER_VERSION = "11.4.0"; + public static final String DESIGNER_VERSION = "11.4.0"; - @Mock - ProductDesignerInstallationService productDesignerInstallationService; + @Mock + ProductDesignerInstallationService productDesignerInstallationService; - @InjectMocks - private ProductDesignerInstallationController productDesignerInstallationController; + @InjectMocks + private ProductDesignerInstallationController productDesignerInstallationController; - @Test - void testGetProductDesignerInstallationByProductId() { - List models = List.of(new DesignerInstallation(DESIGNER_VERSION, 5)); - Mockito.when(productDesignerInstallationService.findByProductId(Mockito.anyString())).thenReturn(models); - ResponseEntity> result = productDesignerInstallationController.getProductDesignerInstallationByProductId("portal"); - Assertions.assertEquals(HttpStatus.OK, result.getStatusCode()); - Assertions.assertEquals(1, Objects.requireNonNull(result.getBody()).size()); - Assertions.assertEquals(DESIGNER_VERSION, result.getBody().get(0).getDesignerVersion()); - Assertions.assertEquals(5, result.getBody().get(0).getNumberOfDownloads()); - Assertions.assertEquals(models, result.getBody()); - } + @Test + void testGetProductDesignerInstallationByProductId() { + List models = List.of(new DesignerInstallation(DESIGNER_VERSION, 5)); + Mockito.when(productDesignerInstallationService.findByProductId(Mockito.anyString())).thenReturn(models); + ResponseEntity> result = + productDesignerInstallationController.getProductDesignerInstallationByProductId( + "portal"); + Assertions.assertEquals(HttpStatus.OK, result.getStatusCode()); + Assertions.assertEquals(1, Objects.requireNonNull(result.getBody()).size()); + Assertions.assertEquals(DESIGNER_VERSION, result.getBody().get(0).getDesignerVersion()); + Assertions.assertEquals(5, result.getBody().get(0).getNumberOfDownloads()); + Assertions.assertEquals(models, result.getBody()); + } } diff --git a/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java b/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java index 42ae3db76..9feb56a37 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,21 +1,17 @@ package com.axonivy.market.controller; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.util.List; -import java.util.Objects; -import java.util.HashMap; -import java.util.Map; +import com.axonivy.market.assembler.ProductDetailModelAssembler; import com.axonivy.market.constants.RequestMappingConstants; +import com.axonivy.market.entity.Product; import com.axonivy.market.entity.ProductJsonContent; +import com.axonivy.market.enums.Language; +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; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -26,42 +22,41 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import com.axonivy.market.assembler.ProductDetailModelAssembler; -import com.axonivy.market.entity.Product; -import com.axonivy.market.enums.Language; -import com.axonivy.market.model.MavenArtifactVersionModel; -import com.axonivy.market.model.ProductDetailModel; -import com.axonivy.market.service.ProductService; -import com.axonivy.market.service.VersionService; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class ProductDetailsControllerTest { public static final String TAG = "v10.0.6"; - @Mock - private ProductService productService; - - @Mock - private ImageService imageService; - + public static final String DOCKER_CONNECTOR_ID = "docker-connector"; + private static final String PRODUCT_NAME_SAMPLE = "Docker"; + private static final String PRODUCT_NAME_DE_SAMPLE = "Docker DE"; @Mock VersionService versionService; - @Mock ProductDesignerInstallationService productDesignerInstallationService; - + @Mock + private ProductService productService; + @Mock + private ImageService imageService; @Mock private ProductDetailModelAssembler detailModelAssembler; - @InjectMocks private ProductDetailsController productDetailsController; - private static final String PRODUCT_NAME_SAMPLE = "Docker"; - private static final String PRODUCT_NAME_DE_SAMPLE = "Docker DE"; - public static final String DOCKER_CONNECTOR_ID = "docker-connector"; @Test void testProductDetails() { Mockito.when(productService.fetchProductDetail(Mockito.anyString())).thenReturn(mockProduct()); - Mockito.when(detailModelAssembler.toModel(mockProduct(), RequestMappingConstants.BY_ID)).thenReturn(createProductMockWithDetails()); + Mockito.when(detailModelAssembler.toModel(mockProduct(), RequestMappingConstants.BY_ID)).thenReturn( + createProductMockWithDetails()); ResponseEntity mockExpectedResult = new ResponseEntity<>(createProductMockWithDetails(), HttpStatus.OK); @@ -79,24 +74,31 @@ void testProductDetails() { @Test void testFindBestMatchProductDetailsByVersion() { - Mockito.when(productService.fetchBestMatchProductDetail(Mockito.anyString(), Mockito.anyString())).thenReturn(mockProduct()); - Mockito.when(detailModelAssembler.toModel(mockProduct(), TAG, RequestMappingConstants.BEST_MATCH_BY_ID_AND_VERSION)).thenReturn(createProductMockWithDetails()); + Mockito.when(productService.fetchBestMatchProductDetail(Mockito.anyString(), Mockito.anyString())).thenReturn( + mockProduct()); + Mockito.when(detailModelAssembler.toModel(mockProduct(), TAG, + RequestMappingConstants.BEST_MATCH_BY_ID_AND_VERSION)).thenReturn(createProductMockWithDetails()); ResponseEntity mockExpectedResult = new ResponseEntity<>(createProductMockWithDetails(), HttpStatus.OK); - ResponseEntity result = productDetailsController.findBestMatchProductDetailsByVersion(DOCKER_CONNECTOR_ID, TAG); + ResponseEntity result = productDetailsController.findBestMatchProductDetailsByVersion( + DOCKER_CONNECTOR_ID, TAG); assertEquals(HttpStatus.OK, result.getStatusCode()); assertEquals(result, mockExpectedResult); verify(productService, times(1)).fetchBestMatchProductDetail(DOCKER_CONNECTOR_ID, TAG); - verify(detailModelAssembler, times(1)).toModel(mockProduct(), TAG, RequestMappingConstants.BEST_MATCH_BY_ID_AND_VERSION); + verify(detailModelAssembler, times(1)).toModel(mockProduct(), TAG, + RequestMappingConstants.BEST_MATCH_BY_ID_AND_VERSION); } @Test void testProductDetailsWithVersion() { - Mockito.when(productService.fetchProductDetailByIdAndVersion(Mockito.anyString(), Mockito.anyString())).thenReturn(mockProduct()); - Mockito.when(detailModelAssembler.toModel(mockProduct(), TAG, RequestMappingConstants.BY_ID_AND_VERSION)).thenReturn(createProductMockWithDetails()); + Mockito.when(productService.fetchProductDetailByIdAndVersion(Mockito.anyString(), Mockito.anyString())).thenReturn( + mockProduct()); + Mockito.when( + detailModelAssembler.toModel(mockProduct(), TAG, RequestMappingConstants.BY_ID_AND_VERSION)).thenReturn( + createProductMockWithDetails()); ResponseEntity mockExpectedResult = new ResponseEntity<>(createProductMockWithDetails(), HttpStatus.OK); @@ -113,7 +115,8 @@ void testProductDetailsWithVersion() { void testFindProductVersionsById() { List models = List.of(new MavenArtifactVersionModel()); Mockito.when( - versionService.getArtifactsAndVersionToDisplay(Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyString())) + versionService.getArtifactsAndVersionToDisplay(Mockito.anyString(), Mockito.anyBoolean(), + Mockito.anyString())) .thenReturn(models); ResponseEntity> result = productDetailsController.findProductVersionsById("portal", true, "10.0.1"); @@ -146,7 +149,7 @@ void findProductVersionsById() { Objects.requireNonNull(result.getBody()).get(1).getUrl()); } - private List mockVersionAndUrlModels(){ + private List mockVersionAndUrlModels() { VersionAndUrlModel versionAndUrlModel = VersionAndUrlModel.builder() .version("10.0.21") .url("/api/product-details/productjsoncontent/portal/10.0.21") @@ -157,13 +160,13 @@ private List mockVersionAndUrlModels(){ .url("/api/product-details/productjsoncontent/portal/10.0.22") .build(); - return List.of(versionAndUrlModel,versionAndUrlModel2); + return List.of(versionAndUrlModel, versionAndUrlModel2); } @Test void findProductJsonContentByIdAndTag() throws IOException { ProductJsonContent productJsonContent = mockProductJsonContent(); - Map map = new ObjectMapper().readValue(productJsonContent.getContent(), Map.class); + Map map = new ObjectMapper().readValue(productJsonContent.getContent(), Map.class); when(versionService.getProductJsonContentByIdAndVersion("bpmnstatistic", "10.0.21")).thenReturn( map); 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 24aa148c7..0b36d91b7 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 @@ -2,6 +2,7 @@ import com.axonivy.market.constants.ProductJsonConstants; import com.axonivy.market.entity.Product; +import com.axonivy.market.enums.Language; import com.axonivy.market.github.model.Meta; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -20,11 +21,10 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import com.axonivy.market.enums.Language; - @ExtendWith(MockitoExtension.class) class ProductFactoryTest { - private static final String DUMMY_LOGO_URL = "https://raw.githubusercontent.com/axonivy-market/market/master/market/connector/amazon-comprehend-connector/logo.png"; + private static final String DUMMY_LOGO_URL = "https://raw.githubusercontent" + + ".com/axonivy-market/market/master/market/connector/amazon-comprehend-connector/logo.png"; @Test void testMappingByGHContent() throws IOException { @@ -77,15 +77,15 @@ void testExtractSourceUrl() { @Test void testTransferComputedData() { - String initialVersion ="10.0.2"; + String initialVersion = "10.0.2"; Product product = new Product(); Product persistedData = new Product(); persistedData.setCustomOrder(1); persistedData.setReleasedVersions(List.of(initialVersion)); persistedData.setNewestReleaseVersion(initialVersion); - ProductFactory.transferComputedPersistedDataToProduct(persistedData,product); - assertEquals(1,product.getCustomOrder()); + ProductFactory.transferComputedPersistedDataToProduct(persistedData, product); + assertEquals(1, product.getCustomOrder()); assertEquals(initialVersion, product.getNewestReleaseVersion()); assertEquals(1, product.getReleasedVersions().size()); assertEquals(initialVersion, product.getReleasedVersions().get(0)); 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 3938e5d54..fc61922d9 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 @@ -19,25 +19,18 @@ import java.util.List; -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.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class CustomProductRepositoryImplTest extends BaseSetup { private static final String ID = "bmpn-statistic"; private static final String TAG = "v10.0.21"; - private Product mockProduct; - private Aggregation mockAggregation; - @Mock ProductModuleContentRepository contentRepo; - + private Product mockProduct; + private Aggregation mockAggregation; @Mock private MongoTemplate mongoTemplate; @@ -56,7 +49,8 @@ private void setUpMockAggregateResult() { mockAggregation = mock(Aggregation.class); AggregationResults aggregationResults = mock(AggregationResults.class); - when(mongoTemplate.aggregate(any(Aggregation.class), eq(MongoDBConstants.PRODUCT_COLLECTION), eq(Product.class))).thenReturn(aggregationResults); + when(mongoTemplate.aggregate(any(Aggregation.class), eq(MongoDBConstants.PRODUCT_COLLECTION), + eq(Product.class))).thenReturn(aggregationResults); mockProduct = new Product(); mockProduct.setId(ID); when(aggregationResults.getUniqueMappedResult()).thenReturn(mockProduct); @@ -66,7 +60,8 @@ private void setUpMockAggregateResult() { void testQueryProductByAggregation_WhenResultIsNull() { Aggregation aggregation = mock(Aggregation.class); AggregationResults aggregationResults = mock(AggregationResults.class); - when(mongoTemplate.aggregate(any(Aggregation.class), eq(MongoDBConstants.PRODUCT_COLLECTION), eq(Product.class))).thenReturn(aggregationResults); + when(mongoTemplate.aggregate(any(Aggregation.class), eq(MongoDBConstants.PRODUCT_COLLECTION), + eq(Product.class))).thenReturn(aggregationResults); when(aggregationResults.getUniqueMappedResult()).thenReturn(null); Product actualProduct = repo.queryProductByAggregation(aggregation); @@ -78,7 +73,8 @@ void testQueryProductByAggregation_WhenResultIsNull() { void testReleasedVersionsById_WhenResultIsNull() { AggregationResults aggregationResults = mock(AggregationResults.class); - when(mongoTemplate.aggregate(any(Aggregation.class), eq(MongoDBConstants.PRODUCT_COLLECTION), eq(Product.class))).thenReturn(aggregationResults); + when(mongoTemplate.aggregate(any(Aggregation.class), eq(MongoDBConstants.PRODUCT_COLLECTION), + eq(Product.class))).thenReturn(aggregationResults); when(aggregationResults.getUniqueMappedResult()).thenReturn(null); List results = repo.getReleasedVersionsById(ID); @@ -113,15 +109,18 @@ void testIncreaseInstallationCount() { Product product = new Product(); product.setId(productId); product.setInstallationCount(5); - when(mongoTemplate.findAndModify(any(Query.class), any(Update.class), any(FindAndModifyOptions.class), eq(Product.class))).thenReturn(product); + when(mongoTemplate.findAndModify(any(Query.class), any(Update.class), any(FindAndModifyOptions.class), + eq(Product.class))).thenReturn(product); int updatedCount = repo.increaseInstallationCount(productId); assertEquals(5, updatedCount); - verify(mongoTemplate).findAndModify(any(Query.class), any(Update.class), any(FindAndModifyOptions.class), eq(Product.class)); + verify(mongoTemplate).findAndModify(any(Query.class), any(Update.class), any(FindAndModifyOptions.class), + eq(Product.class)); } @Test void testIncreaseInstallationCount_NullProduct() { - when(mongoTemplate.findAndModify(any(Query.class), any(Update.class), any(FindAndModifyOptions.class), eq(Product.class))).thenReturn(null); + when(mongoTemplate.findAndModify(any(Query.class), any(Update.class), any(FindAndModifyOptions.class), + eq(Product.class))).thenReturn(null); int updatedCount = repo.increaseInstallationCount(ID); assertEquals(0, updatedCount); } @@ -131,7 +130,9 @@ void testUpdateInitialCount() { setUpMockAggregateResult(); int initialCount = 10; repo.updateInitialCount(ID, initialCount); - verify(mongoTemplate).updateFirst(any(Query.class), eq(new Update().inc("InstallationCount", initialCount).set("SynchronizedInstallationCount", true)), eq(Product.class)); + verify(mongoTemplate).updateFirst(any(Query.class), + eq(new Update().inc("InstallationCount", initialCount).set("SynchronizedInstallationCount", true)), + eq(Product.class)); } @Test diff --git a/marketplace-service/src/test/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImplTest.java index bdcfeb12e..f6adc1434 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImplTest.java @@ -1,15 +1,10 @@ package com.axonivy.market.repository.impl; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -import java.util.List; - +import com.axonivy.market.BaseSetup; +import com.axonivy.market.criteria.ProductSearchCriteria; +import com.axonivy.market.entity.Product; +import com.axonivy.market.enums.DocumentField; +import com.axonivy.market.enums.Language; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -19,11 +14,12 @@ import org.springframework.data.domain.Page; import org.springframework.data.mongodb.core.MongoTemplate; -import com.axonivy.market.BaseSetup; -import com.axonivy.market.criteria.ProductSearchCriteria; -import com.axonivy.market.entity.Product; -import com.axonivy.market.enums.DocumentField; -import com.axonivy.market.enums.Language; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class ProductSearchRepositoryImplTest extends BaseSetup { diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/FeedbackServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/FeedbackServiceImplTest.java index 45be07c10..d8af7a6c6 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/impl/FeedbackServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/FeedbackServiceImplTest.java @@ -27,14 +27,8 @@ import java.util.List; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class FeedbackServiceImplTest { @@ -170,7 +164,7 @@ void testFindFeedbackByUserIdAndProductId_NotFound() { when(userRepository.findById(userId)).thenReturn(Optional.empty()); NotFoundException exception = assertThrows(NotFoundException.class, - () -> feedbackService.findFeedbackByUserIdAndProductId(userId, "product")); + () -> feedbackService.findFeedbackByUserIdAndProductId(userId, "product")); assertEquals(ErrorCode.USER_NOT_FOUND.getCode(), exception.getCode()); verify(userRepository, times(1)).findById(userId); } 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 b4ebfce5e..db5a544f6 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 @@ -1,26 +1,18 @@ package com.axonivy.market.service.impl; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - +import com.axonivy.market.constants.CommonConstants; +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.entity.ProductJsonContent; +import com.axonivy.market.enums.Language; +import com.axonivy.market.github.model.MavenArtifact; +import com.axonivy.market.github.service.GitHubService; +import com.axonivy.market.github.service.impl.GHAxonIvyProductRepoServiceImpl; +import com.axonivy.market.repository.ProductJsonContentRepository; +import com.axonivy.market.service.ImageService; +import com.fasterxml.jackson.databind.JsonNode; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -36,29 +28,27 @@ import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; -import com.axonivy.market.constants.CommonConstants; -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.entity.ProductJsonContent; -import com.axonivy.market.enums.Language; -import com.axonivy.market.github.model.MavenArtifact; -import com.axonivy.market.github.service.GitHubService; -import com.axonivy.market.github.service.impl.GHAxonIvyProductRepoServiceImpl; -import com.axonivy.market.repository.ProductJsonContentRepository; -import com.axonivy.market.service.ImageService; -import com.fasterxml.jackson.databind.JsonNode; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class GHAxonIvyProductRepoServiceImplTest { - private static final String DUMMY_TAG = "v1.0.0"; public static final String RELEASE_TAG = "v10.0.0"; public static final String IMAGE_NAME = "image.png"; public static final String DOCUWARE_CONNECTOR_PRODUCT = "docuware-connector-product"; public static final String IMAGE_DOWNLOAD_URL = "https://raw.githubusercontent.com/image.png"; - + private static final String DUMMY_TAG = "v1.0.0"; @Mock PagedIterable listTags; @@ -89,6 +79,107 @@ class GHAxonIvyProductRepoServiceImplTest { @Spy private GHAxonIvyProductRepoServiceImpl axonivyProductRepoServiceImpl; + 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; + } + + 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()); + } + + private static InputStream getMockInputStream() { + String jsonContent = getMockProductJsonContent(); + return new ByteArrayInputStream(jsonContent.getBytes(StandardCharsets.UTF_8)); + } + + private static String getMockProductJsonContent() { + return """ + { + "$schema": "https://json-schema.axonivy.com/market/10.0.0/product.json", + "installers": [ + { + "id": "maven-import", + "data": { + "projects": [ + { + "groupId": "com.axonivy.utils.bpmnstatistic", + "artifactId": "bpmn-statistic-demo", + "version": "${version}", + "type": "iar" + } + ], + "repositories": [ + { + "id": "maven.axonivy.com", + "url": "https://maven.axonivy.com", + "snapshots": { + "enabled": "true" + } + } + ] + } + }, + { + "id": "maven-dependency", + "data": { + "dependencies": [ + { + "groupId": "com.axonivy.utils.bpmnstatistic", + "artifactId": "bpmn-statistic", + "version": "${version}", + "type": "iar" + } + ], + "repositories": [ + { + "id": "maven.axonivy.com", + "url": "https://maven.axonivy.com", + "snapshots": { + "enabled": "true" + } + } + ] + } + } + ] + } + """; + } + + private static InputStream getMockInputStreamWithOutProjectAndDependency() { + String jsonContent = """ + { + "installers": [ + { + "data": { + "repositories": [ + { + "url": "http://example.com/repo" + } + ] + } + } + ] + } + """; + return new ByteArrayInputStream(jsonContent.getBytes(StandardCharsets.UTF_8)); + } + + private static GHContent createMockProductJson() { + GHContent mockProductJson = mock(GHContent.class); + when(mockProductJson.isFile()).thenReturn(true); + when(mockProductJson.getName()).thenReturn(ProductJsonConstants.PRODUCT_JSON_FILE, IMAGE_NAME); + return mockProductJson; + } + void setup() throws IOException { when(gitHubService.getOrganization(any())).thenReturn(mockGHOrganization); when(mockGHOrganization.getRepository(any())).thenReturn(ghRepository); @@ -216,9 +307,11 @@ void testGetOrganization() throws IOException { @Test void testGetReadmeAndProductContentsFromTag() throws IOException { - String readmeContentWithImage = "#Product-name\n Test README\n## Demo\nDemo content\n## Setup\nSetup content (image.png)"; + String readmeContentWithImage = "#Product-name\n Test README\n## Demo\nDemo content\n## Setup\nSetup content " + + "(image.png)"; testGetReadmeAndProductContentsFromTagWithReadmeText(readmeContentWithImage); - String readmeContentWithoutHashProductName = "Test README\n## Demo\nDemo content\n## Setup\nSetup content (image.png)"; + String readmeContentWithoutHashProductName = "Test README\n## Demo\nDemo content\n## Setup\nSetup content (image" + + ".png)"; testGetReadmeAndProductContentsFromTagWithReadmeText(readmeContentWithoutHashProductName); } @@ -228,7 +321,7 @@ private void testGetReadmeAndProductContentsFromTagWithReadmeText(String readmeC getReadmeInputStream(readmeContentWithImage, mockContent); InputStream inputStream = getMockInputStream(); Mockito.when(axonivyProductRepoServiceImpl.extractedContentStream(any())).thenReturn(inputStream); - Mockito.when(imageService.mappingImageFromGHContent(any(),any(),anyBoolean())).thenReturn(mockImage()); + Mockito.when(imageService.mappingImageFromGHContent(any(), any(), anyBoolean())).thenReturn(mockImage()); var result = axonivyProductRepoServiceImpl.getReadmeAndProductContentsFromTag(createMockProduct(), ghRepository, RELEASE_TAG); @@ -242,24 +335,15 @@ private void testGetReadmeAndProductContentsFromTagWithReadmeText(String readmeC assertEquals("Setup content (imageId-66e2b14868f2f95b2f95549a)", result.getSetup().get(Language.EN.getValue())); } - 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_ImageFromFolder() throws IOException { - String readmeContentWithImageFolder = "#Product-name\n Test README\n## Demo\nDemo content\n## Setup\nSetup content (./images/image.png)"; + String readmeContentWithImageFolder = "#Product-name\n Test README\n## Demo\nDemo content\n## Setup\nSetup " + + "content (./images/image.png)"; GHContent mockImageFile = mock(GHContent.class); when(mockImageFile.getName()).thenReturn(ReadmeConstants.IMAGES, IMAGE_NAME); when(mockImageFile.isDirectory()).thenReturn(true); - Mockito.when(imageService.mappingImageFromGHContent(any(),any() ,anyBoolean())).thenReturn(mockImage()); + Mockito.when(imageService.mappingImageFromGHContent(any(), any(), anyBoolean())).thenReturn(mockImage()); PagedIterable pagedIterable = Mockito.mock(String.valueOf(GHContent.class)); when(mockImageFile.listDirectoryContent()).thenReturn(pagedIterable); when(pagedIterable.toList()).thenReturn(List.of(mockImageFile)); @@ -268,7 +352,8 @@ void testGetReadmeAndProductContentFromTag_ImageFromFolder() throws IOException List.of(mockImageFile), readmeContentWithImageFolder); assertEquals( - "#Product-name\n Test README\n## Demo\nDemo content\n## Setup\nSetup content (imageId-66e2b14868f2f95b2f95549a)", + "#Product-name\n Test README\n## Demo\nDemo content\n## Setup\nSetup content " + + "(imageId-66e2b14868f2f95b2f95549a)", updatedReadme); } @@ -301,92 +386,8 @@ void testGetReadmeAndProductContentsFromTag_SwitchPartsPosition() throws IOExcep assertEquals("Setup content", result.getSetup().get(Language.EN.getValue())); } - 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()); - } - - private static InputStream getMockInputStream() { - String jsonContent = getMockProductJsonContent(); - return new ByteArrayInputStream(jsonContent.getBytes(StandardCharsets.UTF_8)); - } - - private static String getMockProductJsonContent(){ - return """ - { - "$schema": "https://json-schema.axonivy.com/market/10.0.0/product.json", - "installers": [ - { - "id": "maven-import", - "data": { - "projects": [ - { - "groupId": "com.axonivy.utils.bpmnstatistic", - "artifactId": "bpmn-statistic-demo", - "version": "${version}", - "type": "iar" - } - ], - "repositories": [ - { - "id": "maven.axonivy.com", - "url": "https://maven.axonivy.com", - "snapshots": { - "enabled": "true" - } - } - ] - } - }, - { - "id": "maven-dependency", - "data": { - "dependencies": [ - { - "groupId": "com.axonivy.utils.bpmnstatistic", - "artifactId": "bpmn-statistic", - "version": "${version}", - "type": "iar" - } - ], - "repositories": [ - { - "id": "maven.axonivy.com", - "url": "https://maven.axonivy.com", - "snapshots": { - "enabled": "true" - } - } - ] - } - } - ] - } - """; - } - - private static InputStream getMockInputStreamWithOutProjectAndDependency() { - String jsonContent = """ - { - "installers": [ - { - "data": { - "repositories": [ - { - "url": "http://example.com/repo" - } - ] - } - } - ] - } - """; - return new ByteArrayInputStream(jsonContent.getBytes(StandardCharsets.UTF_8)); - } - private Product createMockProduct() { - Map names = Map.of("en","docuware-connector-name"); + Map names = Map.of("en", "docuware-connector-name"); Product product = new Product(); product.setId("docuware-connector"); product.setNames(names); @@ -422,22 +423,17 @@ private GHContent createMockProductFolderWithProductJson() throws IOException { return mockContent; } - private static GHContent createMockProductJson() { - GHContent mockProductJson = mock(GHContent.class); - when(mockProductJson.isFile()).thenReturn(true); - when(mockProductJson.getName()).thenReturn(ProductJsonConstants.PRODUCT_JSON_FILE, IMAGE_NAME); - return mockProductJson; - } - @Test void test_insertProductJsonContent() throws IOException { ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(ProductJsonContent.class); - String readmeContentWithImage = "#Product-name\n Test README\n## Demo\nDemo content\n## Setup\nSetup content (image.png)"; + String readmeContentWithImage = "#Product-name\n Test README\n## Demo\nDemo content\n## Setup\nSetup content " + + "(image.png)"; GHContent mockContent = createMockProductFolderWithProductJson(); getReadmeInputStream(readmeContentWithImage, mockContent); InputStream inputStream = getMockInputStream(); Mockito.when(axonivyProductRepoServiceImpl.extractedContentStream(any())).thenReturn(inputStream); - Mockito.when(axonivyProductRepoServiceImpl.extractProductJsonContent(any(),anyString())).thenReturn(getMockProductJsonContent()); + Mockito.when(axonivyProductRepoServiceImpl.extractProductJsonContent(any(), anyString())).thenReturn( + getMockProductJsonContent()); ProductJsonContent expectedProductJsonContent = new ProductJsonContent(); expectedProductJsonContent.setProductId("docuware-connector"); @@ -449,9 +445,9 @@ void test_insertProductJsonContent() throws IOException { RELEASE_TAG); verify(productJsonContentRepository).save(argumentCaptor.capture()); - assertEquals("docuware-connector-name" , argumentCaptor.getValue().getName()); - assertEquals("10.0.0",argumentCaptor.getValue().getVersion()); - assertEquals("docuware-connector" , argumentCaptor.getValue().getProductId()); - assertEquals(getMockProductJsonContent().replace("${version}", "10.0.0"),argumentCaptor.getValue().getContent()); + assertEquals("docuware-connector-name", argumentCaptor.getValue().getName()); + assertEquals("10.0.0", argumentCaptor.getValue().getVersion()); + assertEquals("docuware-connector", argumentCaptor.getValue().getProductId()); + assertEquals(getMockProductJsonContent().replace("${version}", "10.0.0"), argumentCaptor.getValue().getContent()); } } 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 5dd3ce8b6..1cfd8f00d 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 @@ -17,9 +17,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.lenient; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) 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 9f95c35fa..78881cd9f 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 @@ -1,16 +1,8 @@ package com.axonivy.market.service.impl; -import static com.axonivy.market.constants.CommonConstants.SLASH; -import static com.axonivy.market.constants.MetaConstants.META_FILE; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.io.InputStream; - +import com.axonivy.market.entity.Image; +import com.axonivy.market.entity.Product; +import com.axonivy.market.repository.ImageRepository; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.kohsuke.github.GHContent; @@ -20,21 +12,24 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import com.axonivy.market.entity.Image; -import com.axonivy.market.entity.Product; -import com.axonivy.market.repository.ImageRepository; +import java.io.IOException; +import java.io.InputStream; + +import static com.axonivy.market.constants.CommonConstants.SLASH; +import static com.axonivy.market.constants.MetaConstants.META_FILE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class ImageServiceImplTest { + @Captor + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Image.class); @InjectMocks private ImageServiceImpl imageService; - @Mock private ImageRepository imageRepository; - @Captor - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Image.class); - @Test void testMappingImageFromGHContent() throws IOException { GHContent content = mock(GHContent.class); @@ -44,7 +39,7 @@ void testMappingImageFromGHContent() throws IOException { InputStream inputStream = this.getClass().getResourceAsStream(SLASH.concat(META_FILE)); when(content.read()).thenReturn(inputStream); - imageService.mappingImageFromGHContent(mockProduct(), content , true); + imageService.mappingImageFromGHContent(mockProduct(), content, true); Image expectedImage = new Image(); expectedImage.setProductId("google-maps-connector"); @@ -53,14 +48,14 @@ void testMappingImageFromGHContent() throws IOException { verify(imageRepository).save(argumentCaptor.capture()); - assertEquals(argumentCaptor.getValue().getProductId(),expectedImage.getProductId()); - assertEquals(argumentCaptor.getValue().getSha(),expectedImage.getSha()); - assertEquals(argumentCaptor.getValue().getImageUrl(),expectedImage.getImageUrl()); + assertEquals(argumentCaptor.getValue().getProductId(), expectedImage.getProductId()); + assertEquals(argumentCaptor.getValue().getSha(), expectedImage.getSha()); + assertEquals(argumentCaptor.getValue().getImageUrl(), expectedImage.getImageUrl()); } @Test void testMappingImageFromGHContent_noGhContent() { - var result = imageService.mappingImageFromGHContent(mockProduct(), null , true); + var result = imageService.mappingImageFromGHContent(mockProduct(), null, true); assertNull(result); } diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/JwtServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/JwtServiceImplTest.java index 923aae407..922af253e 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/impl/JwtServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/JwtServiceImplTest.java @@ -10,10 +10,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; @ExtendWith(MockitoExtension.class) class JwtServiceImplTest { diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductDesignerInstallationServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductDesignerInstallationServiceImplTest.java index 6e8998fef..b95be2443 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductDesignerInstallationServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductDesignerInstallationServiceImplTest.java @@ -34,8 +34,9 @@ public void setup() { @Test void testFindByProductId() { when(productDesignerInstallationRepository.findByProductId(any(), any())).thenReturn(this.mockResultReturn); - List results = productDesignerInstallationServiceImpl.findByProductId(BaseSetup.SAMPLE_PRODUCT_ID); - assertEquals(2,results.size()); + List results = productDesignerInstallationServiceImpl.findByProductId( + BaseSetup.SAMPLE_PRODUCT_ID); + assertEquals(2, results.size()); assertEquals("10.0.22", results.get(0).getDesignerVersion()); assertEquals(50, results.get(0).getNumberOfDownloads()); assertEquals("11.4.0", results.get(1).getDesignerVersion()); 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 2f466ab8b..54b29705e 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 @@ -1,40 +1,28 @@ package com.axonivy.market.service.impl; -import static com.axonivy.market.constants.ProductJsonConstants.LOGO_FILE; -import static com.axonivy.market.constants.CommonConstants.SLASH; -import static com.axonivy.market.constants.MetaConstants.META_FILE; -import static com.axonivy.market.enums.DocumentField.SHORT_DESCRIPTIONS; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.anyLong; -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; - -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; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; - +import com.axonivy.market.BaseSetup; +import com.axonivy.market.constants.GitHubConstants; import com.axonivy.market.criteria.ProductSearchCriteria; +import com.axonivy.market.entity.GitHubRepoMeta; +import com.axonivy.market.entity.Product; +import com.axonivy.market.entity.ProductCustomSort; +import com.axonivy.market.entity.ProductModuleContent; +import com.axonivy.market.enums.ErrorCode; +import com.axonivy.market.enums.FileStatus; +import com.axonivy.market.enums.FileType; +import com.axonivy.market.enums.Language; +import com.axonivy.market.enums.SortOption; +import com.axonivy.market.enums.TypeOption; +import com.axonivy.market.exceptions.model.InvalidParamException; +import com.axonivy.market.github.model.GitHubFile; +import com.axonivy.market.github.service.GHAxonIvyMarketRepoService; +import com.axonivy.market.github.service.GHAxonIvyProductRepoService; +import com.axonivy.market.github.service.GitHubService; +import com.axonivy.market.model.ProductCustomSortRequest; +import com.axonivy.market.repository.GitHubRepoMetaRepository; +import com.axonivy.market.repository.ProductCustomSortRepository; +import com.axonivy.market.repository.ProductModuleContentRepository; +import com.axonivy.market.repository.ProductRepository; import com.axonivy.market.service.ImageService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -59,89 +47,69 @@ import org.springframework.data.mongodb.core.query.Update; import org.springframework.test.util.ReflectionTestUtils; -import com.axonivy.market.BaseSetup; -import com.axonivy.market.constants.GitHubConstants; -import com.axonivy.market.entity.GitHubRepoMeta; -import com.axonivy.market.entity.Product; -import com.axonivy.market.entity.ProductCustomSort; -import com.axonivy.market.entity.ProductModuleContent; -import com.axonivy.market.enums.ErrorCode; -import com.axonivy.market.enums.FileStatus; -import com.axonivy.market.enums.FileType; -import com.axonivy.market.enums.Language; -import com.axonivy.market.enums.SortOption; -import com.axonivy.market.enums.TypeOption; -import com.axonivy.market.exceptions.model.InvalidParamException; -import com.axonivy.market.github.model.GitHubFile; -import com.axonivy.market.github.service.GHAxonIvyMarketRepoService; -import com.axonivy.market.github.service.GHAxonIvyProductRepoService; -import com.axonivy.market.github.service.GitHubService; -import com.axonivy.market.model.ProductCustomSortRequest; -import com.axonivy.market.repository.GitHubRepoMetaRepository; -import com.axonivy.market.repository.ProductCustomSortRepository; -import com.axonivy.market.repository.ProductModuleContentRepository; -import com.axonivy.market.repository.ProductRepository; +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; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import static com.axonivy.market.constants.CommonConstants.SLASH; +import static com.axonivy.market.constants.MetaConstants.META_FILE; +import static com.axonivy.market.constants.ProductJsonConstants.LOGO_FILE; +import static com.axonivy.market.enums.DocumentField.SHORT_DESCRIPTIONS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class ProductServiceImplTest extends BaseSetup { + public static final String RELEASE_TAG = "v10.0.2"; private static final long LAST_CHANGE_TIME = 1718096290000L; private static final Pageable PAGEABLE = PageRequest.of(0, 20, Sort.by(SortOption.ALPHABETICALLY.getOption()).descending()); 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"; - + @Captor + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Product.class); + @Captor + ArgumentCaptor> argumentCaptorProductModuleContents; + @Captor + ArgumentCaptor argumentCaptorProductModuleContent; + @Captor + ArgumentCaptor> productListArgumentCaptor; + @Captor + ArgumentCaptor productSearchCriteriaArgumentCaptor; private String keyword; private String language; private Page mockResultReturn; - @Mock private MongoTemplate mongoTemplate; - @Mock private GHRepository ghRepository; - @Mock private ProductRepository productRepository; - @Mock private ProductModuleContentRepository productModuleContentRepository; - @Mock private GHAxonIvyMarketRepoService marketRepoService; - @Mock private GitHubRepoMetaRepository repoMetaRepository; - @Mock private GitHubService gitHubService; - @Mock private ProductCustomSortRepository productCustomSortRepository; - - @Captor - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Product.class); - - @Captor - ArgumentCaptor> argumentCaptorProductModuleContents; - - @Captor - ArgumentCaptor argumentCaptorProductModuleContent; - @Mock private GHAxonIvyProductRepoService ghAxonIvyProductRepoService; - @Mock private ImageService imageService; - - @Captor - ArgumentCaptor> productListArgumentCaptor; - - @Captor - ArgumentCaptor productSearchCriteriaArgumentCaptor; - @InjectMocks private ProductServiceImpl productService; @@ -355,7 +323,7 @@ void testSyncProductsFirstTime() throws IOException { assertEquals(1, argumentCaptorProductModuleContents.getValue().size()); assertThat(argumentCaptorProductModuleContents.getValue().get(0).getId()) - .isEqualTo(mockReadmeProductContent().getId()); + .isEqualTo(mockReadmeProductContent().getId()); } @Test @@ -374,7 +342,8 @@ void testSyncProductsFirstTimeWithOutSourceUrl() throws IOException { List mockMetaJsonAndLogoList = new ArrayList<>(List.of(mockContent, mockContentLogo)); mockGHContentMap.put(SAMPLE_PRODUCT_ID, mockMetaJsonAndLogoList); when(marketRepoService.fetchAllMarketItems()).thenReturn(mockGHContentMap); - Mockito.when(imageService.mappingImageFromGHContent(any(),any(),anyBoolean())).thenReturn(GHAxonIvyProductRepoServiceImplTest.mockImage()); + Mockito.when(imageService.mappingImageFromGHContent(any(), any(), anyBoolean())).thenReturn( + GHAxonIvyProductRepoServiceImplTest.mockImage()); // Executes productService.syncLatestDataFromMarketRepo(); verify(productModuleContentRepository).save(argumentCaptorProductModuleContent.capture()); diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/SchedulingTasksTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/SchedulingTasksTest.java index 0e2b26bcd..94e022e1b 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/impl/SchedulingTasksTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/SchedulingTasksTest.java @@ -1,17 +1,16 @@ package com.axonivy.market.service.impl; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.verify; - +import com.axonivy.market.schedulingtask.ScheduledTasks; import org.awaitility.Awaitility; import org.awaitility.Durations; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.SpyBean; -import com.axonivy.market.schedulingtask.ScheduledTasks; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.verify; -@SpringBootTest(properties = { "MONGODB_USERNAME=user", "MONGODB_PASSWORD=password", "MONGODB_HOST=mongoHost", +@SpringBootTest(properties = {"MONGODB_USERNAME=user", "MONGODB_PASSWORD=password", "MONGODB_HOST=mongoHost", "MONGODB_DATABASE=product", "MARKET_GITHUB_OAUTH_APP_CLIENT_ID=clientId", "MARKET_GITHUB_OAUTH_APP_CLIENT_SECRET=clientSecret", "MARKET_JWT_SECRET_KEY=jwtSecret", "MARKET_CORS_ALLOWED_ORIGIN=*", "MARKET_GITHUB_MARKET_BRANCH=master", "MARKET_MONGO_LOG_LEVEL=DEBUG"}) diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/UserServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/UserServiceImplTest.java index 389c87f3c..c6b907d9e 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/impl/UserServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/UserServiceImplTest.java @@ -15,13 +15,8 @@ import java.util.List; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class UserServiceImplTest { 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 4efe92693..49b7bf7f1 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 @@ -118,7 +118,8 @@ void testGetMavenArtifactsFromProductJsonByVersion() throws IOException { Assertions.assertEquals(0, versionService.getMavenArtifactsFromProductJsonByVersion("10.0.20", "adobe-acrobat-connector").size()); - String jsonContent = "{ \"installers\": [{ \"id\": \"maven-import\", \"data\": { \"repositories\": [{ \"url\": \"http://repo.url\" }], \"projects\": [], \"dependencies\": [] } }] }"; + String jsonContent = "{ \"installers\": [{ \"id\": \"maven-import\", \"data\": { \"repositories\": [{ \"url\": " + + "\"http://repo.url\" }], \"projects\": [], \"dependencies\": [] } }] }"; ProductJsonContent productJson = new ProductJsonContent(); productJson.setContent(jsonContent); when(productJsonContentRepository.findByProductIdAndVersion("adobe-acrobat-connector", "10.0.20")).thenReturn( diff --git a/marketplace-service/src/test/java/com/axonivy/market/util/ImageUtilsTest.java b/marketplace-service/src/test/java/com/axonivy/market/util/ImageUtilsTest.java index 07e7a218e..f5c2d897c 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/util/ImageUtilsTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/util/ImageUtilsTest.java @@ -1,14 +1,13 @@ package com.axonivy.market.util; -import java.util.HashMap; -import java.util.Map; - +import com.axonivy.market.entity.ProductModuleContent; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; -import com.axonivy.market.entity.ProductModuleContent; +import java.util.HashMap; +import java.util.Map; @ExtendWith(MockitoExtension.class) class ImageUtilsTest { @@ -21,7 +20,7 @@ void testMappingImageForProductModuleContent() { Assertions.assertEquals(expectedValue, result.getSetup().get("de")); } - private ProductModuleContent mockProductModuleContent(){ + private ProductModuleContent mockProductModuleContent() { ProductModuleContent productModuleContent = new ProductModuleContent(); productModuleContent.setDescription(mockDescriptionForProductModuleContent()); productModuleContent.setDemo(null); @@ -30,7 +29,7 @@ private ProductModuleContent mockProductModuleContent(){ return productModuleContent; } - private Map mockDescriptionForProductModuleContent(){ + private Map mockDescriptionForProductModuleContent() { Map mutableMap = new HashMap<>(); mutableMap.put("en", "Login or create a new account.[demo-process](imageId-66e2b13c68f2f95b2f95548c)"); mutableMap.put("de", "Login or create a new account.[demo-process](imageId-66e2b13c68f2f95b2f95548c)"); 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 a1686746f..265ec8ba0 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 @@ -15,168 +15,167 @@ import java.util.List; @ExtendWith(MockitoExtension.class) - class VersionUtilsTest { - @InjectMocks - private VersionUtils versionUtils; - - @Test - void testIsSnapshotVersion() { - String targetVersion = "10.0.21-SNAPSHOT"; - Assertions.assertTrue(VersionUtils.isSnapshotVersion(targetVersion)); - - targetVersion = "10.0.21-m1234"; - Assertions.assertFalse(VersionUtils.isSnapshotVersion(targetVersion)); - - targetVersion = "10.0.21"; - Assertions.assertFalse(VersionUtils.isSnapshotVersion(targetVersion)); - } - - @Test - void testIsSprintVersion() { - String targetVersion = "10.0.21-m1234"; - Assertions.assertTrue(VersionUtils.isSprintVersion(targetVersion)); - - targetVersion = "10.0.21-SNAPSHOT"; - Assertions.assertFalse(VersionUtils.isSprintVersion(targetVersion)); - - targetVersion = "10.0.21"; - Assertions.assertFalse(VersionUtils.isSprintVersion(targetVersion)); - } - - @Test - void testIsReleasedVersion() { - String targetVersion = "10.0.21"; - Assertions.assertTrue(VersionUtils.isReleasedVersion(targetVersion)); - - targetVersion = "10.0.21-SNAPSHOT"; - Assertions.assertFalse(VersionUtils.isReleasedVersion(targetVersion)); - - targetVersion = "10.0.21-m1231"; - Assertions.assertFalse(VersionUtils.isReleasedVersion(targetVersion)); - } - - @Test - void testIsMatchWithDesignerVersion() { - String designerVersion = "10.0.21"; - String targetVersion = "10.0.21.2"; - Assertions.assertTrue(VersionUtils.isMatchWithDesignerVersion(targetVersion, designerVersion)); - - targetVersion = "10.0.21-SNAPSHOT"; - Assertions.assertFalse(VersionUtils.isMatchWithDesignerVersion(targetVersion, designerVersion)); - - targetVersion = "10.0.19"; - Assertions.assertFalse(VersionUtils.isMatchWithDesignerVersion(targetVersion, designerVersion)); - } - - @Test - void testConvertVersionToTag() { - - String rawVersion = StringUtils.EMPTY; - Assertions.assertEquals(rawVersion, VersionUtils.convertVersionToTag(StringUtils.EMPTY, rawVersion)); - - rawVersion = "11.0.0"; - String tag = "11.0.0"; - Assertions.assertEquals(tag, VersionUtils.convertVersionToTag(NonStandardProduct.PORTAL.getId(), rawVersion)); - - tag = "v11.0.0"; - Assertions.assertEquals(tag, VersionUtils.convertVersionToTag(NonStandardProduct.GRAPHQL_DEMO.getId(), rawVersion)); - } - - @Test - void testGetVersionsToDisplay() { - ArrayList versionFromArtifact = new ArrayList<>(); - versionFromArtifact.add("10.0.6"); - versionFromArtifact.add("10.0.5"); - versionFromArtifact.add("10.0.4"); - versionFromArtifact.add("10.0.3-SNAPSHOT"); - Assertions.assertEquals(versionFromArtifact, VersionUtils.getVersionsToDisplay(versionFromArtifact, true, null)); - Assertions.assertEquals(List.of("10.0.5"), VersionUtils.getVersionsToDisplay(versionFromArtifact, null, "10.0.5")); - versionFromArtifact.remove("10.0.3-SNAPSHOT"); - Assertions.assertEquals(versionFromArtifact, VersionUtils.getVersionsToDisplay(versionFromArtifact, null, null)); - } - - - @Test - void testIsReleasedVersionOrUnReleaseDevVersion() { - String releasedVersion = "10.0.20"; - String snapshotVersion = "10.0.20-SNAPSHOT"; - String sprintVersion = "10.0.20-m1234"; - String minorSprintVersion = "10.0.20.1-m1234"; - String unreleasedSprintVersion = "10.0.21-m1235"; - List versions = List.of(releasedVersion, snapshotVersion, sprintVersion, unreleasedSprintVersion); - Assertions.assertTrue(VersionUtils.isOfficialVersionOrUnReleasedDevVersion(versions, releasedVersion)); - Assertions.assertFalse(VersionUtils.isOfficialVersionOrUnReleasedDevVersion(versions, sprintVersion)); - Assertions.assertFalse(VersionUtils.isOfficialVersionOrUnReleasedDevVersion(versions, snapshotVersion)); - Assertions.assertFalse(VersionUtils.isOfficialVersionOrUnReleasedDevVersion(versions, minorSprintVersion)); - Assertions.assertTrue(VersionUtils.isOfficialVersionOrUnReleasedDevVersion(versions, unreleasedSprintVersion)); - } - - @Test - void testGetBugfixVersion() { - String releasedVersion = "10.0.20"; - String shortReleasedVersion = "10.0"; - String snapshotVersion = "10.0.20-SNAPSHOT"; - String sprintVersion = "10.0.20-m1234"; - String minorSprintVersion = "10.0.20.1-m1234"; - Assertions.assertEquals(releasedVersion, VersionUtils.getBugfixVersion(releasedVersion)); - Assertions.assertEquals(releasedVersion, VersionUtils.getBugfixVersion(snapshotVersion)); - Assertions.assertEquals(releasedVersion, VersionUtils.getBugfixVersion(sprintVersion)); - Assertions.assertEquals(releasedVersion, VersionUtils.getBugfixVersion(minorSprintVersion)); - Assertions.assertEquals(shortReleasedVersion, VersionUtils.getBugfixVersion(shortReleasedVersion)); - - } - - @Test - void testGetBestMatchVersion() { - List releasedVersions = List.of("10.0.21-SNAPSHOT", "10.0.21", "10.0.19", "10.0.17"); - Assertions.assertEquals("10.0.19", VersionUtils.getBestMatchVersion(releasedVersions, "10.0.19")); - Assertions.assertEquals("10.0.21", VersionUtils.getBestMatchVersion(releasedVersions, "10.0.22")); - Assertions.assertEquals("10.0.17", VersionUtils.getBestMatchVersion(releasedVersions, "10.0.18")); - Assertions.assertEquals("10.0.21", VersionUtils.getBestMatchVersion(releasedVersions, "10.0.16")); - } - - @Test - void testConvertTagToVersion() { - Assertions.assertEquals("10.0.19", VersionUtils.convertTagToVersion("10.0.19")); - Assertions.assertEquals("10.0.19", VersionUtils.convertTagToVersion("v10.0.19")); - Assertions.assertEquals("", VersionUtils.convertTagToVersion("")); - } - - @Test - void testConvertTagsToVersions() { - List results = VersionUtils.convertTagsToVersions(List.of("10.0.1", "v10.0.2")); - Assertions.assertEquals(2, results.size()); - Assertions.assertEquals("10.0.1", results.get(0)); - Assertions.assertEquals("10.0.2", 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); // Assuming the replacement of non-numeric characters works correctly - } + @InjectMocks + private VersionUtils versionUtils; + + @Test + void testIsSnapshotVersion() { + String targetVersion = "10.0.21-SNAPSHOT"; + Assertions.assertTrue(VersionUtils.isSnapshotVersion(targetVersion)); + + targetVersion = "10.0.21-m1234"; + Assertions.assertFalse(VersionUtils.isSnapshotVersion(targetVersion)); + + targetVersion = "10.0.21"; + Assertions.assertFalse(VersionUtils.isSnapshotVersion(targetVersion)); + } + + @Test + void testIsSprintVersion() { + String targetVersion = "10.0.21-m1234"; + Assertions.assertTrue(VersionUtils.isSprintVersion(targetVersion)); + + targetVersion = "10.0.21-SNAPSHOT"; + Assertions.assertFalse(VersionUtils.isSprintVersion(targetVersion)); + + targetVersion = "10.0.21"; + Assertions.assertFalse(VersionUtils.isSprintVersion(targetVersion)); + } + + @Test + void testIsReleasedVersion() { + String targetVersion = "10.0.21"; + Assertions.assertTrue(VersionUtils.isReleasedVersion(targetVersion)); + + targetVersion = "10.0.21-SNAPSHOT"; + Assertions.assertFalse(VersionUtils.isReleasedVersion(targetVersion)); + + targetVersion = "10.0.21-m1231"; + Assertions.assertFalse(VersionUtils.isReleasedVersion(targetVersion)); + } + + @Test + void testIsMatchWithDesignerVersion() { + String designerVersion = "10.0.21"; + String targetVersion = "10.0.21.2"; + Assertions.assertTrue(VersionUtils.isMatchWithDesignerVersion(targetVersion, designerVersion)); + + targetVersion = "10.0.21-SNAPSHOT"; + Assertions.assertFalse(VersionUtils.isMatchWithDesignerVersion(targetVersion, designerVersion)); + + targetVersion = "10.0.19"; + Assertions.assertFalse(VersionUtils.isMatchWithDesignerVersion(targetVersion, designerVersion)); + } + + @Test + void testConvertVersionToTag() { + + String rawVersion = StringUtils.EMPTY; + Assertions.assertEquals(rawVersion, VersionUtils.convertVersionToTag(StringUtils.EMPTY, rawVersion)); + + rawVersion = "11.0.0"; + String tag = "11.0.0"; + Assertions.assertEquals(tag, VersionUtils.convertVersionToTag(NonStandardProduct.PORTAL.getId(), rawVersion)); + + tag = "v11.0.0"; + Assertions.assertEquals(tag, VersionUtils.convertVersionToTag(NonStandardProduct.GRAPHQL_DEMO.getId(), rawVersion)); + } + + @Test + void testGetVersionsToDisplay() { + ArrayList versionFromArtifact = new ArrayList<>(); + versionFromArtifact.add("10.0.6"); + versionFromArtifact.add("10.0.5"); + versionFromArtifact.add("10.0.4"); + versionFromArtifact.add("10.0.3-SNAPSHOT"); + Assertions.assertEquals(versionFromArtifact, VersionUtils.getVersionsToDisplay(versionFromArtifact, true, null)); + Assertions.assertEquals(List.of("10.0.5"), VersionUtils.getVersionsToDisplay(versionFromArtifact, null, "10.0.5")); + versionFromArtifact.remove("10.0.3-SNAPSHOT"); + Assertions.assertEquals(versionFromArtifact, VersionUtils.getVersionsToDisplay(versionFromArtifact, null, null)); + } + + + @Test + void testIsReleasedVersionOrUnReleaseDevVersion() { + String releasedVersion = "10.0.20"; + String snapshotVersion = "10.0.20-SNAPSHOT"; + String sprintVersion = "10.0.20-m1234"; + String minorSprintVersion = "10.0.20.1-m1234"; + String unreleasedSprintVersion = "10.0.21-m1235"; + List versions = List.of(releasedVersion, snapshotVersion, sprintVersion, unreleasedSprintVersion); + Assertions.assertTrue(VersionUtils.isOfficialVersionOrUnReleasedDevVersion(versions, releasedVersion)); + Assertions.assertFalse(VersionUtils.isOfficialVersionOrUnReleasedDevVersion(versions, sprintVersion)); + Assertions.assertFalse(VersionUtils.isOfficialVersionOrUnReleasedDevVersion(versions, snapshotVersion)); + Assertions.assertFalse(VersionUtils.isOfficialVersionOrUnReleasedDevVersion(versions, minorSprintVersion)); + Assertions.assertTrue(VersionUtils.isOfficialVersionOrUnReleasedDevVersion(versions, unreleasedSprintVersion)); + } + + @Test + void testGetBugfixVersion() { + String releasedVersion = "10.0.20"; + String shortReleasedVersion = "10.0"; + String snapshotVersion = "10.0.20-SNAPSHOT"; + String sprintVersion = "10.0.20-m1234"; + String minorSprintVersion = "10.0.20.1-m1234"; + Assertions.assertEquals(releasedVersion, VersionUtils.getBugfixVersion(releasedVersion)); + Assertions.assertEquals(releasedVersion, VersionUtils.getBugfixVersion(snapshotVersion)); + Assertions.assertEquals(releasedVersion, VersionUtils.getBugfixVersion(sprintVersion)); + Assertions.assertEquals(releasedVersion, VersionUtils.getBugfixVersion(minorSprintVersion)); + Assertions.assertEquals(shortReleasedVersion, VersionUtils.getBugfixVersion(shortReleasedVersion)); + + } + + @Test + void testGetBestMatchVersion() { + List releasedVersions = List.of("10.0.21-SNAPSHOT", "10.0.21", "10.0.19", "10.0.17"); + Assertions.assertEquals("10.0.19", VersionUtils.getBestMatchVersion(releasedVersions, "10.0.19")); + Assertions.assertEquals("10.0.21", VersionUtils.getBestMatchVersion(releasedVersions, "10.0.22")); + Assertions.assertEquals("10.0.17", VersionUtils.getBestMatchVersion(releasedVersions, "10.0.18")); + Assertions.assertEquals("10.0.21", VersionUtils.getBestMatchVersion(releasedVersions, "10.0.16")); + } + + @Test + void testConvertTagToVersion() { + Assertions.assertEquals("10.0.19", VersionUtils.convertTagToVersion("10.0.19")); + Assertions.assertEquals("10.0.19", VersionUtils.convertTagToVersion("v10.0.19")); + Assertions.assertEquals("", VersionUtils.convertTagToVersion("")); + } + + @Test + void testConvertTagsToVersions() { + List results = VersionUtils.convertTagsToVersions(List.of("10.0.1", "v10.0.2")); + Assertions.assertEquals(2, results.size()); + Assertions.assertEquals("10.0.1", results.get(0)); + Assertions.assertEquals("10.0.2", 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); // Assuming the replacement of non-numeric characters works correctly + } } From ae5270aeb05b0d91f0ece357bf5dcf896205a9f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=E1=BA=A1m=20Duy=20Linh?= <138570547+linhpd-axonivy@users.noreply.github.com> Date: Mon, 23 Sep 2024 08:57:53 +0700 Subject: [PATCH 08/20] Feature/format3 (#169) --- marketplace-service/.editorconfig | 2 +- .../market/controller/FeedbackController.java | 52 ++--- .../market/controller/ImageController.java | 12 +- .../market/controller/OAuth2Controller.java | 8 +- .../market/controller/ProductController.java | 44 ++-- ...ProductDesignerInstallationController.java | 8 +- .../controller/ProductDetailsController.java | 40 ++-- .../java/com/axonivy/market/entity/Image.java | 8 +- .../market/entity/MavenArtifactModel.java | 6 +- .../market/entity/ProductModuleContent.java | 14 +- .../market/enums/NonStandardProduct.java | 9 +- .../market/model/ProductDetailModel.java | 8 +- .../axonivy/market/model/ProductModel.java | 16 +- .../market/repository/ProductRepository.java | 2 +- ...uctDesignerInstallationControllerTest.java | 2 +- .../ProductDetailsControllerTest.java | 21 +- .../GHAxonIvyProductRepoServiceImplTest.java | 205 +++++++++--------- 17 files changed, 234 insertions(+), 223 deletions(-) diff --git a/marketplace-service/.editorconfig b/marketplace-service/.editorconfig index bded54b66..ffd49d3c5 100644 --- a/marketplace-service/.editorconfig +++ b/marketplace-service/.editorconfig @@ -44,7 +44,7 @@ ij_java_align_multiline_throws_list = false ij_java_align_subsequent_simple_methods = false ij_java_align_throws_keyword = false ij_java_align_types_in_multi_catch = true -ij_java_annotation_parameter_wrap = off +ij_java_annotation_parameter_wrap = normal ij_java_array_initializer_new_line_after_left_brace = false ij_java_array_initializer_right_brace_on_new_line = false ij_java_array_initializer_wrap = off diff --git a/marketplace-service/src/main/java/com/axonivy/market/controller/FeedbackController.java b/marketplace-service/src/main/java/com/axonivy/market/controller/FeedbackController.java index baa642b83..1b60e3f0c 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/controller/FeedbackController.java +++ b/marketplace-service/src/main/java/com/axonivy/market/controller/FeedbackController.java @@ -63,18 +63,19 @@ public FeedbackController(FeedbackService feedbackService, JwtService jwtService } @GetMapping(PRODUCT_BY_ID) - @Operation(summary = "Find feedbacks by product id with lazy loading", description = "Get all user feedback by " + - "product id (from meta.json) with lazy loading", parameters = { + @Operation(summary = "Find feedbacks by product id with lazy loading", + description = "Get all user feedback by product id (from meta.json) with lazy loading", parameters = { @Parameter(name = "page", description = "Page number to retrieve", in = ParameterIn.QUERY, example = "0", required = true), @Parameter(name = "size", description = "Number of items per page", in = ParameterIn.QUERY, example = "20", required = true), - @Parameter(name = "sort", description = "Sorting criteria in the format: Sorting criteria" + - "(popularity|alphabetically|recent), Sorting order(asc|desc)", in = ParameterIn.QUERY, example = - "[\"popularity\",\"asc\"]", required = true)}) + @Parameter(name = "sort", + description = "Sorting criteria in the format: Sorting criteria(popularity|alphabetically|recent), Sorting " + + "order(asc|desc)", + in = ParameterIn.QUERY, example = "[\"popularity\",\"asc\"]", required = true)}) public ResponseEntity> findFeedbacks( - @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "portal", in = - ParameterIn.PATH) String productId, + @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "portal", + in = ParameterIn.PATH) String productId, @ParameterObject Pageable pageable) { Page results = feedbackService.findFeedbacks(productId, pageable); if (results.isEmpty()) { @@ -86,32 +87,33 @@ public ResponseEntity> findFeedbacks( } @GetMapping(BY_ID) - @Operation(summary = "Find all feedbacks by product id", description = "Get all feedbacks by product id(from meta" + - ".json) which is used in mobile viewport.") + @Operation(summary = "Find all feedbacks by product id", + description = "Get all feedbacks by product id(from meta.json) which is used in mobile viewport.") public ResponseEntity findFeedback( - @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "portal", in = - ParameterIn.PATH) String id) { + @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "portal", + in = ParameterIn.PATH) String id) { Feedback feedback = feedbackService.findFeedback(id); return ResponseEntity.ok(feedbackModelAssembler.toModel(feedback)); } @GetMapping() - @Operation(summary = "Find all feedbacks by user id and product id", description = "Get current user feedback on " + - "target product.") + @Operation(summary = "Find all feedbacks by user id and product id", + description = "Get current user feedback on target product.") public ResponseEntity findFeedbackByUserIdAndProductId( - @RequestParam(USER_ID) @Parameter(description = "Id of current user from DB", example = "1234", in = - ParameterIn.QUERY) String userId, - @RequestParam("productId") @Parameter(description = "Product id (from meta.json)", example = "portal", in = - ParameterIn.QUERY) String productId) { + @RequestParam(USER_ID) @Parameter(description = "Id of current user from DB", example = "1234", + in = ParameterIn.QUERY) String userId, + @RequestParam("productId") @Parameter(description = "Product id (from meta.json)", example = "portal", + in = ParameterIn.QUERY) String productId) { Feedback feedback = feedbackService.findFeedbackByUserIdAndProductId(userId, productId); return ResponseEntity.ok(feedbackModelAssembler.toModel(feedback)); } @PostMapping - @Operation(summary = "Create user feedback", description = "Save user feedback of product with their token from " + - "Github account.") - @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Example request body for feedback", content = - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = FeedbackModelRequest.class))) + @Operation(summary = "Create user feedback", + description = "Save user feedback of product with their token from Github account.") + @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Example request body for feedback", + content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = FeedbackModelRequest.class))) @ApiResponses(value = {@ApiResponse(responseCode = "201", description = "Successfully created user feedback"), @ApiResponse(responseCode = "401", description = "Unauthorized request")}) public ResponseEntity createFeedback(@RequestBody @Valid FeedbackModelRequest feedbackRequest, @@ -136,12 +138,12 @@ public ResponseEntity createFeedback(@RequestBody @Valid FeedbackModelRequ return ResponseEntity.created(location).build(); } - @Operation(summary = "Find rating information of product by its id.", description = "Get overall rating of product " + - "by its id.") + @Operation(summary = "Find rating information of product by its id.", + description = "Get overall rating of product by its id.") @GetMapping(PRODUCT_RATING_BY_ID) public ResponseEntity> getProductRating( - @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "portal", in = - ParameterIn.PATH) String productId) { + @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "portal", + in = ParameterIn.PATH) String productId) { return ResponseEntity.ok(feedbackService.getProductRatingById(productId)); } diff --git a/marketplace-service/src/main/java/com/axonivy/market/controller/ImageController.java b/marketplace-service/src/main/java/com/axonivy/market/controller/ImageController.java index 73444c5fe..a3df81a39 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/controller/ImageController.java +++ b/marketplace-service/src/main/java/com/axonivy/market/controller/ImageController.java @@ -33,15 +33,15 @@ public ImageController(ImageService imageService) { } @GetMapping(BY_ID) - @Operation(summary = "Get the image content by id", description = "Collect the byte[] of image with contentType in " + - "header is PNG") - @ApiResponse(responseCode = "200", description = "Image found and returned", content = @Content(mediaType = - MediaType.IMAGE_PNG_VALUE, schema = @Schema(implementation = Image.class))) + @Operation(summary = "Get the image content by id", + description = "Collect the byte[] of image with contentType in header is PNG") + @ApiResponse(responseCode = "200", description = "Image found and returned", + content = @Content(mediaType = MediaType.IMAGE_PNG_VALUE, schema = @Schema(implementation = Image.class))) @ApiResponse(responseCode = "404", description = "Image not found") @ApiResponse(responseCode = "204", description = "No content (image empty)") public ResponseEntity findImageById( - @PathVariable(ID) @Parameter(description = "The image id", example = "66e7efc8a24f36158df06fc7", in = - ParameterIn.PATH) String id) { + @PathVariable(ID) @Parameter(description = "The image id", example = "66e7efc8a24f36158df06fc7", + in = ParameterIn.PATH) String id) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.IMAGE_PNG); byte[] imageData = imageService.readImage(id); diff --git a/marketplace-service/src/main/java/com/axonivy/market/controller/OAuth2Controller.java b/marketplace-service/src/main/java/com/axonivy/market/controller/OAuth2Controller.java index ed9b515ac..2f6a4a3ca 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/controller/OAuth2Controller.java +++ b/marketplace-service/src/main/java/com/axonivy/market/controller/OAuth2Controller.java @@ -48,11 +48,11 @@ public OAuth2Controller(GitHubService gitHubService, JwtService jwtService, GitH @PostMapping(GIT_HUB_LOGIN) @Operation(description = "Get rating authentication token") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Successfully login to GitHub provider", content = - @Content(mediaType = "application/json", schema = @Schema(implementation = Map.class))), + @ApiResponse(responseCode = "200", description = "Successfully login to GitHub provider", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = Map.class))), @ApiResponse(responseCode = "400", description = "Bad Request")}) - @io.swagger.v3.oas.annotations.parameters.RequestBody(content = @Content(mediaType = - MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = Oauth2AuthorizationCode.class))) + @io.swagger.v3.oas.annotations.parameters.RequestBody(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = Oauth2AuthorizationCode.class))) public ResponseEntity> gitHubLogin(@RequestBody Oauth2AuthorizationCode oauth2AuthorizationCode) { String accessToken; try { 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 2af26b2eb..f484eeae5 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 @@ -55,33 +55,29 @@ public ProductController(ProductService productService, GitHubService gitHubServ this.pagedResourcesAssembler = pagedResourcesAssembler; } - public static String getBearerToken(String authorizationHeader) { - String token = null; - if (authorizationHeader.startsWith(CommonConstants.BEARER)) { - token = authorizationHeader.substring(CommonConstants.BEARER.length()).trim(); // Remove "Bearer " prefix - } - return token; - } - @GetMapping() - @Operation(summary = "Retrieve a paginated list of all products, optionally filtered by type, keyword, and " + - "language", description = "By default, the system finds products with type 'all'", parameters = { + @Operation(summary = "Retrieve a paginated list of all products, optionally filtered by type, keyword, and language", + description = "By default, the system finds products with type 'all'", parameters = { @Parameter(name = "page", description = "Page number to retrieve", in = ParameterIn.QUERY, example = "0", required = true), @Parameter(name = "size", description = "Number of items per page", in = ParameterIn.QUERY, example = "20", required = true), - @Parameter(name = "sort", description = "Sorting criteria in the format: Sorting criteria" + - "(popularity|alphabetically|recent), Sorting order(asc|desc)", + @Parameter(name = "sort", + description = "Sorting criteria in the format: Sorting criteria(popularity|alphabetically|recent), Sorting " + + "order(asc|desc)", in = ParameterIn.QUERY, example = "[\"popularity\",\"asc\"]", required = true)}) public ResponseEntity> findProducts( - @RequestParam(name = TYPE) @Parameter(description = "Type of product.", in = ParameterIn.QUERY, schema = - @Schema(type = "string", allowableValues = {"all", "connectors", "utilities", "solutions", "demos"})) String type, - @RequestParam(required = false, name = KEYWORD) @Parameter(description = "Keyword that exist in product's name " + - "or short description", example = "connector", in = ParameterIn.QUERY) String keyword, - @RequestParam(name = LANGUAGE) @Parameter(description = "Language of product short description", in = - ParameterIn.QUERY, schema = @Schema(allowableValues = {"en", "de"})) String language, - @RequestParam(name = IS_REST_CLIENT) @Parameter(description = "Option to render the website in the REST Client " + - "Editor of Designer", in = ParameterIn.QUERY) Boolean isRESTClient, + @RequestParam(name = TYPE) @Parameter(description = "Type of product.", in = ParameterIn.QUERY, + schema = @Schema(type = "string", + allowableValues = {"all", "connectors", "utilities", "solutions", "demos"})) String type, + @RequestParam(required = false, name = KEYWORD) @Parameter( + description = "Keyword that exist in product's name or short description", example = "connector", + in = ParameterIn.QUERY) String keyword, + @RequestParam(name = LANGUAGE) @Parameter(description = "Language of product short description", + in = ParameterIn.QUERY, schema = @Schema(allowableValues = {"en", "de"})) String language, + @RequestParam(name = IS_REST_CLIENT) @Parameter( + description = "Option to render the website in the REST Client Editor of Designer", + in = ParameterIn.QUERY) Boolean isRESTClient, @ParameterObject Pageable pageable) { Page results = productService.findProducts(type, keyword, language, isRESTClient, pageable); if (results.isEmpty()) { @@ -136,4 +132,12 @@ private ResponseEntity> generateEmptyPagedModel() { ProductModel.class); return new ResponseEntity<>(emptyPagedModel, HttpStatus.OK); } + + public static String getBearerToken(String authorizationHeader) { + String token = null; + if (authorizationHeader.startsWith(CommonConstants.BEARER)) { + token = authorizationHeader.substring(CommonConstants.BEARER.length()).trim(); // Remove "Bearer " prefix + } + return token; + } } diff --git a/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDesignerInstallationController.java b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDesignerInstallationController.java index 24118bfcc..d2d2f00ed 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDesignerInstallationController.java +++ b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDesignerInstallationController.java @@ -21,8 +21,8 @@ @RestController @RequestMapping(PRODUCT_DESIGNER_INSTALLATION) -@Tag(name = "Product Designer Installation Controllers", description = "API collection to get designer installation " + - "count.") +@Tag(name = "Product Designer Installation Controllers", + description = "API collection to get designer installation count.") public class ProductDesignerInstallationController { private final ProductDesignerInstallationService productDesignerInstallationService; @@ -31,8 +31,8 @@ public ProductDesignerInstallationController(ProductDesignerInstallationService } @GetMapping(DESIGNER_INSTALLATION_BY_ID) - @Operation(summary = "Get designer installation count by product id.", description = "get designer installation " + - "count by product id") + @Operation(summary = "Get designer installation count by product id.", + description = "get designer installation count by product id") public ResponseEntity> getProductDesignerInstallationByProductId( @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "adobe-acrobat-connector", in = ParameterIn.PATH) String productId) { 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 88220c64a..e68f6ff0b 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 @@ -41,37 +41,38 @@ public ProductDetailsController(VersionService versionService, ProductService pr } @GetMapping(BY_ID_AND_VERSION) - @Operation(summary = "Find product detail by product id and release version.", description = "get product detail by" + - " it product id and release version") + @Operation(summary = "Find product detail by product id and release version.", + description = "get product detail by it product id and release version") public ResponseEntity findProductDetailsByVersion( @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", in = ParameterIn.PATH) String id, - @PathVariable(VERSION) @Parameter(description = "Release version (from maven metadata.xml)", example = "10.0" + - ".20", in = ParameterIn.PATH) String version) { + @PathVariable(VERSION) @Parameter(description = "Release version (from maven metadata.xml)", example = "10.0.20", + in = ParameterIn.PATH) String version) { var productDetail = productService.fetchProductDetailByIdAndVersion(id, version); return new ResponseEntity<>(detailModelAssembler.toModel(productDetail, version, BY_ID_AND_VERSION), HttpStatus.OK); } @GetMapping(BEST_MATCH_BY_ID_AND_VERSION) - @Operation(summary = "Find best match product detail by product id and version.", description = "get product detail" + - " by it product id and version") + @Operation(summary = "Find best match product detail by product id and version.", + description = "get product detail by it product id and version") public ResponseEntity findBestMatchProductDetailsByVersion( @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", in = ParameterIn.PATH) String id, - @PathVariable(VERSION) @Parameter(description = "Version", example = "10.0.20", in = ParameterIn.PATH) String version) { + @PathVariable(VERSION) @Parameter(description = "Version", example = "10.0.20", + in = ParameterIn.PATH) String version) { var productDetail = productService.fetchBestMatchProductDetail(id, version); return new ResponseEntity<>(detailModelAssembler.toModel(productDetail, version, BEST_MATCH_BY_ID_AND_VERSION), HttpStatus.OK); } @PutMapping(INSTALLATION_COUNT_BY_ID) - @Operation(summary = "Update installation count of product", description = "By default, increase installation count" + - " when click download product files by users") + @Operation(summary = "Update installation count of product", + description = "By default, increase installation count when click download product files by users") public ResponseEntity syncInstallationCount( @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", in = ParameterIn.PATH) String productId, - @RequestParam(name = DESIGNER_VERSION, required = false) @Parameter(in = ParameterIn.QUERY, example = "v10.0" + - ".20") String designerVersion) { + @RequestParam(name = DESIGNER_VERSION, required = false) @Parameter(in = ParameterIn.QUERY, + example = "v10.0.20") String designerVersion) { int result = productService.updateInstallationCountForProduct(productId, designerVersion); return new ResponseEntity<>(result, HttpStatus.OK); } @@ -89,18 +90,19 @@ public ResponseEntity findProductDetails( public ResponseEntity> findProductVersionsById( @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "adobe-acrobat-connector", in = ParameterIn.PATH) String id, - @RequestParam(SHOW_DEV_VERSION) @Parameter(description = "Option to get Dev Version (Snapshot/ sprint release)" - , in = ParameterIn.QUERY) boolean isShowDevVersion, - @RequestParam(name = DESIGNER_VERSION, required = false) @Parameter(in = ParameterIn.QUERY, example = "v10.0" + - ".20") String designerVersion) { + @RequestParam(SHOW_DEV_VERSION) @Parameter(description = "Option to get Dev Version (Snapshot/ sprint release)", + in = ParameterIn.QUERY) boolean isShowDevVersion, + @RequestParam(name = DESIGNER_VERSION, required = false) @Parameter(in = ParameterIn.QUERY, + example = "v10.0.20") String designerVersion) { List models = versionService.getArtifactsAndVersionToDisplay(id, isShowDevVersion, designerVersion); return new ResponseEntity<>(models, HttpStatus.OK); } @GetMapping(PRODUCT_JSON_CONTENT_BY_PRODUCT_ID_AND_VERSION) - @Operation(summary = "Get product json content for designer to install", description = "When we click install in " + - "designer, this API will send content of product json for installing in Ivy designer") + @Operation(summary = "Get product json content for designer to install", + description = "When we click install in designer, this API will send content of product json for installing in " + + "Ivy designer") public ResponseEntity> findProductJsonContent(@PathVariable(ID) String productId, @PathVariable(VERSION) String version) { Map productJsonContent = versionService.getProductJsonContentByIdAndVersion(productId, version); @@ -108,8 +110,8 @@ public ResponseEntity> findProductJsonContent(@PathVariable( } @GetMapping(VERSIONS_IN_DESIGNER) - @Operation(summary = "Get the list of released version in product", description = "Collect the released versions in" + - " product for ivy designer") + @Operation(summary = "Get the list of released version in product", + description = "Collect the released versions in product for ivy designer") public ResponseEntity> findVersionsForDesigner(@PathVariable(ID) String id) { List versionList = versionService.getVersionsForDesigner(id); return new ResponseEntity<>(versionList, HttpStatus.OK); diff --git a/marketplace-service/src/main/java/com/axonivy/market/entity/Image.java b/marketplace-service/src/main/java/com/axonivy/market/entity/Image.java index 17fa50356..0a2075581 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/entity/Image.java +++ b/marketplace-service/src/main/java/com/axonivy/market/entity/Image.java @@ -23,11 +23,11 @@ public class Image { private String id; @Schema(description = "Product id", example = "jira-connector") private String productId; - @Schema(description = "The download url from github", example = "https://raw.githubusercontent" + - ".comamazon-comprehend/logo.png") + @Schema(description = "The download url from github", + example = "https://raw.githubusercontent.comamazon-comprehend/logo.png") private String imageUrl; - @Schema(description = "The image content as binary type", example = "Binary(Buffer.from" + - "(\"89504e470d0a1a0a0000000d\", \"hex\"), 0)") + @Schema(description = "The image content as binary type", + example = "Binary(Buffer.from(\"89504e470d0a1a0a0000000d\", \"hex\"), 0)") private Binary imageData; @Schema(description = "The SHA from github", example = "93b1e2f1595d3a85e51b01") private String sha; diff --git a/marketplace-service/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java b/marketplace-service/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java index 87b386e7e..55fe24fd8 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java +++ b/marketplace-service/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java @@ -20,9 +20,9 @@ public class MavenArtifactModel implements Serializable { private static final long serialVersionUID = 1L; @Schema(description = "Display name and type of artifact", example = "Adobe Acrobat Sign Connector (.iar)") private String name; - @Schema(description = "Artifact download url", example = "https://maven.axonivy" + - ".com/com/axonivy/connector/adobe/acrobat/sign/adobe-acrobat-sign-connector/10.0" + - ".25/adobe-acrobat-sign-connector-10.0.25.iar") + @Schema(description = "Artifact download url", + example = "https://maven.axonivy.com/com/axonivy/connector/adobe/acrobat/sign/adobe-acrobat-sign-connector/10.0" + + ".25/adobe-acrobat-sign-connector-10.0.25.iar") private String downloadUrl; @Transient private Boolean isProductArtifact; 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 c05b21515..86dfc7197 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 @@ -1,11 +1,7 @@ package com.axonivy.market.entity; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.mongodb.core.mapping.Document; @@ -24,16 +20,16 @@ @Builder @Document(PRODUCT_MODULE_CONTENT) public class ProductModuleContent implements Serializable { - @Serial - private static final long serialVersionUID = 1L; @Id private String id; + @Serial + private static final long serialVersionUID = 1L; @Schema(description = "product Id (from meta.json)", example = "portal") private String productId; @Schema(description = "Target release tag", example = "v10.0.25") private String tag; - @Schema(description = "Product detail description content ", example = "{ \"de\": \"E-Sign-Konnektor\", \"en\": " + - "\"E-sign connector\" }") + @Schema(description = "Product detail description content ", + example = "{ \"de\": \"E-Sign-Konnektor\", \"en\": \"E-sign connector\" }") private Map description; @Schema(description = "Setup tab content", example = "{ \"de\": \"Setup\", \"en\": \"Setup\" ") private Map setup; 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 index 4568b4925..8aec295c5 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/enums/NonStandardProduct.java +++ b/marketplace-service/src/main/java/com/axonivy/market/enums/NonStandardProduct.java @@ -43,6 +43,10 @@ public enum NonStandardProduct { 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 { @@ -50,11 +54,6 @@ public enum NonStandardProduct { Collectors.toMap(NonStandardProduct::getId, Function.identity())); } - private final String id; - private final boolean isVersionTagNumberOnly; - private final String pathToImageFolder; - private final String pathToProductFolder; - public static NonStandardProduct findById(String id) { return NON_STANDARD_PRODUCT_MAP.getOrDefault(id, DEFAULT); } 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 11d2e36a1..5e55e4574 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 @@ -20,11 +20,11 @@ public class ProductDetailModel extends ProductModel { private String newestReleaseVersion; @Schema(description = "Product cost", example = "Free") private String cost; - @Schema(description = "Source repository url", example = "https://github.com/axonivy-market/adobe-acrobat-sign" + - "-connector") + @Schema(description = "Source repository url", + example = "https://github.com/axonivy-market/adobe-acrobat-sign-connector") private String sourceUrl; - @Schema(description = "Status badge url", example = "https://github.com/axonivy-market/adobe-acrobat-sign-connector" + - "/actions/workflows/ci.yml/badge.svg") + @Schema(description = "Status badge url", + example = "https://github.com/axonivy-market/adobe-acrobat-sign-connector/actions/workflows/ci.yml/badge.svg") private String statusBadgeUrl; @Schema(description = "Default language", example = "English") private String language; diff --git a/marketplace-service/src/main/java/com/axonivy/market/model/ProductModel.java b/marketplace-service/src/main/java/com/axonivy/market/model/ProductModel.java index 6e2b5e4cd..a531ac080 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/model/ProductModel.java +++ b/marketplace-service/src/main/java/com/axonivy/market/model/ProductModel.java @@ -22,15 +22,17 @@ public class ProductModel extends RepresentationModel { @Schema(description = "Product id", example = "jira-connector") private String id; - @Schema(description = "Product name by locale", example = "{ \"de\": \"Atlassian Jira\", \"en\": \"Atlassian Jira\"" + - " }") + @Schema(description = "Product name by locale", + example = "{ \"de\": \"Atlassian Jira\", \"en\": \"Atlassian Jira\" }") private Map names; - @Schema(description = "Product's short descriptions by locale", example = "{ \"de\": \"Nutze den Jira Connector von" + - " Atlassian, um Jira-Tickets direkt von der Axon Ivy Plattform aus zu verfolgen.\", \"en\": \"Atlassian's " + - "Jira connector lets you track issues directly from the Axon Ivy platform\" }") + @Schema(description = "Product's short descriptions by locale", + example = "{ \"de\": \"Nutze den Jira Connector von Atlassian, um Jira-Tickets direkt von der Axon Ivy " + + "Plattform aus zu verfolgen.\", \"en\": \"Atlassian's Jira connector lets you track issues directly " + + "from the Axon Ivy platform\" }") private Map shortDescriptions; - @Schema(description = "Product's logo url", example = "https://raw.githubusercontent" + - ".com/axonivy-market/market/feature/MARP-463-Multilingualism-for-Website/market/connector/jira/logo.png") + @Schema(description = "Product's logo url", + example = "https://raw.githubusercontent.com/axonivy-market/market/feature/MARP-463-Multilingualism-for-Website" + + "/market/connector/jira/logo.png") private String logoUrl; @Schema(description = "Type of product", example = "connector") private String type; diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductRepository.java b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductRepository.java index 4fe090d01..16338a751 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductRepository.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductRepository.java @@ -6,7 +6,7 @@ @Repository public interface ProductRepository extends MongoRepository, ProductSearchRepository, - CustomProductRepository { + CustomProductRepository { Product findByLogoUrl(String logoUrl); diff --git a/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDesignerInstallationControllerTest.java b/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDesignerInstallationControllerTest.java index b4ab1cd3f..5b85137d7 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDesignerInstallationControllerTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDesignerInstallationControllerTest.java @@ -31,7 +31,7 @@ void testGetProductDesignerInstallationByProductId() { Mockito.when(productDesignerInstallationService.findByProductId(Mockito.anyString())).thenReturn(models); ResponseEntity> result = productDesignerInstallationController.getProductDesignerInstallationByProductId( - "portal"); + "portal"); Assertions.assertEquals(HttpStatus.OK, result.getStatusCode()); Assertions.assertEquals(1, Objects.requireNonNull(result.getBody()).size()); Assertions.assertEquals(DESIGNER_VERSION, result.getBody().get(0).getDesignerVersion()); 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 9feb56a37..0aa8e05da 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 @@ -36,21 +36,26 @@ @ExtendWith(MockitoExtension.class) class ProductDetailsControllerTest { public static final String TAG = "v10.0.6"; - public static final String DOCKER_CONNECTOR_ID = "docker-connector"; - private static final String PRODUCT_NAME_SAMPLE = "Docker"; - private static final String PRODUCT_NAME_DE_SAMPLE = "Docker DE"; - @Mock - VersionService versionService; - @Mock - ProductDesignerInstallationService productDesignerInstallationService; @Mock private ProductService productService; + @Mock private ImageService imageService; + + @Mock + VersionService versionService; + + @Mock + ProductDesignerInstallationService productDesignerInstallationService; + @Mock private ProductDetailModelAssembler detailModelAssembler; + @InjectMocks private ProductDetailsController productDetailsController; + private static final String PRODUCT_NAME_SAMPLE = "Docker"; + private static final String PRODUCT_NAME_DE_SAMPLE = "Docker DE"; + public static final String DOCKER_CONNECTOR_ID = "docker-connector"; @Test void testProductDetails() { @@ -116,7 +121,7 @@ void testFindProductVersionsById() { List models = List.of(new MavenArtifactVersionModel()); Mockito.when( versionService.getArtifactsAndVersionToDisplay(Mockito.anyString(), Mockito.anyBoolean(), - Mockito.anyString())) + Mockito.anyString())) .thenReturn(models); ResponseEntity> result = productDetailsController.findProductVersionsById("portal", true, "10.0.1"); 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 db5a544f6..5d9a6d8e7 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 @@ -44,11 +44,12 @@ @ExtendWith(MockitoExtension.class) class GHAxonIvyProductRepoServiceImplTest { + private static final String DUMMY_TAG = "v1.0.0"; public static final String RELEASE_TAG = "v10.0.0"; public static final String IMAGE_NAME = "image.png"; public static final String DOCUWARE_CONNECTOR_PRODUCT = "docuware-connector-product"; public static final String IMAGE_DOWNLOAD_URL = "https://raw.githubusercontent.com/image.png"; - private static final String DUMMY_TAG = "v1.0.0"; + @Mock PagedIterable listTags; @@ -79,107 +80,6 @@ class GHAxonIvyProductRepoServiceImplTest { @Spy private GHAxonIvyProductRepoServiceImpl axonivyProductRepoServiceImpl; - 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; - } - - 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()); - } - - private static InputStream getMockInputStream() { - String jsonContent = getMockProductJsonContent(); - return new ByteArrayInputStream(jsonContent.getBytes(StandardCharsets.UTF_8)); - } - - private static String getMockProductJsonContent() { - return """ - { - "$schema": "https://json-schema.axonivy.com/market/10.0.0/product.json", - "installers": [ - { - "id": "maven-import", - "data": { - "projects": [ - { - "groupId": "com.axonivy.utils.bpmnstatistic", - "artifactId": "bpmn-statistic-demo", - "version": "${version}", - "type": "iar" - } - ], - "repositories": [ - { - "id": "maven.axonivy.com", - "url": "https://maven.axonivy.com", - "snapshots": { - "enabled": "true" - } - } - ] - } - }, - { - "id": "maven-dependency", - "data": { - "dependencies": [ - { - "groupId": "com.axonivy.utils.bpmnstatistic", - "artifactId": "bpmn-statistic", - "version": "${version}", - "type": "iar" - } - ], - "repositories": [ - { - "id": "maven.axonivy.com", - "url": "https://maven.axonivy.com", - "snapshots": { - "enabled": "true" - } - } - ] - } - } - ] - } - """; - } - - private static InputStream getMockInputStreamWithOutProjectAndDependency() { - String jsonContent = """ - { - "installers": [ - { - "data": { - "repositories": [ - { - "url": "http://example.com/repo" - } - ] - } - } - ] - } - """; - return new ByteArrayInputStream(jsonContent.getBytes(StandardCharsets.UTF_8)); - } - - private static GHContent createMockProductJson() { - GHContent mockProductJson = mock(GHContent.class); - when(mockProductJson.isFile()).thenReturn(true); - when(mockProductJson.getName()).thenReturn(ProductJsonConstants.PRODUCT_JSON_FILE, IMAGE_NAME); - return mockProductJson; - } - void setup() throws IOException { when(gitHubService.getOrganization(any())).thenReturn(mockGHOrganization); when(mockGHOrganization.getRepository(any())).thenReturn(ghRepository); @@ -335,6 +235,16 @@ private void testGetReadmeAndProductContentsFromTagWithReadmeText(String readmeC assertEquals("Setup content (imageId-66e2b14868f2f95b2f95549a)", result.getSetup().get(Language.EN.getValue())); } + 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_ImageFromFolder() throws IOException { String readmeContentWithImageFolder = "#Product-name\n Test README\n## Demo\nDemo content\n## Setup\nSetup " + @@ -386,6 +296,90 @@ void testGetReadmeAndProductContentsFromTag_SwitchPartsPosition() throws IOExcep assertEquals("Setup content", result.getSetup().get(Language.EN.getValue())); } + 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()); + } + + private static InputStream getMockInputStream() { + String jsonContent = getMockProductJsonContent(); + return new ByteArrayInputStream(jsonContent.getBytes(StandardCharsets.UTF_8)); + } + + private static String getMockProductJsonContent() { + return """ + { + "$schema": "https://json-schema.axonivy.com/market/10.0.0/product.json", + "installers": [ + { + "id": "maven-import", + "data": { + "projects": [ + { + "groupId": "com.axonivy.utils.bpmnstatistic", + "artifactId": "bpmn-statistic-demo", + "version": "${version}", + "type": "iar" + } + ], + "repositories": [ + { + "id": "maven.axonivy.com", + "url": "https://maven.axonivy.com", + "snapshots": { + "enabled": "true" + } + } + ] + } + }, + { + "id": "maven-dependency", + "data": { + "dependencies": [ + { + "groupId": "com.axonivy.utils.bpmnstatistic", + "artifactId": "bpmn-statistic", + "version": "${version}", + "type": "iar" + } + ], + "repositories": [ + { + "id": "maven.axonivy.com", + "url": "https://maven.axonivy.com", + "snapshots": { + "enabled": "true" + } + } + ] + } + } + ] + } + """; + } + + private static InputStream getMockInputStreamWithOutProjectAndDependency() { + String jsonContent = """ + { + "installers": [ + { + "data": { + "repositories": [ + { + "url": "http://example.com/repo" + } + ] + } + } + ] + } + """; + return new ByteArrayInputStream(jsonContent.getBytes(StandardCharsets.UTF_8)); + } + private Product createMockProduct() { Map names = Map.of("en", "docuware-connector-name"); Product product = new Product(); @@ -423,6 +417,13 @@ private GHContent createMockProductFolderWithProductJson() throws IOException { return mockContent; } + private static GHContent createMockProductJson() { + GHContent mockProductJson = mock(GHContent.class); + when(mockProductJson.isFile()).thenReturn(true); + when(mockProductJson.getName()).thenReturn(ProductJsonConstants.PRODUCT_JSON_FILE, IMAGE_NAME); + return mockProductJson; + } + @Test void test_insertProductJsonContent() throws IOException { ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(ProductJsonContent.class); From a26a150da68463e39da53bbcf73a6cc249c74904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=E1=BA=A1m=20Duy=20Linh?= <138570547+linhpd-axonivy@users.noreply.github.com> Date: Mon, 23 Sep 2024 10:35:13 +0700 Subject: [PATCH 09/20] fix sonar (#171) --- .../GHAxonIvyProductRepoServiceImplTest.java | 50 +++++++++++++++---- 1 file changed, 39 insertions(+), 11 deletions(-) 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 5d9a6d8e7..8216c57a1 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 @@ -207,11 +207,22 @@ void testGetOrganization() throws IOException { @Test void testGetReadmeAndProductContentsFromTag() throws IOException { - String readmeContentWithImage = "#Product-name\n Test README\n## Demo\nDemo content\n## Setup\nSetup content " + - "(image.png)"; + String readmeContentWithImage = """ + #Product-name + Test README + ## Demo + Demo content + ## Setup + Setup content (image.png) + """; testGetReadmeAndProductContentsFromTagWithReadmeText(readmeContentWithImage); - String readmeContentWithoutHashProductName = "Test README\n## Demo\nDemo content\n## Setup\nSetup content (image" + - ".png)"; + String readmeContentWithoutHashProductName = """ + Test README + ## Demo + Demo content + ## Setup + Setup content (image.png) + """; testGetReadmeAndProductContentsFromTagWithReadmeText(readmeContentWithoutHashProductName); } @@ -247,8 +258,14 @@ public static Image mockImage() { @Test void testGetReadmeAndProductContentFromTag_ImageFromFolder() throws IOException { - String readmeContentWithImageFolder = "#Product-name\n Test README\n## Demo\nDemo content\n## Setup\nSetup " + - "content (./images/image.png)"; + String readmeContentWithImageFolder = """ + #Product-name + Test README + ## Demo + Demo content + ## Setup + Setup content (./images/image.png) + """; GHContent mockImageFile = mock(GHContent.class); when(mockImageFile.getName()).thenReturn(ReadmeConstants.IMAGES, IMAGE_NAME); @@ -261,9 +278,14 @@ void testGetReadmeAndProductContentFromTag_ImageFromFolder() throws IOException String updatedReadme = axonivyProductRepoServiceImpl.updateImagesWithDownloadUrl(createMockProduct(), List.of(mockImageFile), readmeContentWithImageFolder); - assertEquals( - "#Product-name\n Test README\n## Demo\nDemo content\n## Setup\nSetup content " + - "(imageId-66e2b14868f2f95b2f95549a)", + assertEquals(""" + #Product-name + Test README + ## Demo + Demo content + ## Setup + Setup content (imageId-66e2b14868f2f95b2f95549a) + """, updatedReadme); } @@ -427,8 +449,14 @@ private static GHContent createMockProductJson() { @Test void test_insertProductJsonContent() throws IOException { ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(ProductJsonContent.class); - String readmeContentWithImage = "#Product-name\n Test README\n## Demo\nDemo content\n## Setup\nSetup content " + - "(image.png)"; + String readmeContentWithImage = """ + #Product-name + Test README + ## Demo + Demo content + ## Setup + Setup content (image.png) + """; GHContent mockContent = createMockProductFolderWithProductJson(); getReadmeInputStream(readmeContentWithImage, mockContent); InputStream inputStream = getMockInputStream(); From 9d12bd9f16936833ccf7d95d278528cce9f2b03a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=E1=BA=A1m=20Duy=20Linh?= <138570547+linhpd-axonivy@users.noreply.github.com> Date: Mon, 23 Sep 2024 11:03:07 +0700 Subject: [PATCH 10/20] MARP-994: Market-website-Artifact-and-version-in-download-feature-not save-update (#129) --- marketplace-ui/src/app/app.component.html | 4 +- marketplace-ui/src/app/app.component.scss | 18 --- marketplace-ui/src/app/app.component.ts | 3 +- .../show-feedbacks-dialog.component.spec.ts | 40 ++++- ...oduct-detail-version-action.component.html | 5 +- ...ct-detail-version-action.component.spec.ts | 153 +++++++++++++++++- ...product-detail-version-action.component.ts | 69 ++++++-- .../product-detail.component.ts | 3 +- .../modules/product/product.service.spec.ts | 4 +- .../app/modules/product/product.service.ts | 15 +- .../search-bar/search-bar.component.spec.ts | 20 ++- .../loading-spinner.component.html | 3 + .../loading-spinner.component.scss | 16 ++ .../loading-spinner.component.spec.ts | 45 ++++++ .../loading-spinner.component.ts | 16 ++ .../src/app/shared/pipes/icon.pipe.spec.ts | 33 ++++ .../src/app/shared/utils/common-utils.spec.ts | 97 +++++++++++ .../src/app/shared/utils/common.utils.ts | 23 +++ 18 files changed, 508 insertions(+), 59 deletions(-) create mode 100644 marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.html create mode 100644 marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.scss create mode 100644 marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.spec.ts create mode 100644 marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts create mode 100644 marketplace-ui/src/app/shared/pipes/icon.pipe.spec.ts create mode 100644 marketplace-ui/src/app/shared/utils/common-utils.spec.ts diff --git a/marketplace-ui/src/app/app.component.html b/marketplace-ui/src/app/app.component.html index 7ee11ade9..016a69a82 100644 --- a/marketplace-ui/src/app/app.component.html +++ b/marketplace-ui/src/app/app.component.html @@ -25,8 +25,6 @@ } @if (loadingService.isLoading()) { -
-
-
+ } diff --git a/marketplace-ui/src/app/app.component.scss b/marketplace-ui/src/app/app.component.scss index d3196b6f5..dba00e98b 100644 --- a/marketplace-ui/src/app/app.component.scss +++ b/marketplace-ui/src/app/app.component.scss @@ -25,24 +25,6 @@ footer { } } -.spinner-container { - position: fixed; - height: 100%; - width: 100%; - display: flex; - justify-content: center; - align-items: center; - top: 0; - left: 0; - background: rgba(0, 0, 0, 0.32); - z-index: 2000; -} - -.spinner-border { - width: 4rem; - height: 4rem; -} - .header-mobile { z-index: 1; position: absolute; diff --git a/marketplace-ui/src/app/app.component.ts b/marketplace-ui/src/app/app.component.ts index 720a809d8..c4016a8f8 100644 --- a/marketplace-ui/src/app/app.component.ts +++ b/marketplace-ui/src/app/app.component.ts @@ -12,11 +12,12 @@ import { RouterOutlet, Event } from '@angular/router'; +import { LoadingSpinnerComponent } from "./shared/components/loading-spinner/loading-spinner.component"; @Component({ selector: 'app-root', standalone: true, - imports: [RouterOutlet, HeaderComponent, FooterComponent, CommonModule], + imports: [RouterOutlet, HeaderComponent, FooterComponent, CommonModule, LoadingSpinnerComponent], templateUrl: './app.component.html', styleUrl: './app.component.scss' }) diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/show-feedbacks-dialog/show-feedbacks-dialog.component.spec.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/show-feedbacks-dialog/show-feedbacks-dialog.component.spec.ts index e0ff75b7c..d1f8273cd 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/show-feedbacks-dialog/show-feedbacks-dialog.component.spec.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/show-feedbacks-dialog/show-feedbacks-dialog.component.spec.ts @@ -7,10 +7,7 @@ import { AppModalService } from '../../../../../shared/services/app-modal.servic import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { CommonModule } from '@angular/common'; import { By } from '@angular/platform-browser'; -import { - provideHttpClient, - withInterceptorsFromDi -} from '@angular/common/http'; +import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; import { provideHttpClientTesting } from '@angular/common/http/testing'; describe('ShowFeedbacksDialogComponent', () => { @@ -74,4 +71,39 @@ describe('ShowFeedbacksDialogComponent', () => { expect(mockAppModalService.openAddFeedbackDialog).toHaveBeenCalled(); }); + + it('should call activeModal.dismiss when the window is resized to less than or equal to 767px', () => { + // Set up a media query list that matches + spyOn(window, 'matchMedia').and.returnValue({ + matches: true, + addListener: () => { + }, + removeListener: () => { + } + } as any); + + // Trigger the resize event + component.onResize(); + + // Check that dismiss was called + expect(mockActiveModal.dismiss).toHaveBeenCalled(); + }); + + it('should not call activeModal.dismiss when the window is resized to more than 767px', () => { + // Set up a media query list that does not match + spyOn(window, 'matchMedia').and.returnValue({ + matches: false, + addListener: () => { + }, + removeListener: () => { + } + } as any); + + // Trigger the resize event + component.onResize(); + + // Check that dismiss was not called + expect(mockActiveModal.dismiss).not.toHaveBeenCalled(); + }); + }); 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 b6bb3e8d0..ba5356303 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 @@ -56,7 +56,7 @@ } @if (isDropDownDisplayed()) { - } @case ('designerEnv') {
@@ -53,68 +119,4 @@ {{ 'common.product.detail.contactUs.label' | translate }} } -} - -@if (isDropDownDisplayed()) { - -} +} \ 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.scss b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.scss index 753ca8452..d0d22de86 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.scss +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.scss @@ -48,9 +48,6 @@ } } } -::ng-deep .dropdown-menu { - min-width: 100%; -} ::ng-deep .dropdown-item { font-size: 14px; @@ -86,12 +83,8 @@ } .btn__install { - margin-right: 10px; border: 0px; } -.btn__download { - border: 0.5px solid #ebebeb; -} .primary-color { color: var(--ivy-primary-bg); } @@ -106,7 +99,7 @@ } ::ng-deep .dropdown-menu.menu-bar { - margin-top: 4px; + margin-top: 2.5rem; } ::ng-deep .install-designer-dropdown { @@ -115,22 +108,32 @@ text-align: start; } +.install-download-button-container { + position: relative; +} + +.btn__download { + border: 0.5px solid #ebebeb; +} + ::ng-deep .dropdown-menu { + position: absolute; + right: 0; + width: 400px; + height: 204px; + margin-top: 1rem; + background-color: var(--bs-body-bg); + z-index: 999; padding: 10px; border-radius: 5px; - gap: 15px; box-shadow: 0px 4px 30px 0px rgba(0, 0, 0, 0.1); - margin-top: 1.2rem; - &.maven-artifact-version__action { - min-width: 400px; - min-height: 204px; - @media (max-width: 500px) { - left: auto; - right: auto; - min-width: 80vw; - } + @media screen and (max-width: 991px) { + width: 100%; + margin-top: 4rem; + } + &.maven-artifact-version__action { .form-group { gap: 7px; @@ -182,17 +185,16 @@ top: -10px; width: 20px; height: 20px; - left: 50%; + right: 12%; z-index: 1001; - @media (max-width: 767px) { - top: -8px; - width: 16px; - height: 16px; - } transform: translateX(-50%) rotate(45deg); border-right-color: transparent; border-bottom-color: transparent; background-color: var(--bs-body-bg); + + @media screen and (max-width: 991px) { + right: 22%; + } } .btn_contact-us { 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 83cd43ae9..11421ff95 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 @@ -5,7 +5,6 @@ import { computed, ElementRef, EventEmitter, - HostListener, inject, Input, model, @@ -57,9 +56,6 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { @Input() productId!: string; @Input() actionType!: ProductDetailActionType; - @ViewChild('artifactDownloadButton') artifactDownloadButton!: ElementRef; - @ViewChild('artifactDownloadDialog') artifactDownloadDialog!: ElementRef; - @Input() product!: ProductDetail; selectedVersion = model(''); versions: WritableSignal = signal([]); @@ -155,44 +151,8 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { this.getVersionWithArtifact(); } this.isDropDownDisplayed.set(!this.isDropDownDisplayed()); - this.changeDetectorRef.detectChanges(); - this.reLocaleDialog(); - } - - @HostListener('window:resize', ['$event']) - onResize() { - this.reLocaleDialog(); - } - - reLocaleDialog() { - const buttonPosition = this.getElementPosition(this.artifactDownloadButton); - const dialogPosition = this.getElementPosition(this.artifactDownloadDialog); - if (buttonPosition && dialogPosition) { - const dialogElement = this.artifactDownloadDialog.nativeElement; - - dialogElement.style.position = 'absolute'; - dialogElement.style.top = `${buttonPosition.y + buttonPosition.height}px`; - - // Align the dialog to the center of the button - const dialogWidth = dialogElement.offsetWidth; - const buttonCenterX = buttonPosition.x + buttonPosition.width / 2; - dialogElement.style.left = `${buttonCenterX - dialogWidth / 2}px`; - } - } - - getElementPosition(element: ElementRef) { - if (element?.nativeElement) { - const rect = element.nativeElement.getBoundingClientRect(); - return { - x: rect.left + window.scrollX, - y: rect.top + window.scrollY, - width: rect.width, - height: rect.height - }; - } - return null; } - + getVersionWithArtifact(ignoreRouteVersion = false) { this.isArtifactLoading.set(true); this.sanitizeDataBeforeFetching(); 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 fbb560db5..105663298 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 @@ -15,8 +15,8 @@
-
-
+
+
-
-
- -
+
-
From f08040613970981789c46aa82b1e8d3b2086735b Mon Sep 17 00:00:00 2001 From: vhhoang-axonivy Date: Thu, 26 Sep 2024 14:40:45 +0700 Subject: [PATCH 15/20] MARP-1092 Show real error code in error page (#173) --- marketplace-ui/src/app/app.routes.ts | 10 ++- .../app/core/interceptors/api.interceptor.ts | 8 ++- .../error-page.component.html} | 6 +- .../error-page.component.scss} | 2 +- .../error-page.component.spec.ts} | 70 +++++++++++++++---- .../error-page.component.ts} | 17 +++-- .../app/shared/constants/common.constant.ts | 2 +- marketplace-ui/src/assets/i18n/de.yaml | 2 +- marketplace-ui/src/assets/i18n/en.yaml | 2 +- 9 files changed, 89 insertions(+), 30 deletions(-) rename marketplace-ui/src/app/shared/components/{error-page-component/error-page-component.component.html => error-page/error-page.component.html} (87%) rename marketplace-ui/src/app/shared/components/{error-page-component/error-page-component.component.scss => error-page/error-page.component.scss} (97%) rename marketplace-ui/src/app/shared/components/{error-page-component/error-page-component.component.spec.ts => error-page/error-page.component.spec.ts} (57%) rename marketplace-ui/src/app/shared/components/{error-page-component/error-page-component.component.ts => error-page/error-page.component.ts} (74%) diff --git a/marketplace-ui/src/app/app.routes.ts b/marketplace-ui/src/app/app.routes.ts index 08f5524ec..7efb78add 100644 --- a/marketplace-ui/src/app/app.routes.ts +++ b/marketplace-ui/src/app/app.routes.ts @@ -1,11 +1,15 @@ import { Routes } from '@angular/router'; import { GithubCallbackComponent } from './auth/github-callback/github-callback.component'; -import { ErrorPageComponentComponent } from './shared/components/error-page-component/error-page-component.component'; +import { ErrorPageComponent } from './shared/components/error-page/error-page.component'; export const routes: Routes = [ { path: 'error-page', - component: ErrorPageComponentComponent + component: ErrorPageComponent + }, + { + path: 'error-page/:id', + component: ErrorPageComponent }, { path: '', @@ -20,4 +24,4 @@ export const routes: Routes = [ path: 'auth/github/callback', component: GithubCallbackComponent } -]; +]; \ No newline at end of file diff --git a/marketplace-ui/src/app/core/interceptors/api.interceptor.ts b/marketplace-ui/src/app/core/interceptors/api.interceptor.ts index 19475e3d5..afba94bed 100644 --- a/marketplace-ui/src/app/core/interceptors/api.interceptor.ts +++ b/marketplace-ui/src/app/core/interceptors/api.interceptor.ts @@ -6,7 +6,7 @@ import { import { environment } from '../../../environments/environment'; import { LoadingService } from '../services/loading/loading.service'; import { inject } from '@angular/core'; -import { catchError, finalize, throwError } from 'rxjs'; +import { catchError, EMPTY, finalize } from 'rxjs'; import { Router } from '@angular/router'; import { ERROR_CODES, ERROR_PAGE_PATH } from '../../shared/constants/common.constant'; @@ -45,9 +45,11 @@ export const apiInterceptor: HttpInterceptorFn = (req, next) => { return next(cloneReq).pipe( catchError(error => { if (ERROR_CODES.includes(error.status)) { + router.navigate([`${ERROR_PAGE_PATH}/error.status`]); + } else { router.navigate([ERROR_PAGE_PATH]); } - return throwError(() => new Error(error.message)); + return EMPTY; }), finalize(() => { loadingService.hide(); @@ -60,4 +62,4 @@ function addIvyHeaders(headers: HttpHeaders): HttpHeaders { return headers; } return headers.append(REQUEST_BY, IVY); -} +} \ No newline at end of file diff --git a/marketplace-ui/src/app/shared/components/error-page-component/error-page-component.component.html b/marketplace-ui/src/app/shared/components/error-page/error-page.component.html similarity index 87% rename from marketplace-ui/src/app/shared/components/error-page-component/error-page-component.component.html rename to marketplace-ui/src/app/shared/components/error-page/error-page.component.html index 9de60b3d1..bf6cbc0f2 100644 --- a/marketplace-ui/src/app/shared/components/error-page-component/error-page-component.component.html +++ b/marketplace-ui/src/app/shared/components/error-page/error-page.component.html @@ -3,7 +3,11 @@ [ngClass]="isMobileMode() ? 'mobile-mode' : ''">
-
{{ 'common.error.code' | translate }}
+ @if (errorId !== undefined) { +
+ {{ 'common.error.code' | translate }}: {{ errorId }} +
+ }
{{ 'common.error.oops' | translate }}
{{ 'common.error.description' | translate }} diff --git a/marketplace-ui/src/app/shared/components/error-page-component/error-page-component.component.scss b/marketplace-ui/src/app/shared/components/error-page/error-page.component.scss similarity index 97% rename from marketplace-ui/src/app/shared/components/error-page-component/error-page-component.component.scss rename to marketplace-ui/src/app/shared/components/error-page/error-page.component.scss index 0085d3d45..7c92445e9 100644 --- a/marketplace-ui/src/app/shared/components/error-page-component/error-page-component.component.scss +++ b/marketplace-ui/src/app/shared/components/error-page/error-page.component.scss @@ -10,7 +10,7 @@ } } - .error-code-404 { + .error-code { font-size: 14px; font-weight: 600; line-height: 16.8px; diff --git a/marketplace-ui/src/app/shared/components/error-page-component/error-page-component.component.spec.ts b/marketplace-ui/src/app/shared/components/error-page/error-page.component.spec.ts similarity index 57% rename from marketplace-ui/src/app/shared/components/error-page-component/error-page-component.component.spec.ts rename to marketplace-ui/src/app/shared/components/error-page/error-page.component.spec.ts index a1d35370e..1e105b5a7 100644 --- a/marketplace-ui/src/app/shared/components/error-page-component/error-page-component.component.spec.ts +++ b/marketplace-ui/src/app/shared/components/error-page/error-page.component.spec.ts @@ -1,39 +1,52 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ErrorPageComponentComponent } from './error-page-component.component'; +import { ErrorPageComponent } from './error-page.component'; import { TranslateModule } from '@ngx-translate/core'; import { Viewport } from 'karma-viewport/dist/adapter/viewport'; import { By } from '@angular/platform-browser'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; declare const viewport: Viewport; describe('ErrorPageComponentComponent', () => { - let component: ErrorPageComponentComponent; - let fixture: ComponentFixture; + let component: ErrorPageComponent; + let fixture: ComponentFixture; let router: Router; - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ErrorPageComponentComponent, TranslateModule.forRoot()] - }).compileComponents(); +const setupComponent = (idValue: string | undefined) => { + TestBed.configureTestingModule({ + imports: [ErrorPageComponent, TranslateModule.forRoot()], + providers: [ + { + provide: ActivatedRoute, + useValue: { + snapshot: { + params: { id: idValue } + } + } + } + ] + }).compileComponents(); - fixture = TestBed.createComponent(ErrorPageComponentComponent); - component = fixture.componentInstance; - router = TestBed.inject(Router); - fixture.detectChanges(); - }); + fixture = TestBed.createComponent(ErrorPageComponent ); + component = fixture.componentInstance; + router = TestBed.inject(Router); + fixture.detectChanges(); +}; it('should create', () => { + setupComponent(undefined); expect(component).toBeTruthy(); }); it('should call checkMediaSize on window resize', () => { + setupComponent(undefined); spyOn(component, 'checkMediaSize'); component.onResize(); expect(component.checkMediaSize).toHaveBeenCalled(); }); it('should display image with the light mode on small and large viewport', () => { + setupComponent(undefined); component.themeService.isDarkMode.set(false); viewport.set(1920); component.onResize(); @@ -50,6 +63,7 @@ describe('ErrorPageComponentComponent', () => { }); it('should display image with the dark mode on small and large viewport', () => { + setupComponent(undefined); component.themeService.isDarkMode.set(true); viewport.set(1920); component.onResize(); @@ -68,6 +82,7 @@ describe('ErrorPageComponentComponent', () => { }); it('should back to the home page', () => { + setupComponent(undefined); spyOn(component, 'backToHomePage'); let buttonElement = fixture.debugElement.query(By.css('button')); buttonElement.nativeElement.click(); @@ -75,8 +90,35 @@ describe('ErrorPageComponentComponent', () => { }); it('should redirect to the home page', () => { + setupComponent(undefined); const navigateSpy = spyOn(router, 'navigate'); component.backToHomePage(); expect(navigateSpy).toHaveBeenCalledWith(['/']); }); -}); + + describe('when id is undefined', () => { + beforeEach(() => { + setupComponent(undefined); + }); + + it('should set error id to undefined', () => { + const errorCode = fixture.debugElement.query(By.css('.error-code')); + + expect(component.errorId).toBeUndefined(); + expect(errorCode).toBeFalsy(); + }); + }); + + describe('when error id is in error codes', () => { + beforeEach(() => { + setupComponent('404'); + }); + + it('should set error id to the same as in route param', () => { + const errorCode = fixture.debugElement.query(By.css('.error-code')); + + expect(component.errorId).toBe('404'); + expect(errorCode).toBeTruthy(); + }); + }); +}); \ No newline at end of file diff --git a/marketplace-ui/src/app/shared/components/error-page-component/error-page-component.component.ts b/marketplace-ui/src/app/shared/components/error-page/error-page.component.ts similarity index 74% rename from marketplace-ui/src/app/shared/components/error-page-component/error-page-component.component.ts rename to marketplace-ui/src/app/shared/components/error-page/error-page.component.ts index f97495db8..274b6f522 100644 --- a/marketplace-ui/src/app/shared/components/error-page-component/error-page-component.component.ts +++ b/marketplace-ui/src/app/shared/components/error-page/error-page.component.ts @@ -1,26 +1,33 @@ -import { Component, HostListener, inject, signal } from '@angular/core'; +import { Component, HostListener, inject, OnInit, signal } from '@angular/core'; import { ThemeService } from '../../../core/services/theme/theme.service'; import { LanguageService } from '../../../core/services/language/language.service'; import { CommonModule } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; @Component({ selector: 'app-error-page-component', standalone: true, imports: [CommonModule, TranslateModule], - templateUrl: './error-page-component.component.html', - styleUrl: './error-page-component.component.scss' + templateUrl: './error-page.component.html', + styleUrl: './error-page.component.scss' }) -export class ErrorPageComponentComponent { +export class ErrorPageComponent implements OnInit { themeService = inject(ThemeService); languageService = inject(LanguageService); isMobileMode = signal(false); + route = inject(ActivatedRoute); + + errorId: string | undefined; constructor(private readonly router: Router) { this.checkMediaSize(); } + ngOnInit(): void { + this.errorId = this.route.snapshot.params['id']; + } + backToHomePage() { this.router.navigate(['/']); } diff --git a/marketplace-ui/src/app/shared/constants/common.constant.ts b/marketplace-ui/src/app/shared/constants/common.constant.ts index ffc6e73ef..61a714f70 100644 --- a/marketplace-ui/src/app/shared/constants/common.constant.ts +++ b/marketplace-ui/src/app/shared/constants/common.constant.ts @@ -196,7 +196,7 @@ export const VERSION = { displayPrefix: 'Version ' }; -export const ERROR_PAGE_PATH = '/error-page'; +export const ERROR_PAGE_PATH = 'error-page'; export const NOT_FOUND_ERROR_CODE = 404; export const INTERNAL_SERVER_ERROR_CODE = 500; export const UNDEFINED_ERROR_CODE = 0; diff --git a/marketplace-ui/src/assets/i18n/de.yaml b/marketplace-ui/src/assets/i18n/de.yaml index 7a844258d..add8509a9 100644 --- a/marketplace-ui/src/assets/i18n/de.yaml +++ b/marketplace-ui/src/assets/i18n/de.yaml @@ -24,7 +24,7 @@ common: demos: Demos solution: Lösungen error: - code: 'ERROR CODE: 404' + code: 'ERROR CODE' oops: 'Oops!' description: 'Unsere Webseite macht eine kleine Pause. Offenbar finden wir die von Dir angefragte Seite nicht.' buttonLabel: Zurück zur Startseite diff --git a/marketplace-ui/src/assets/i18n/en.yaml b/marketplace-ui/src/assets/i18n/en.yaml index 6d2b425ed..5c0fb61cc 100644 --- a/marketplace-ui/src/assets/i18n/en.yaml +++ b/marketplace-ui/src/assets/i18n/en.yaml @@ -28,7 +28,7 @@ common: demos: Demos solution: Solutions error: - code: 'ERROR CODE: 404' + code: 'ERROR CODE' oops: 'Oops!' description: 'Our website is taking a break. It seems that we cannot find the page you requested.' buttonLabel: Back to homepage From 4d77c04a555d6c9b7053239f60fefc180a31b09b Mon Sep 17 00:00:00 2001 From: Khanh Nguyen <119989010+ndkhanh-axonivy@users.noreply.github.com> Date: Thu, 26 Sep 2024 15:26:18 +0700 Subject: [PATCH 16/20] MARP-1175 Add null check for finding product detail (#178) --- .../controller/ProductDetailsController.java | 9 +++++ .../ProductDetailsControllerTest.java | 40 +++++++++++++++++++ 2 files changed, 49 insertions(+) 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 e68f6ff0b..0740663fa 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 @@ -49,6 +49,9 @@ public ResponseEntity findProductDetailsByVersion( @PathVariable(VERSION) @Parameter(description = "Release version (from maven metadata.xml)", example = "10.0.20", in = ParameterIn.PATH) String version) { var productDetail = productService.fetchProductDetailByIdAndVersion(id, version); + if (productDetail == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } return new ResponseEntity<>(detailModelAssembler.toModel(productDetail, version, BY_ID_AND_VERSION), HttpStatus.OK); } @@ -61,6 +64,9 @@ public ResponseEntity findBestMatchProductDetailsByVersion( @PathVariable(VERSION) @Parameter(description = "Version", example = "10.0.20", in = ParameterIn.PATH) String version) { var productDetail = productService.fetchBestMatchProductDetail(id, version); + if (productDetail == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } return new ResponseEntity<>(detailModelAssembler.toModel(productDetail, version, BEST_MATCH_BY_ID_AND_VERSION), HttpStatus.OK); } @@ -83,6 +89,9 @@ public ResponseEntity findProductDetails( @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", in = ParameterIn.PATH) String id) { var productDetail = productService.fetchProductDetail(id); + if (productDetail == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } return new ResponseEntity<>(detailModelAssembler.toModel(productDetail, BY_ID), HttpStatus.OK); } 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 0aa8e05da..a2135f21c 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 @@ -56,6 +56,7 @@ class ProductDetailsControllerTest { private static final String PRODUCT_NAME_SAMPLE = "Docker"; private static final String PRODUCT_NAME_DE_SAMPLE = "Docker DE"; public static final String DOCKER_CONNECTOR_ID = "docker-connector"; + public static final String WRONG_PRODUCT_ID = "wrong-product-id"; @Test void testProductDetails() { @@ -116,6 +117,45 @@ void testProductDetailsWithVersion() { verify(productService, times(1)).fetchProductDetailByIdAndVersion(DOCKER_CONNECTOR_ID, TAG); } + @Test + void testProductDetailsWithVersionWithWrongProductId() { + Mockito.when(productService.fetchProductDetailByIdAndVersion(Mockito.anyString(), Mockito.anyString())).thenReturn( + null); + + ResponseEntity result = productDetailsController.findProductDetailsByVersion( + WRONG_PRODUCT_ID, TAG); + + assertEquals(HttpStatus.NOT_FOUND, result.getStatusCode()); + + verify(productService, times(1)).fetchProductDetailByIdAndVersion(WRONG_PRODUCT_ID, TAG); + } + + @Test + void testBestMatchProductDetailsWithVersionWithWrongProductId() { + Mockito.when(productService.fetchBestMatchProductDetail(Mockito.anyString(), Mockito.anyString())).thenReturn( + null); + + ResponseEntity result = productDetailsController.findBestMatchProductDetailsByVersion( + WRONG_PRODUCT_ID, TAG); + + assertEquals(HttpStatus.NOT_FOUND, result.getStatusCode()); + + verify(productService, times(1)).fetchBestMatchProductDetail(WRONG_PRODUCT_ID, TAG); + } + + @Test + void testProductDetailsWithWrongProductId() { + Mockito.when(productService.fetchProductDetail(Mockito.anyString())).thenReturn( + null); + + ResponseEntity result = productDetailsController.findProductDetails( + WRONG_PRODUCT_ID); + + assertEquals(HttpStatus.NOT_FOUND, result.getStatusCode()); + + verify(productService, times(1)).fetchProductDetail(WRONG_PRODUCT_ID); + } + @Test void testFindProductVersionsById() { List models = List.of(new MavenArtifactVersionModel()); From d8f0214bf5f89ae754d4885fc9bdbce4be6837e0 Mon Sep 17 00:00:00 2001 From: vhhoang-axonivy Date: Thu, 26 Sep 2024 17:22:15 +0700 Subject: [PATCH 17/20] MARP-1174 Fix various styling issues of buttons and dropdowns (#177) --- .../app/core/interceptors/api.interceptor.ts | 2 +- ...oduct-detail-version-action.component.html | 4 +- ...oduct-detail-version-action.component.scss | 29 ++- ...product-detail-version-action.component.ts | 1 - .../product-detail.component.html | 183 +++++++++--------- .../product-detail.component.scss | 17 +- .../product-detail.component.spec.ts | 25 +++ .../common-dropdown.component.html | 2 +- 8 files changed, 158 insertions(+), 105 deletions(-) diff --git a/marketplace-ui/src/app/core/interceptors/api.interceptor.ts b/marketplace-ui/src/app/core/interceptors/api.interceptor.ts index afba94bed..91b9b729c 100644 --- a/marketplace-ui/src/app/core/interceptors/api.interceptor.ts +++ b/marketplace-ui/src/app/core/interceptors/api.interceptor.ts @@ -45,7 +45,7 @@ export const apiInterceptor: HttpInterceptorFn = (req, next) => { return next(cloneReq).pipe( catchError(error => { if (ERROR_CODES.includes(error.status)) { - router.navigate([`${ERROR_PAGE_PATH}/error.status`]); + router.navigate([`${ERROR_PAGE_PATH}/${error.status}`]); } else { router.navigate([ERROR_PAGE_PATH]); } 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 cd77299f4..e86f0ea13 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,6 +1,6 @@ @switch (actionType) { @case ('standard') { -
+
+
- -
- + show: isDropdownOpen() + }"> + +
-
-
- @if (!isEmptyProductContent()) { - @for (displayedTab of displayedTabsSignal(); track $index) { -
- @if (displayedTab.value === 'dependency') { - - - } @else { - - } -
+
+ @if (!isEmptyProductContent()) { + @for (displayedTab of displayedTabsSignal(); track $index) { +
+ @if (displayedTab.value === 'dependency') { + + + } @else { + + } +
+ } } - } -
+
+ }
{ ProductDetailActionType.STANDARD ); }); + + it('displayed tabs array should have size 0 if product module content description, setup, demo, dependcy are all empty', () => { + const mockContent: ProductModuleContent = { + ...MOCK_PRODUCT_MODULE_CONTENT, + }; + component.productModuleContent.set(mockContent); + + expect(component.displayedTabsSignal().length).toBe(0); + }); + + it('should hide tab and tab content when all tabs have no content', () => { + const mockContent: ProductModuleContent = { + ...MOCK_PRODUCT_MODULE_CONTENT, + }; + component.productModuleContent.set(mockContent); + + const tabGroup = fixture.debugElement.query(By.css('.tab-group')); + const tabs = tabGroup.query(By.css('.row-tab d-none d-xl-block col-12')); + const dropdown = tabGroup.query(By.css('.dropdown-tab d-block d-xl-none d-flex flex-row justify-content-center align-items-center w-100')); + const tabContent = tabGroup.query(By.css('.tab-content col-12 default-cursor')); + + expect(tabs).toBeFalsy(); + expect(dropdown).toBeFalsy(); + expect(tabContent).toBeFalsy(); + }); }); diff --git a/marketplace-ui/src/app/shared/components/common-dropdown/common-dropdown.component.html b/marketplace-ui/src/app/shared/components/common-dropdown/common-dropdown.component.html index 2799c25f8..69293998f 100644 --- a/marketplace-ui/src/app/shared/components/common-dropdown/common-dropdown.component.html +++ b/marketplace-ui/src/app/shared/components/common-dropdown/common-dropdown.component.html @@ -6,7 +6,7 @@ [attr.aria-label]="ariaLabel"> {{ selectedItem }} -