From 0850286e01d436623252d78296e09b180db1f4ac Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Tue, 10 Dec 2024 18:02:14 +0700 Subject: [PATCH 01/26] Update UI --- marketplace-ui/src/app/app.component.html | 4 - marketplace-ui/src/app/app.component.ts | 5 +- ...oduct-detail-version-action.component.html | 2 +- .../modules/product/product.component.html | 4 + .../app/modules/product/product.component.ts | 6 +- .../loading-spinner.component.html | 8 +- .../loading-spinner.component.scss | 82 ++++++++++++++++--- .../loading-spinner.component.ts | 5 +- 8 files changed, 88 insertions(+), 28 deletions(-) diff --git a/marketplace-ui/src/app/app.component.html b/marketplace-ui/src/app/app.component.html index ce18d841b..308f1e2ce 100644 --- a/marketplace-ui/src/app/app.component.html +++ b/marketplace-ui/src/app/app.component.html @@ -22,8 +22,4 @@ } - - @if (loadingService.isLoading()) { - - } diff --git a/marketplace-ui/src/app/app.component.ts b/marketplace-ui/src/app/app.component.ts index 67b0d9dff..8ca778c5b 100644 --- a/marketplace-ui/src/app/app.component.ts +++ b/marketplace-ui/src/app/app.component.ts @@ -1,6 +1,5 @@ import { FooterComponent } from './shared/components/footer/footer.component'; import { HeaderComponent } from './shared/components/header/header.component'; -import { LoadingService } from './core/services/loading/loading.service'; import { RoutingQueryParamService } from './shared/services/routing.query.param.service'; import { CommonModule } from '@angular/common'; import { ERROR_PAGE_PATH } from './shared/constants/common.constant'; @@ -12,18 +11,16 @@ import { RouterOutlet, Event } from '@angular/router'; -import { LoadingSpinnerComponent } from "./shared/components/loading-spinner/loading-spinner.component"; import { BackToTopComponent } from "./shared/components/back-to-top/back-to-top.component"; @Component({ selector: 'app-root', standalone: true, - imports: [RouterOutlet, HeaderComponent, FooterComponent, CommonModule, LoadingSpinnerComponent, BackToTopComponent], + imports: [RouterOutlet, HeaderComponent, FooterComponent, CommonModule, BackToTopComponent], templateUrl: './app.component.html', styleUrl: './app.component.scss' }) export class AppComponent { - loadingService = inject(LoadingService); routingQueryParamService = inject(RoutingQueryParamService); route = inject(ActivatedRoute); isMobileMenuCollapsed = true; 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 4fb9bca6c..d1b6f090d 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 @@ -104,7 +104,7 @@ @if (isArtifactLoading()) { - + } } diff --git a/marketplace-ui/src/app/modules/product/product.component.html b/marketplace-ui/src/app/modules/product/product.component.html index 17e919271..b07fb4544 100644 --- a/marketplace-ui/src/app/modules/product/product.component.html +++ b/marketplace-ui/src/app/modules/product/product.component.html @@ -48,5 +48,9 @@

} + @if (loadingService.isLoading()) { + } + +
diff --git a/marketplace-ui/src/app/modules/product/product.component.ts b/marketplace-ui/src/app/modules/product/product.component.ts index 46a8eac23..f9de02844 100644 --- a/marketplace-ui/src/app/modules/product/product.component.ts +++ b/marketplace-ui/src/app/modules/product/product.component.ts @@ -34,6 +34,8 @@ import { DESIGNER_COOKIE_VARIABLE } from '../../shared/constants/common.constant'; import { ItemDropdown } from '../../shared/models/item-dropdown.model'; +import { LoadingSpinnerComponent } from '../../shared/components/loading-spinner/loading-spinner.component'; +import { LoadingService } from '../../core/services/loading/loading.service'; const SEARCH_DEBOUNCE_TIME = 500; @@ -41,6 +43,7 @@ const SEARCH_DEBOUNCE_TIME = 500; selector: 'app-product', standalone: true, imports: [ + LoadingSpinnerComponent, CommonModule, FormsModule, TranslateModule, @@ -56,6 +59,7 @@ export class ProductComponent implements AfterViewInit, OnDestroy { productDetail!: ProductDetail; subscriptions: Subscription[] = []; searchTextChanged = new Subject(); + loadingService = inject(LoadingService); criteria: Criteria = { search: '', type: TypeOption.All_TYPES, @@ -115,7 +119,7 @@ export class ProductComponent implements AfterViewInit, OnDestroy { } viewProductDetail(productId: string, _productTag: string) { - if(this.isRESTClient()) { + if (this.isRESTClient()) { window.location.href = `/${productId}`; } this.router.navigate([`/${productId}`]); diff --git a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.html b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.html index bd8f64d01..21fe9e4e9 100644 --- a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.html +++ b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.html @@ -1,3 +1,5 @@ -
-
-
\ No newline at end of file +
+
+
+
+
diff --git a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.scss b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.scss index ea8350003..10d4cc7fb 100644 --- a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.scss +++ b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.scss @@ -1,16 +1,74 @@ -.spinner-container { - height: 100%; - width: 100%; - display: flex; - justify-content: center; - align-items: center; +.dot-stretching { + position: relative; + width: 10px; + height: 10px; + border-radius: 5px; + background-color: var(--ivy-primary-bg); + color: var(--ivy-primary-bg); + transform: scale(1.25, 1.25); + animation: dot-stretching 1s infinite ease-in; +} +.dot-stretching::before, +.dot-stretching::after { + content: ''; + display: inline-block; + position: absolute; top: 0; - left: 0; - background: rgba(0, 0, 0, 0.32); - z-index: 2000; +} +.dot-stretching::before { + width: 10px; + height: 10px; + border-radius: 5px; + background-color: var(--ivy-primary-bg); + color: var(--ivy-primary-bg); + animation: dot-stretching-before 2s infinite ease-in; +} +.dot-stretching::after { + width: 10px; + height: 10px; + border-radius: 5px; + background-color: var(--ivy-primary-bg); + color: var(--ivy-primary-bg); + animation: dot-stretching-after 2s infinite ease-in; } -.spinner-border { - width: 4rem; - height: 4rem; +.stage { + width: 0px; +} + +@keyframes dot-stretching { + 0% { + transform: scale(1.25, 1.25); + } + 50%, + 60% { + transform: scale(0.8, 0.8); + } + 100% { + transform: scale(1.25, 1.25); + } +} +@keyframes dot-stretching-before { + 0% { + transform: translate(0) scale(0.7, 0.7); + } + 50%, + 60% { + transform: translate(-20px) scale(1, 1); + } + 100% { + transform: translate(0) scale(0.7, 0.7); + } +} +@keyframes dot-stretching-after { + 0% { + transform: translate(0) scale(0.7, 0.7); + } + 50%, + 60% { + transform: translate(20px) scale(1, 1); + } + 100% { + transform: translate(0) scale(0.7, 0.7); + } } diff --git a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts index 182731f23..74a54a837 100644 --- a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts +++ b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts @@ -1,4 +1,4 @@ -import { Component, Input } from '@angular/core'; +import { Component } from '@angular/core'; import { NgClass } from '@angular/common'; @Component({ @@ -11,6 +11,5 @@ import { NgClass } from '@angular/common'; styleUrl: './loading-spinner.component.scss' }) export class LoadingSpinnerComponent { - @Input() - isFixPosition = true; + spinners = Array(5).fill(null); } From 5803f590e1699b0b59943e5c8ef0484a37aa939c Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Tue, 10 Dec 2024 18:20:26 +0700 Subject: [PATCH 02/26] refactor css --- .../loading-spinner.component.scss | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.scss b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.scss index 10d4cc7fb..4298d17dc 100644 --- a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.scss +++ b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.scss @@ -8,6 +8,17 @@ transform: scale(1.25, 1.25); animation: dot-stretching 1s infinite ease-in; } + +.dot-stretching, +.dot-stretching::before, +.dot-stretching::after{ + width: 10px; + height: 10px; + border-radius: 5px; + background-color: var(--ivy-primary-bg); + color: var(--ivy-primary-bg); +} + .dot-stretching::before, .dot-stretching::after { content: ''; @@ -15,21 +26,14 @@ position: absolute; top: 0; } + + .dot-stretching::before { - width: 10px; - height: 10px; - border-radius: 5px; - background-color: var(--ivy-primary-bg); - color: var(--ivy-primary-bg); - animation: dot-stretching-before 2s infinite ease-in; + animation: dot-stretching-before 1s infinite ease-in; } + .dot-stretching::after { - width: 10px; - height: 10px; - border-radius: 5px; - background-color: var(--ivy-primary-bg); - color: var(--ivy-primary-bg); - animation: dot-stretching-after 2s infinite ease-in; + animation: dot-stretching-after 1s infinite ease-in; } .stage { From d5ede2b66f7a500bff4c208484cccc183814d84f Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Tue, 10 Dec 2024 18:29:41 +0700 Subject: [PATCH 03/26] update html --- marketplace-ui/src/app/modules/product/product.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketplace-ui/src/app/modules/product/product.component.html b/marketplace-ui/src/app/modules/product/product.component.html index b07fb4544..21a5e6c93 100644 --- a/marketplace-ui/src/app/modules/product/product.component.html +++ b/marketplace-ui/src/app/modules/product/product.component.html @@ -49,8 +49,8 @@

} @if (loadingService.isLoading()) { + } -
From e71683f441b3b617937e635a3c10e7445b542aef Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Wed, 11 Dec 2024 16:02:32 +0700 Subject: [PATCH 04/26] update service --- ...duct-detail-information-tab.component.html | 228 ++++++++++-------- ...roduct-detail-information-tab.component.ts | 3 +- ...oduct-detail-version-action.component.html | 7 +- ...oduct-detail-version-action.component.scss | 5 + .../product-detail.component.html | 4 + .../product-detail.component.ts | 2 - .../app/modules/product/product.service.ts | 4 +- ...issing-product-Information-content-pipe.ts | 14 ++ .../pipes/missing-readme-content.pipe.ts | 14 -- .../src/app/shared/pipes/product-type.pipe.ts | 2 +- 10 files changed, 163 insertions(+), 120 deletions(-) create mode 100644 marketplace-ui/src/app/shared/pipes/missing-product-Information-content-pipe.ts delete mode 100644 marketplace-ui/src/app/shared/pipes/missing-readme-content.pipe.ts diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.html b/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.html index 57a78e8d1..359cb46c8 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.html +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.html @@ -1,118 +1,150 @@ -

- {{ 'common.product.detail.information.label' | translate }} -

-
-
- - {{ 'common.product.detail.information.value.implementedBy' | translate }} - - - Logo Vendor - -
-
-
- - {{ 'common.product.detail.information.value.version' | translate }} - - - {{ displayVersion }} - -
- @if(productDetail.compatibility) { +@if (!(productDetail | missingProductInformationContent)) { +

+ {{ 'common.product.detail.information.label' | translate }} +

+ +
+
+ + {{ + 'common.product.detail.information.value.implementedBy' | translate + }} + + + Logo Vendor + +

- {{ 'common.product.detail.information.value.compatibility' | translate }} + {{ 'common.product.detail.information.value.version' | translate }} - {{ productDetail.compatibility }} + {{ displayVersion }}
- } -
-
- - {{ 'common.product.detail.information.value.cost' | translate }} - - {{ productDetail.cost }} -
-
-
- - {{ 'common.product.detail.information.value.language' | translate }} - - {{ productDetail.language }} -
- @if (externalDocumentLink !== '') { -
-
- - {{ 'common.product.detail.information.value.documentation' | translate }} - - - {{ displayExternalDocName ?? 'common.product.detail.information.value.defaultDocName' | translate }} - -
- } -
-
- - {{ 'common.product.detail.type' | translate }} - - {{ productDetail.type }} -
-
-
- - {{ 'common.product.detail.information.value.tag' | translate }} - - - {{ productDetail.tags ? productDetail.tags!.join(', ') : '' }} - -
- @if(productDetail.sourceUrl) { + @if (productDetail.compatibility) { +
+
+ + {{ + 'common.product.detail.information.value.compatibility' | translate + }} + + + {{ productDetail.compatibility }} + +
+ }
- {{ 'common.product.detail.information.value.source' | translate }} + {{ 'common.product.detail.information.value.cost' | translate }} - - - github.com + {{ productDetail.cost }} +
+
+
+ + {{ 'common.product.detail.information.value.language' | translate }} + + {{ productDetail.language }} +
+ @if (externalDocumentLink !== '') { +
+
+ } +
+
+ + {{ 'common.product.detail.type' | translate }} + {{ productDetail.type }}
- } - @if(productDetail.statusBadgeUrl) {
- {{ 'common.product.detail.information.value.status' | translate }} + {{ 'common.product.detail.information.value.tag' | translate }} + + + {{ productDetail.tags ? productDetail.tags!.join(', ') : '' }} + +
+ @if (productDetail.sourceUrl) { +
+
+ + {{ 'common.product.detail.information.value.source' | translate }} + + + + github.com + + +
+ } + @if (productDetail.statusBadgeUrl) { +
+
+ + {{ 'common.product.detail.information.value.status' | translate }} + + +
+ } +
+
+ + {{ + 'common.product.detail.information.value.moreInformation' | translate + }} + + + + {{ 'common.product.detail.information.value.contactUs' | translate }} + -
- } -
-
- - {{ - 'common.product.detail.information.value.moreInformation' | translate - }} - - - - {{ 'common.product.detail.information.value.contactUs' | translate }} - -
-
+} diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts index 9c102cfae..8b4c674ab 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts @@ -7,13 +7,14 @@ import { ProductDetailService } from '../product-detail.service'; import { VERSION } from '../../../../shared/constants/common.constant'; import { LoadingService } from '../../../../core/services/loading/loading.service'; import { ThemeService } from '../../../../core/services/theme/theme.service'; +import { MissingProductInformationContentPipe } from '../../../../shared/pipes/missing-product-Information-content-pipe'; const SELECTED_VERSION = 'selectedVersion'; const PRODUCT_DETAIL = 'productDetail'; @Component({ selector: 'app-product-detail-information-tab', standalone: true, - imports: [CommonModule, TranslateModule], + imports: [CommonModule, TranslateModule, MissingProductInformationContentPipe], templateUrl: './product-detail-information-tab.component.html', styleUrl: './product-detail-information-tab.component.scss' }) 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 d1b6f090d..5d290ea30 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 @@ -47,7 +47,7 @@ @if (isDropDownDisplayed()) {
-
+
} @@ -154,4 +155,6 @@ {{ 'common.product.detail.contactUs.label' | translate }} } + @default { + } } 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 db54c4926..a74051fb1 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 @@ -90,10 +90,15 @@ .btn__install { border: 0px; } + .primary-color { color: var(--ivy-primary-bg); } +.overlay-background{ + background: rgba(0, 0, 0, 0.32); +} + ::ng-deep .item-bar { height: 44px; padding: 15px; 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 129825a0f..5407ad999 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 @@ -38,6 +38,7 @@ }}

+
+
@if (displayedTabsSignal().length > 0) { @@ -180,6 +182,7 @@

}

+

+
@if (productDetail() && productFeedbackService.totalElements() > 0) { 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 6daf53420..4450bd027 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 @@ -29,7 +29,6 @@ import { ItemDropdown } from '../../../shared/models/item-dropdown.model'; import { ProductDetail } from '../../../shared/models/product-detail.model'; import { ProductModuleContent } from '../../../shared/models/product-module-content.model'; import { ProductTypeIconPipe } from '../../../shared/pipes/icon.pipe'; -import { MissingReadmeContentPipe } from '../../../shared/pipes/missing-readme-content.pipe'; import { MultilingualismPipe } from '../../../shared/pipes/multilingualism.pipe'; import { ProductTypePipe } from '../../../shared/pipes/product-type.pipe'; import { AppModalService } from '../../../shared/services/app-modal.service'; @@ -78,7 +77,6 @@ const DEFAULT_ACTIVE_TAB = 'description'; ProductDetailFeedbackComponent, ProductInstallationCountActionComponent, ProductTypeIconPipe, - MissingReadmeContentPipe, CommonDropdownComponent, NgOptimizedImage ], diff --git a/marketplace-ui/src/app/modules/product/product.service.ts b/marketplace-ui/src/app/modules/product/product.service.ts index 22ee095b1..2be41d84a 100644 --- a/marketplace-ui/src/app/modules/product/product.service.ts +++ b/marketplace-ui/src/app/modules/product/product.service.ts @@ -1,6 +1,6 @@ import { HttpClient, HttpContext, HttpParams } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { Observable } from 'rxjs'; +import { delay, Observable } from 'rxjs'; import { LoadingService } from '../../core/services/loading/loading.service'; import { RequestParam } from '../../shared/enums/request-param'; import { ProductApiResponse } from '../../shared/models/apis/product-response.model'; @@ -63,7 +63,7 @@ export class ProductService { ): Observable { return this.httpClient.get( `${API_URI.PRODUCT_DETAILS}/${productId}?isShowDevVersion=${isShowDevVersion}` - ); + ).pipe(delay(5000)); } sendRequestToProductDetailVersionAPI( diff --git a/marketplace-ui/src/app/shared/pipes/missing-product-Information-content-pipe.ts b/marketplace-ui/src/app/shared/pipes/missing-product-Information-content-pipe.ts new file mode 100644 index 000000000..f69a693c9 --- /dev/null +++ b/marketplace-ui/src/app/shared/pipes/missing-product-Information-content-pipe.ts @@ -0,0 +1,14 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { ProductDetail } from "../models/product-detail.model"; + +@Pipe({ + standalone: true, + name: 'missingProductInformationContent' +}) +export class MissingProductInformationContentPipe + implements PipeTransform +{ + transform(productDetail: ProductDetail): boolean { + return !productDetail || Object.keys(productDetail).length === 0; + } +} \ No newline at end of file diff --git a/marketplace-ui/src/app/shared/pipes/missing-readme-content.pipe.ts b/marketplace-ui/src/app/shared/pipes/missing-readme-content.pipe.ts deleted file mode 100644 index 72f8d166d..000000000 --- a/marketplace-ui/src/app/shared/pipes/missing-readme-content.pipe.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; -import { ProductModuleContent } from "../models/product-module-content.model"; - -@Pipe({ - standalone: true, - name: 'missingReadmeContent' -}) -export class MissingReadmeContentPipe implements PipeTransform { - transform(productModuleContent: ProductModuleContent): boolean { - return ( - !productModuleContent || Object.keys(productModuleContent).length === 0 - ); - } -} \ No newline at end of file diff --git a/marketplace-ui/src/app/shared/pipes/product-type.pipe.ts b/marketplace-ui/src/app/shared/pipes/product-type.pipe.ts index 7c922ba25..f1a573d04 100644 --- a/marketplace-ui/src/app/shared/pipes/product-type.pipe.ts +++ b/marketplace-ui/src/app/shared/pipes/product-type.pipe.ts @@ -6,6 +6,6 @@ import { Pipe, PipeTransform } from '@angular/core'; }) export class ProductTypePipe implements PipeTransform { transform(type: string, _args?: []): string { - return `common.filter.value.${type}`; + return type ? `common.filter.value.${type}` : ''; } } From 73fc859b57dd6c90a330034d845175e42bcd9da5 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Wed, 11 Dec 2024 18:53:36 +0700 Subject: [PATCH 05/26] so tired --- .../app/core/interceptors/api.interceptor.ts | 13 +----- .../core/services/loading/loading.service.ts | 32 ++++++++++--- ...roduct-detail-information-tab.component.ts | 46 +++++++++++++++---- .../product-detail/product-detail.service.ts | 2 + .../modules/product/product.component.html | 6 +-- .../app/modules/product/product.component.ts | 2 + .../app/modules/product/product.service.ts | 8 +++- .../loading-spinner.component.html | 10 ++-- .../loading-spinner.component.ts | 12 ++--- .../app/shared/enums/loading-component-id.ts | 7 +++ 10 files changed, 94 insertions(+), 44 deletions(-) create mode 100644 marketplace-ui/src/app/shared/enums/loading-component-id.ts diff --git a/marketplace-ui/src/app/core/interceptors/api.interceptor.ts b/marketplace-ui/src/app/core/interceptors/api.interceptor.ts index a9447f370..9b9c13583 100644 --- a/marketplace-ui/src/app/core/interceptors/api.interceptor.ts +++ b/marketplace-ui/src/app/core/interceptors/api.interceptor.ts @@ -4,9 +4,8 @@ import { HttpInterceptorFn } from '@angular/common/http'; import { environment } from '../../../environments/environment'; -import { LoadingService } from '../services/loading/loading.service'; import { inject } from '@angular/core'; -import { catchError, EMPTY, finalize } from 'rxjs'; +import { catchError, EMPTY } from 'rxjs'; import { Router } from '@angular/router'; import { ERROR_CODES, ERROR_PAGE_PATH } from '../../shared/constants/common.constant'; @@ -25,7 +24,6 @@ export const ForwardingError = new HttpContextToken(() => false); export const apiInterceptor: HttpInterceptorFn = (req, next) => { const router = inject(Router); - const loadingService = inject(LoadingService); if (req.url.includes('i18n')) { return next(req); @@ -41,10 +39,6 @@ export const apiInterceptor: HttpInterceptorFn = (req, next) => { headers: addIvyHeaders(req.headers) }); - if (!req.context.get(SkipLoading)) { - loadingService.show(); - } - if (req.context.get(ForwardingError)) { return next(cloneReq); } @@ -57,11 +51,6 @@ export const apiInterceptor: HttpInterceptorFn = (req, next) => { router.navigate([ERROR_PAGE_PATH]); } return EMPTY; - }), - finalize(() => { - if (!req.context.get(SkipLoading)) { - loadingService.hide(); - } }) ); }; diff --git a/marketplace-ui/src/app/core/services/loading/loading.service.ts b/marketplace-ui/src/app/core/services/loading/loading.service.ts index f3981781c..803ab18ca 100644 --- a/marketplace-ui/src/app/core/services/loading/loading.service.ts +++ b/marketplace-ui/src/app/core/services/loading/loading.service.ts @@ -1,17 +1,35 @@ -import { computed, Injectable, signal } from '@angular/core'; +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class LoadingService { - private readonly isShow = signal(false); - isLoading = computed(() => this.isShow()); + private readonly loadingSubject = new BehaviorSubject<{ + [key: string]: boolean; + }>({}); + loading$ = this.loadingSubject.asObservable; - show() { - this.isShow.set(true); + private setLoading(componentIds: string[], isLoading: boolean): void { + const currentState = this.loadingSubject.value; + + // Update the loading state for each component ID + componentIds.forEach(id => { + currentState[id] = isLoading; + }); + + this.loadingSubject.next({ ...currentState }); + } + + showLoading(...componentIds: string[]): void { + this.setLoading(componentIds, true); + } + + hideLoading(...componentIds: string[]) { + this.setLoading(componentIds, false); } - hide() { - this.isShow.set(false); + isLoading(compnentId: string): boolean { + return this.loadingSubject.value[compnentId] || false; } } diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts index 8b4c674ab..455b867b4 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts @@ -1,5 +1,12 @@ import { CommonModule } from '@angular/common'; -import { Component, inject, Input, OnChanges, SimpleChange, SimpleChanges } from '@angular/core'; +import { + Component, + inject, + Input, + OnChanges, + SimpleChange, + SimpleChanges +} from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; import { ProductDetail } from '../../../../shared/models/product-detail.model'; import { LanguageService } from '../../../../core/services/language/language.service'; @@ -7,14 +14,20 @@ import { ProductDetailService } from '../product-detail.service'; import { VERSION } from '../../../../shared/constants/common.constant'; import { LoadingService } from '../../../../core/services/loading/loading.service'; import { ThemeService } from '../../../../core/services/theme/theme.service'; +import { finalize } from 'rxjs'; import { MissingProductInformationContentPipe } from '../../../../shared/pipes/missing-product-Information-content-pipe'; +import { LoadingComponentId } from '../../../../shared/enums/loading-component-id'; const SELECTED_VERSION = 'selectedVersion'; const PRODUCT_DETAIL = 'productDetail'; @Component({ selector: 'app-product-detail-information-tab', standalone: true, - imports: [CommonModule, TranslateModule, MissingProductInformationContentPipe], + imports: [ + CommonModule, + TranslateModule, + MissingProductInformationContentPipe + ], templateUrl: './product-detail-information-tab.component.html', styleUrl: './product-detail-information-tab.component.scss' }) @@ -23,6 +36,7 @@ export class ProductDetailInformationTabComponent implements OnChanges { productDetail!: ProductDetail; @Input() selectedVersion!: string; + protected LoadingComponentId = LoadingComponentId; externalDocumentLink = ''; displayVersion = ''; displayExternalDocName: string | null = ''; @@ -34,7 +48,11 @@ export class ProductDetailInformationTabComponent implements OnChanges { ngOnChanges(changes: SimpleChanges): void { let version = ''; const changedSelectedVersion = changes[SELECTED_VERSION]; - if (changedSelectedVersion && changedSelectedVersion.currentValue === changedSelectedVersion.previousValue) { + if ( + changedSelectedVersion && + changedSelectedVersion.currentValue === + changedSelectedVersion.previousValue + ) { return; } const changedProduct = changes[PRODUCT_DETAIL]; @@ -48,7 +66,18 @@ export class ProductDetailInformationTabComponent implements OnChanges { return; } - this.productDetailService.getExternalDocumentForProductByVersion(this.productDetail.id, this.extractVersionValue(version)) + this.productDetailService + .getExternalDocumentForProductByVersion( + this.productDetail.id, + this.extractVersionValue(version) + ) + .pipe( + finalize(() => { + this.loadingService.hideLoading( + LoadingComponentId.DETAIL_INFORMATION_TAB + ); + }) + ) .subscribe({ next: response => { if (response) { @@ -57,11 +86,9 @@ export class ProductDetailInformationTabComponent implements OnChanges { } else { this.resetValues(); } - this.loadingService.hide(); }, error: () => { this.resetValues(); - this.loadingService.hide(); } }); this.displayVersion = this.extractVersionValue(this.selectedVersion); @@ -79,9 +106,10 @@ export class ProductDetailInformationTabComponent implements OnChanges { // To ensure the function always returns a boolean, you can explicitly coerce the result into a boolean using the !! operator or default it to false // Adding !! in case of changedProduct is undefined, it will return false instead of returning undefined isProductChanged(changedProduct: SimpleChange) { - return !!(changedProduct?.previousValue && + return !!( + changedProduct?.previousValue && Object.keys(changedProduct.previousValue).length > 0 && - changedProduct.currentValue !== changedProduct.previousValue); + changedProduct.currentValue !== changedProduct.previousValue + ); } - } diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail.service.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail.service.ts index 6862eb0d8..73e8a7e84 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail.service.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail.service.ts @@ -6,6 +6,7 @@ import { Observable } from 'rxjs'; import { API_URI } from '../../../shared/constants/api.constant'; import { ForwardingError } from '../../../core/interceptors/api.interceptor'; import { ExternalDocument } from '../../../shared/models/external-document.model'; +import { LoadingComponentId } from '../../../shared/enums/loading-component-id'; @Injectable({ providedIn: 'root' @@ -20,6 +21,7 @@ export class ProductDetailService { noFeedbackLabel: WritableSignal = signal(''); getExternalDocumentForProductByVersion(productId: string, version: string): Observable { + this.loadingService.showLoading(LoadingComponentId.DETAIL_INFORMATION_TAB); return this.httpClient.get( `${API_URI.EXTERNAL_DOCUMENT}/${productId}/${version}`, { context: new HttpContext().set(ForwardingError, true)} ); diff --git a/marketplace-ui/src/app/modules/product/product.component.html b/marketplace-ui/src/app/modules/product/product.component.html index 21a5e6c93..a0ea7844c 100644 --- a/marketplace-ui/src/app/modules/product/product.component.html +++ b/marketplace-ui/src/app/modules/product/product.component.html @@ -47,10 +47,6 @@

} - - @if (loadingService.isLoading()) { - - } - +
diff --git a/marketplace-ui/src/app/modules/product/product.component.ts b/marketplace-ui/src/app/modules/product/product.component.ts index f9de02844..0306eb194 100644 --- a/marketplace-ui/src/app/modules/product/product.component.ts +++ b/marketplace-ui/src/app/modules/product/product.component.ts @@ -36,6 +36,7 @@ import { import { ItemDropdown } from '../../shared/models/item-dropdown.model'; import { LoadingSpinnerComponent } from '../../shared/components/loading-spinner/loading-spinner.component'; import { LoadingService } from '../../core/services/loading/loading.service'; +import { LoadingComponentId } from '../../shared/enums/loading-component-id'; const SEARCH_DEBOUNCE_TIME = 500; @@ -55,6 +56,7 @@ const SEARCH_DEBOUNCE_TIME = 500; styleUrl: './product.component.scss' }) export class ProductComponent implements AfterViewInit, OnDestroy { + protected LoadingComponentId = LoadingComponentId; products: WritableSignal = signal([]); productDetail!: ProductDetail; subscriptions: Subscription[] = []; diff --git a/marketplace-ui/src/app/modules/product/product.service.ts b/marketplace-ui/src/app/modules/product/product.service.ts index 2be41d84a..01c7f8699 100644 --- a/marketplace-ui/src/app/modules/product/product.service.ts +++ b/marketplace-ui/src/app/modules/product/product.service.ts @@ -1,6 +1,6 @@ import { HttpClient, HttpContext, HttpParams } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { delay, Observable } from 'rxjs'; +import { delay, finalize, Observable } from 'rxjs'; import { LoadingService } from '../../core/services/loading/loading.service'; import { RequestParam } from '../../shared/enums/request-param'; import { ProductApiResponse } from '../../shared/models/apis/product-response.model'; @@ -10,6 +10,7 @@ import { VersionData } from '../../shared/models/vesion-artifact.model'; import { SkipLoading } from '../../core/interceptors/api.interceptor'; import { VersionAndUrl } from '../../shared/models/version-and-url'; import { API_URI } from '../../shared/constants/api.constant'; +import { LoadingComponentId } from '../../shared/enums/loading-component-id'; @Injectable() export class ProductService { @@ -17,6 +18,7 @@ export class ProductService { loadingService = inject(LoadingService); findProductsByCriteria(criteria: Criteria): Observable { + this.loadingService.showLoading(LoadingComponentId.LANDING_PAGE); let requestParams = new HttpParams(); let requestURL = API_URI.PRODUCT; if (criteria.nextPageHref) { @@ -36,7 +38,9 @@ export class ProductService { } return this.httpClient.get(requestURL, { params: requestParams - }); + }).pipe(finalize(()=> { + this.loadingService.hideLoading(LoadingComponentId.LANDING_PAGE); + })); } getProductDetailsWithVersion( diff --git a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.html b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.html index 21fe9e4e9..993ceab34 100644 --- a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.html +++ b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.html @@ -1,5 +1,9 @@ -
-
-
+@if (isLoading()) { +
+
+
+
+
+} diff --git a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts index 74a54a837..085e41ba2 100644 --- a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts +++ b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts @@ -1,15 +1,15 @@ -import { Component } from '@angular/core'; -import { NgClass } from '@angular/common'; +import { Component, computed, inject, Input } from '@angular/core'; +import { LoadingService } from '../../../core/services/loading/loading.service'; @Component({ selector: 'app-loading-spinner', standalone: true, - imports: [ - NgClass - ], templateUrl: './loading-spinner.component.html', styleUrl: './loading-spinner.component.scss' }) export class LoadingSpinnerComponent { - spinners = Array(5).fill(null); + @Input() key: string = ''; + @Input() containerClasses: string = ''; + loadingService = inject(LoadingService); + isLoading = computed(() => this.loadingService.loadingSubject.value[this.key]); } diff --git a/marketplace-ui/src/app/shared/enums/loading-component-id.ts b/marketplace-ui/src/app/shared/enums/loading-component-id.ts new file mode 100644 index 000000000..a30b622a0 --- /dev/null +++ b/marketplace-ui/src/app/shared/enums/loading-component-id.ts @@ -0,0 +1,7 @@ +export enum LoadingComponentId { + LANDING_PAGE = 'landing-page', + PRODUCT_DETAIL = 'product-detial', + PRODUCT_VERSION = 'product-version', + DETAIL_PAGE = 'detail-page', + DETAIL_INFORMATION_TAB = 'detail-information-tab' +} From 37e57b5b8fa11f0d7cc87150e1d745eec4d7f3c0 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Wed, 11 Dec 2024 19:11:14 +0700 Subject: [PATCH 06/26] update service --- .../core/services/loading/loading.service.ts | 33 +++++++------------ .../loading-spinner.component.ts | 2 +- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/marketplace-ui/src/app/core/services/loading/loading.service.ts b/marketplace-ui/src/app/core/services/loading/loading.service.ts index 803ab18ca..4d5829825 100644 --- a/marketplace-ui/src/app/core/services/loading/loading.service.ts +++ b/marketplace-ui/src/app/core/services/loading/loading.service.ts @@ -1,35 +1,24 @@ -import { Injectable } from '@angular/core'; -import { BehaviorSubject } from 'rxjs'; +import { Injectable, signal } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class LoadingService { - private readonly loadingSubject = new BehaviorSubject<{ - [key: string]: boolean; - }>({}); - loading$ = this.loadingSubject.asObservable; + loadingStates = signal<{ [key: string]: boolean }>({}); - private setLoading(componentIds: string[], isLoading: boolean): void { - const currentState = this.loadingSubject.value; - - // Update the loading state for each component ID - componentIds.forEach(id => { - currentState[id] = isLoading; + private setLoading(componentId: string, isLoading: boolean): void { + this.loadingStates.update(states => { + const updatedStates = { ...states }; + updatedStates[componentId] = isLoading; + return updatedStates; }); - - this.loadingSubject.next({ ...currentState }); - } - - showLoading(...componentIds: string[]): void { - this.setLoading(componentIds, true); } - hideLoading(...componentIds: string[]) { - this.setLoading(componentIds, false); + showLoading(componentId: string): void { + this.setLoading(componentId, true); } - isLoading(compnentId: string): boolean { - return this.loadingSubject.value[compnentId] || false; + hideLoading(componentId: string) { + this.setLoading(componentId, false); } } diff --git a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts index 085e41ba2..a291897e3 100644 --- a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts +++ b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts @@ -11,5 +11,5 @@ export class LoadingSpinnerComponent { @Input() key: string = ''; @Input() containerClasses: string = ''; loadingService = inject(LoadingService); - isLoading = computed(() => this.loadingService.loadingSubject.value[this.key]); + isLoading = computed(() => this.loadingService.loadingStates()[this.key]); } From e285683243ba47fa6e82b419539405620529736b Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Wed, 11 Dec 2024 20:00:51 +0700 Subject: [PATCH 07/26] updatre service --- ...duct-detail-information-tab.component.html | 2 +- ...roduct-detail-information-tab.component.ts | 6 +- .../product-detail.component.html | 392 +++++++++--------- .../product-detail.component.ts | 88 ++-- .../app/modules/product/product.service.ts | 1 + ...t-pipe.ts => empty-product-detail.pipe.ts} | 5 +- 6 files changed, 268 insertions(+), 226 deletions(-) rename marketplace-ui/src/app/shared/pipes/{missing-product-Information-content-pipe.ts => empty-product-detail.pipe.ts} (67%) diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.html b/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.html index 359cb46c8..9aca7f03d 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.html +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.html @@ -1,4 +1,4 @@ -@if (!(productDetail | missingProductInformationContent)) { +@if (!(productDetail | emptyProductDetailPipe)) {

diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts index 455b867b4..8607bf93c 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts @@ -15,7 +15,7 @@ import { VERSION } from '../../../../shared/constants/common.constant'; import { LoadingService } from '../../../../core/services/loading/loading.service'; import { ThemeService } from '../../../../core/services/theme/theme.service'; import { finalize } from 'rxjs'; -import { MissingProductInformationContentPipe } from '../../../../shared/pipes/missing-product-Information-content-pipe'; +import { EmptyProductDetailPipe } from '../../../../shared/pipes/empty-product-detail.pipe'; import { LoadingComponentId } from '../../../../shared/enums/loading-component-id'; const SELECTED_VERSION = 'selectedVersion'; @@ -26,8 +26,8 @@ const PRODUCT_DETAIL = 'productDetail'; imports: [ CommonModule, TranslateModule, - MissingProductInformationContentPipe - ], + EmptyProductDetailPipe +], templateUrl: './product-detail-information-tab.component.html', styleUrl: './product-detail-information-tab.component.scss' }) 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 5407ad999..950758b49 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 @@ -1,160 +1,175 @@
-
-
-

- @if(productDetailActionType() !== ProductDetailActionType.CUSTOM_SOLUTION) { - - } +
-

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

-

- -

-

- {{ productDetail().type | productType | translate }} -

+ class="d-flex flex-row align-items-start module-gap default-cursor"> +
+ +
+ @if ( + productDetailActionType() !== + ProductDetailActionType.CUSTOM_SOLUTION + ) { + + } +
+

+ {{ 'common.product.detail.type' | translate }} +

+

+ +

+

+ {{ productDetail().type | productType | translate }} +

+
+ + - - - -
- -
- @if (displayedTabsSignal().length > 0) { -
- -
- -

- @if ( - productDetailActionType() !== - ProductDetailActionType.CUSTOM_SOLUTION - ) { - - } +
-

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

-

- -

-

- {{ productDetail().type | productType | translate }} -

+ class="d-flex flex-row align-items-start module-gap default-cursor"> +
+ +
+ @if ( + productDetailActionType() !== + ProductDetailActionType.CUSTOM_SOLUTION + ) { + + } +
+

+ {{ 'common.product.detail.type' | translate }} +

+

+ +

+

+ {{ productDetail().type | productType | translate }} +

+
+ + - - - + }
@@ -215,36 +215,37 @@


-
- @if (productDetail() && productFeedbackService.totalElements() > 0) { - - } @else { -
-
- Message Star -

- - {{ productDetailService.noFeedbackLabel() | translate }} - -
- - {{ 'common.feedback.noFeedbackSecondLabel' | translate }} - -

- + @if (!(productDetail() | emptyProductDetailPipe)) { +
+ @if (productDetail() && productFeedbackService.totalElements() > 0) { + + } @else { +
+
+ Message Star +

+ + {{ productDetailService.noFeedbackLabel() | translate }} + +
+ + {{ 'common.feedback.noFeedbackSecondLabel' | translate }} + +

+ +
-
- } -
- } + } +
+ }
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 b001caf29..e2d9dc1e0 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 @@ -53,6 +53,7 @@ import { API_URI } from '../../../shared/constants/api.constant'; import { EmptyProductDetailPipe } from "../../../shared/pipes/empty-product-detail.pipe"; import { LoadingSpinnerComponent } from "../../../shared/components/loading-spinner/loading-spinner.component"; import { LoadingComponentId } from '../../../shared/enums/loading-component-id'; +import { LoadingService } from '../../../core/services/loading/loading.service'; export interface DetailTab { activeClass: string; @@ -103,13 +104,16 @@ export class ProductDetailComponent { elementRef = inject(ElementRef); cookieService = inject(CookieService); routingQueryParamService = inject(RoutingQueryParamService); + loadingService = inject(LoadingService); + protected LoadingComponentId = LoadingComponentId; + protected ProductDetailActionType = ProductDetailActionType; + resizeObserver: ResizeObserver; productDetail: WritableSignal = signal({} as ProductDetail); productModuleContent: WritableSignal = signal( {} as ProductModuleContent ); - protected ProductDetailActionType = ProductDetailActionType; productDetailActionType = signal(ProductDetailActionType.STANDARD); detailTabs = PRODUCT_DETAIL_TABS; activeTab = ''; @@ -125,6 +129,7 @@ export class ProductDetailComponent { isMobileMode = signal(false); installationCount = 0; logoUrl = DEFAULT_IMAGE_URL; + @HostListener('window:popstate', ['$event']) onPopState() { this.activeTab = window.location.hash.split('#tab-')[1]; @@ -150,39 +155,63 @@ export class ProductDetailComponent { const productId = this.route.snapshot.params[ROUTER.ID]; this.productDetailService.productId.set(productId); if (productId) { - const isShowDevVersion = CommonUtils.getCookieValue( - this.cookieService, - SHOW_DEV_VERSION, - false - ); - this.getProductById(productId, isShowDevVersion).subscribe( - productDetail => { - this.productDetail.set(productDetail); - this.productModuleContent.set(productDetail.productModuleContent); - this.metaProductJsonUrl = productDetail.metaProductJsonUrl; - this.productDetailService.productNames.set(productDetail.names); - this.productDetailService.productLogoUrl.set(productDetail.logoUrl); - this.installationCount = productDetail.installationCount; - this.handleProductContentVersion(); - this.updateProductDetailActionType(productDetail); - this.logoUrl = productDetail.logoUrl; - this.updateWebBrowserTitle(); - const ratingLabels = RATING_LABELS_BY_TYPE.find( - button => button.type === productDetail.type - ); - if (ratingLabels !== undefined) { - this.productDetailService.ratingBtnLabel.set(ratingLabels.btnLabel); - this.productDetailService.noFeedbackLabel.set( - ratingLabels.noFeedbackLabel - ); - } - } - ); - + this.loadingService.showLoading(LoadingComponentId.DETAIL_PAGE); + this.getProductContent(productId); this.productFeedbackService.initFeedbacks(); this.productStarRatingService.fetchData(); + this.updateDropdownSelection(); + this.checkMediaSize(); + this.getUserFeedBack(); } - this.updateDropdownSelection(); + } + + ngAfterViewInit(): void { + // this.loadingService.hideLoading(LoadingComponentId.DETAIL_PAGE); + } + + getUserFeedBack() { + this.productFeedbackService.findProductFeedbackOfUser().subscribe(() => { + this.route.queryParams.subscribe(params => { + this.showPopup = params['showPopup'] === 'true'; + if (this.showPopup && this.authService.getToken()) { + this.appModalService + .openAddFeedbackDialog() + .then(() => this.removeQueryParam()) + .catch(() => this.removeQueryParam()) + } + }); + }); + } + + getProductContent(productId: string) { + const isShowDevVersion = CommonUtils.getCookieValue( + this.cookieService, + SHOW_DEV_VERSION, + false + ); + this.getProductById(productId, isShowDevVersion).subscribe( + productDetail => { + this.productDetail.set(productDetail); + this.productModuleContent.set(productDetail.productModuleContent); + this.metaProductJsonUrl = productDetail.metaProductJsonUrl; + this.productDetailService.productNames.set(productDetail.names); + this.productDetailService.productLogoUrl.set(productDetail.logoUrl); + this.installationCount = productDetail.installationCount; + this.handleProductContentVersion(); + this.updateProductDetailActionType(productDetail); + this.logoUrl = productDetail.logoUrl; + this.updateWebBrowserTitle(); + const ratingLabels = RATING_LABELS_BY_TYPE.find( + button => button.type === productDetail.type + ); + if (ratingLabels !== undefined) { + this.productDetailService.ratingBtnLabel.set(ratingLabels.btnLabel); + this.productDetailService.noFeedbackLabel.set( + ratingLabels.noFeedbackLabel + ); + } + } + ); } onClickingBackToHomepageButton() { @@ -240,21 +269,6 @@ export class ProductDetailComponent { ); } - ngAfterViewInit(): void { - this.checkMediaSize(); - this.productFeedbackService.findProductFeedbackOfUser().subscribe(() => { - this.route.queryParams.subscribe(params => { - this.showPopup = params['showPopup'] === 'true'; - if (this.showPopup && this.authService.getToken()) { - this.appModalService - .openAddFeedbackDialog() - .then(() => this.removeQueryParam()) - .catch(() => this.removeQueryParam()); - } - }); - }); - } - getContent(value: string): boolean { const content = this.productModuleContent(); diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail.service.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail.service.ts index 73e8a7e84..6862eb0d8 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail.service.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail.service.ts @@ -6,7 +6,6 @@ import { Observable } from 'rxjs'; import { API_URI } from '../../../shared/constants/api.constant'; import { ForwardingError } from '../../../core/interceptors/api.interceptor'; import { ExternalDocument } from '../../../shared/models/external-document.model'; -import { LoadingComponentId } from '../../../shared/enums/loading-component-id'; @Injectable({ providedIn: 'root' @@ -21,7 +20,6 @@ export class ProductDetailService { noFeedbackLabel: WritableSignal = signal(''); getExternalDocumentForProductByVersion(productId: string, version: string): Observable { - this.loadingService.showLoading(LoadingComponentId.DETAIL_INFORMATION_TAB); return this.httpClient.get( `${API_URI.EXTERNAL_DOCUMENT}/${productId}/${version}`, { context: new HttpContext().set(ForwardingError, true)} ); diff --git a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.html b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.html index 993ceab34..1f2484edc 100644 --- a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.html +++ b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.html @@ -1,5 +1,5 @@ -@if (isLoading()) { -
+@if (this.loadingService.isLoading(this.key)) { +
diff --git a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts index a291897e3..e3c11673a 100644 --- a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts +++ b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts @@ -11,5 +11,4 @@ export class LoadingSpinnerComponent { @Input() key: string = ''; @Input() containerClasses: string = ''; loadingService = inject(LoadingService); - isLoading = computed(() => this.loadingService.loadingStates()[this.key]); } diff --git a/marketplace-ui/src/app/shared/enums/loading-component-id.ts b/marketplace-ui/src/app/shared/enums/loading-component-id.ts index a30b622a0..dedca8eb2 100644 --- a/marketplace-ui/src/app/shared/enums/loading-component-id.ts +++ b/marketplace-ui/src/app/shared/enums/loading-component-id.ts @@ -1,7 +1,6 @@ export enum LoadingComponentId { LANDING_PAGE = 'landing-page', - PRODUCT_DETAIL = 'product-detial', + PRODUCT_DETAIL_INFORMATION = 'product-detail-information', PRODUCT_VERSION = 'product-version', - DETAIL_PAGE = 'detail-page', - DETAIL_INFORMATION_TAB = 'detail-information-tab' + DETAIL_PAGE = 'detail-page' } diff --git a/marketplace-ui/src/app/shared/pipes/empty-product-detail.pipe.ts b/marketplace-ui/src/app/shared/pipes/empty-product-detail.pipe.ts index 9c842b083..d83f056b7 100644 --- a/marketplace-ui/src/app/shared/pipes/empty-product-detail.pipe.ts +++ b/marketplace-ui/src/app/shared/pipes/empty-product-detail.pipe.ts @@ -9,7 +9,6 @@ export class EmptyProductDetailPipe implements PipeTransform { transform(productDetail: ProductDetail): boolean { - console.log(!productDetail || Object.keys(productDetail).length === 0); return !productDetail || Object.keys(productDetail).length === 0; } } \ No newline at end of file From 9558ba7b698ae09de7be369489d384955985c803 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Thu, 12 Dec 2024 17:48:41 +0700 Subject: [PATCH 11/26] update service --- .../product-detail-version-action.component.html | 9 +-------- 1 file changed, 1 insertion(+), 8 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 48918e64a..579e01bbe 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 @@ -132,14 +132,7 @@ const selectedItemElement = document.querySelector('.install-designer-dropdown'); if (selectedItemElement) { const metaDataJsonUrl = selectedItemElement.getAttribute('metaDataJsonUrl'); - try { - install(metaDataJsonUrl); - } catch (error) { - console.log("event"); - - console.log(error); - event.stopImmediatePropagation(); - } + install(metaDataJsonUrl); } } installInDesigner(event);" [ngClass]="themeService.isDarkMode() ? 'btn-light' : 'btn-primary'"> From 663972369a2625bc0a4d97dc79059256536be02f Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Thu, 12 Dec 2024 18:12:23 +0700 Subject: [PATCH 12/26] update service --- .../app/core/interceptors/api.interceptor.ts | 6 +- .../core/services/loading/loading.service.ts | 6 +- .../app/modules/product/product.service.ts | 56 ++++++++++++++----- .../loading-spinner.component.html | 2 +- .../loading-spinner.component.ts | 1 + 5 files changed, 49 insertions(+), 22 deletions(-) diff --git a/marketplace-ui/src/app/core/interceptors/api.interceptor.ts b/marketplace-ui/src/app/core/interceptors/api.interceptor.ts index 60c64106e..44cc28a43 100644 --- a/marketplace-ui/src/app/core/interceptors/api.interceptor.ts +++ b/marketplace-ui/src/app/core/interceptors/api.interceptor.ts @@ -29,7 +29,7 @@ export const ForwardingError = new HttpContextToken(() => false); /** LoadingComponentId: This option for show loading for component which match with id * @Example return httpClient.get('apiEndPoint', { context: new HttpContext().set(LoadingComponentId, "detail-page") }) */ -export const LoadingComponentId = new HttpContextToken(() => ''); +export const LoadingComponent = new HttpContextToken(() => ''); export const apiInterceptor: HttpInterceptorFn = (req, next) => { const router = inject(Router); @@ -51,7 +51,7 @@ export const apiInterceptor: HttpInterceptorFn = (req, next) => { }); if (!req.context.get(SkipLoading)) { - loadingService.showLoading(req.context.get(LoadingComponentId)); + loadingService.showLoading(req.context.get(LoadingComponent)); } if (req.context.get(ForwardingError)) { @@ -69,7 +69,7 @@ export const apiInterceptor: HttpInterceptorFn = (req, next) => { }), finalize(() => { if (!req.context.get(SkipLoading)) { - loadingService.hideLoading(req.context.get(LoadingComponentId)); + loadingService.hideLoading(req.context.get(LoadingComponent)); } }) ); diff --git a/marketplace-ui/src/app/core/services/loading/loading.service.ts b/marketplace-ui/src/app/core/services/loading/loading.service.ts index d15896407..4d5829825 100644 --- a/marketplace-ui/src/app/core/services/loading/loading.service.ts +++ b/marketplace-ui/src/app/core/services/loading/loading.service.ts @@ -1,4 +1,4 @@ -import { computed, Injectable, signal } from '@angular/core'; +import { Injectable, signal } from '@angular/core'; @Injectable({ providedIn: 'root' @@ -21,8 +21,4 @@ export class LoadingService { hideLoading(componentId: string) { this.setLoading(componentId, false); } - - isLoading(componentId: string) { - return computed(() => this.loadingStates()[componentId]); - } } diff --git a/marketplace-ui/src/app/modules/product/product.service.ts b/marketplace-ui/src/app/modules/product/product.service.ts index 3980d0f55..9a88fc1fc 100644 --- a/marketplace-ui/src/app/modules/product/product.service.ts +++ b/marketplace-ui/src/app/modules/product/product.service.ts @@ -1,13 +1,16 @@ import { HttpClient, HttpContext, HttpParams } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { delay, finalize, Observable } from 'rxjs'; +import { delay, Observable } from 'rxjs'; import { LoadingService } from '../../core/services/loading/loading.service'; import { RequestParam } from '../../shared/enums/request-param'; import { ProductApiResponse } from '../../shared/models/apis/product-response.model'; import { Criteria } from '../../shared/models/criteria.model'; import { ProductDetail } from '../../shared/models/product-detail.model'; import { VersionData } from '../../shared/models/vesion-artifact.model'; -import { SkipLoading } from '../../core/interceptors/api.interceptor'; +import { + SkipLoading, + LoadingComponent +} from '../../core/interceptors/api.interceptor'; import { VersionAndUrl } from '../../shared/models/version-and-url'; import { API_URI } from '../../shared/constants/api.constant'; import { LoadingComponentId } from '../../shared/enums/loading-component-id'; @@ -37,10 +40,12 @@ export class ProductService { ); } return this.httpClient.get(requestURL, { - params: requestParams - }).pipe(finalize(()=> { - this.loadingService.hideLoading(LoadingComponentId.LANDING_PAGE); - })); + params: requestParams, + context: new HttpContext().set( + LoadingComponent, + LoadingComponentId.LANDING_PAGE + ) + }); } getProductDetailsWithVersion( @@ -48,7 +53,13 @@ export class ProductService { version: string ): Observable { return this.httpClient.get( - `${API_URI.PRODUCT_DETAILS}/${productId}/${version}` + `${API_URI.PRODUCT_DETAILS}/${productId}/${version}`, + { + context: new HttpContext().set( + LoadingComponent, + LoadingComponentId.PRODUCT_DETAIL_INFORMATION + ) + } ); } @@ -57,7 +68,13 @@ export class ProductService { version: string ): Observable { return this.httpClient.get( - `${API_URI.PRODUCT_DETAILS}/${productId}/${version}/bestmatch` + `${API_URI.PRODUCT_DETAILS}/${productId}/${version}/bestmatch`, + { + context: new HttpContext().set( + LoadingComponent, + LoadingComponentId.DETAIL_PAGE + ) + } ); } @@ -66,9 +83,17 @@ export class ProductService { isShowDevVersion: boolean ): Observable { this.loadingService.showLoading(LoadingComponentId.DETAIL_PAGE); - return this.httpClient.get( - `${API_URI.PRODUCT_DETAILS}/${productId}?isShowDevVersion=${isShowDevVersion}` - ).pipe(delay(5000)); + return this.httpClient + .get( + `${API_URI.PRODUCT_DETAILS}/${productId}?isShowDevVersion=${isShowDevVersion}`, + { + context: new HttpContext().set( + LoadingComponent, + LoadingComponentId.PRODUCT_DETAIL_INFORMATION + ) + } + ) + .pipe(delay(5000)); } sendRequestToProductDetailVersionAPI( @@ -82,11 +107,16 @@ export class ProductService { .append('isShowDevVersion', showDevVersion); return this.httpClient.get(url, { params, - context: new HttpContext().set(SkipLoading, true) + context: new HttpContext() + .set(SkipLoading, true) + .set(LoadingComponent, LoadingComponentId.PRODUCT_DETAIL_INFORMATION) }); } - sendRequestToUpdateInstallationCount(productId: string, designerVersion: string) { + sendRequestToUpdateInstallationCount( + productId: string, + designerVersion: string + ) { const url = `${API_URI.PRODUCT_MARKETPLACE_DATA}/installation-count/${productId}`; const params = new HttpParams().append('designerVersion', designerVersion); return this.httpClient.put(url, null, { params }); diff --git a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.html b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.html index 1f2484edc..c20d6e262 100644 --- a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.html +++ b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.html @@ -1,4 +1,4 @@ -@if (this.loadingService.isLoading(this.key)) { +@if (isLoading()) {
diff --git a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts index e3c11673a..a291897e3 100644 --- a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts +++ b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts @@ -11,4 +11,5 @@ export class LoadingSpinnerComponent { @Input() key: string = ''; @Input() containerClasses: string = ''; loadingService = inject(LoadingService); + isLoading = computed(() => this.loadingService.loadingStates()[this.key]); } From ab6c7af47fa239114ca58f13be08c47574fe2882 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Thu, 12 Dec 2024 19:43:27 +0700 Subject: [PATCH 13/26] handle feedback --- .../core/services/loading/loading.service.ts | 5 ++ .../product-detail.component.html | 7 +- .../product-detail.component.ts | 72 ++++++++++--------- .../app/modules/product/product.service.ts | 3 +- 4 files changed, 49 insertions(+), 38 deletions(-) diff --git a/marketplace-ui/src/app/core/services/loading/loading.service.ts b/marketplace-ui/src/app/core/services/loading/loading.service.ts index 4d5829825..ea0a1fa11 100644 --- a/marketplace-ui/src/app/core/services/loading/loading.service.ts +++ b/marketplace-ui/src/app/core/services/loading/loading.service.ts @@ -5,6 +5,7 @@ import { Injectable, signal } from '@angular/core'; }) export class LoadingService { loadingStates = signal<{ [key: string]: boolean }>({}); + activeCallCount = signal(0); private setLoading(componentId: string, isLoading: boolean): void { this.loadingStates.update(states => { @@ -15,10 +16,14 @@ export class LoadingService { } showLoading(componentId: string): void { + this.activeCallCount.set(this.activeCallCount() + 1); this.setLoading(componentId, true); } hideLoading(componentId: string) { + if (this.activeCallCount() > 0) { + this.activeCallCount.set(this.activeCallCount() - 1); + } this.setLoading(componentId, false); } } 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 5cb45df2e..77d771cb5 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 @@ -1,8 +1,9 @@
-
- + [key]="LoadingComponentId.DETAIL_PAGE" />
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 e2d9dc1e0..7f5c76e57 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 @@ -13,7 +13,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; import { MarkdownModule, MarkdownService } from 'ngx-markdown'; -import { map, Observable } from 'rxjs'; +import { forkJoin, map, Observable } from 'rxjs'; import { AuthService } from '../../../auth/auth.service'; import { LanguageService } from '../../../core/services/language/language.service'; import { ThemeService } from '../../../core/services/theme/theme.service'; @@ -22,7 +22,9 @@ import { DEFAULT_IMAGE_URL, DEFAULT_VENDOR_IMAGE, DEFAULT_VENDOR_IMAGE_BLACK, - PRODUCT_DETAIL_TABS, RATING_LABELS_BY_TYPE, SHOW_DEV_VERSION, + PRODUCT_DETAIL_TABS, + RATING_LABELS_BY_TYPE, + SHOW_DEV_VERSION, VERSION } from '../../../shared/constants/common.constant'; import { ItemDropdown } from '../../../shared/models/item-dropdown.model'; @@ -50,8 +52,8 @@ import { CookieService } from 'ngx-cookie-service'; import { ROUTER } from '../../../shared/constants/router.constant'; import { Title } from '@angular/platform-browser'; import { API_URI } from '../../../shared/constants/api.constant'; -import { EmptyProductDetailPipe } from "../../../shared/pipes/empty-product-detail.pipe"; -import { LoadingSpinnerComponent } from "../../../shared/components/loading-spinner/loading-spinner.component"; +import { EmptyProductDetailPipe } from '../../../shared/pipes/empty-product-detail.pipe'; +import { LoadingSpinnerComponent } from '../../../shared/components/loading-spinner/loading-spinner.component'; import { LoadingComponentId } from '../../../shared/enums/loading-component-id'; import { LoadingService } from '../../../core/services/loading/loading.service'; @@ -155,7 +157,15 @@ export class ProductDetailComponent { const productId = this.route.snapshot.params[ROUTER.ID]; this.productDetailService.productId.set(productId); if (productId) { - this.loadingService.showLoading(LoadingComponentId.DETAIL_PAGE); + const observable = forkJoin({ + productDetail: this.getProductContentObservable(productId) + }); + // const productDetail = this.getProductContentObservable(productId); + forkJoin({ + productDetail: this.getProductContentObservable(productId) + }).subscribe(res => { + this.getProductContent(res.productDetail); + }); this.getProductContent(productId); this.productFeedbackService.initFeedbacks(); this.productStarRatingService.fetchData(); @@ -164,10 +174,6 @@ export class ProductDetailComponent { this.getUserFeedBack(); } } - - ngAfterViewInit(): void { - // this.loadingService.hideLoading(LoadingComponentId.DETAIL_PAGE); - } getUserFeedBack() { this.productFeedbackService.findProductFeedbackOfUser().subscribe(() => { @@ -177,41 +183,41 @@ export class ProductDetailComponent { this.appModalService .openAddFeedbackDialog() .then(() => this.removeQueryParam()) - .catch(() => this.removeQueryParam()) + .catch(() => this.removeQueryParam()); } }); }); } - getProductContent(productId: string) { + getProductContentObservable(productId: string) { const isShowDevVersion = CommonUtils.getCookieValue( this.cookieService, SHOW_DEV_VERSION, false ); - this.getProductById(productId, isShowDevVersion).subscribe( - productDetail => { - this.productDetail.set(productDetail); - this.productModuleContent.set(productDetail.productModuleContent); - this.metaProductJsonUrl = productDetail.metaProductJsonUrl; - this.productDetailService.productNames.set(productDetail.names); - this.productDetailService.productLogoUrl.set(productDetail.logoUrl); - this.installationCount = productDetail.installationCount; - this.handleProductContentVersion(); - this.updateProductDetailActionType(productDetail); - this.logoUrl = productDetail.logoUrl; - this.updateWebBrowserTitle(); - const ratingLabels = RATING_LABELS_BY_TYPE.find( - button => button.type === productDetail.type - ); - if (ratingLabels !== undefined) { - this.productDetailService.ratingBtnLabel.set(ratingLabels.btnLabel); - this.productDetailService.noFeedbackLabel.set( - ratingLabels.noFeedbackLabel - ); - } - } + return this.getProductById(productId, isShowDevVersion); + } + + getProductContent(productDetail: ProductDetail) { + this.productDetail.set(productDetail); + this.productModuleContent.set(productDetail.productModuleContent); + this.metaProductJsonUrl = productDetail.metaProductJsonUrl; + this.productDetailService.productNames.set(productDetail.names); + this.productDetailService.productLogoUrl.set(productDetail.logoUrl); + this.installationCount = productDetail.installationCount; + this.handleProductContentVersion(); + this.updateProductDetailActionType(productDetail); + this.logoUrl = productDetail.logoUrl; + this.updateWebBrowserTitle(); + const ratingLabels = RATING_LABELS_BY_TYPE.find( + button => button.type === productDetail.type ); + if (ratingLabels !== undefined) { + this.productDetailService.ratingBtnLabel.set(ratingLabels.btnLabel); + this.productDetailService.noFeedbackLabel.set( + ratingLabels.noFeedbackLabel + ); + } } onClickingBackToHomepageButton() { diff --git a/marketplace-ui/src/app/modules/product/product.service.ts b/marketplace-ui/src/app/modules/product/product.service.ts index 9a88fc1fc..511a2ac42 100644 --- a/marketplace-ui/src/app/modules/product/product.service.ts +++ b/marketplace-ui/src/app/modules/product/product.service.ts @@ -82,14 +82,13 @@ export class ProductService { productId: string, isShowDevVersion: boolean ): Observable { - this.loadingService.showLoading(LoadingComponentId.DETAIL_PAGE); return this.httpClient .get( `${API_URI.PRODUCT_DETAILS}/${productId}?isShowDevVersion=${isShowDevVersion}`, { context: new HttpContext().set( LoadingComponent, - LoadingComponentId.PRODUCT_DETAIL_INFORMATION + LoadingComponentId.DETAIL_PAGE ) } ) From 305a97a01aa12bf0979347c63f32eeaeafa5ec03 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Fri, 13 Dec 2024 17:29:20 +0700 Subject: [PATCH 14/26] udpate service --- .../product-detail-feedback.component.spec.ts | 4 +- .../product-feedback.service.spec.ts | 38 +++++++++++++------ .../product-feedback.service.ts | 23 +++++++---- .../product-star-rating.service.ts | 27 +++++++------ .../product-detail.component.ts | 24 ++++++------ 5 files changed, 72 insertions(+), 44 deletions(-) diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-detail-feedback.component.spec.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-detail-feedback.component.spec.ts index 628d0ad0a..131db4f11 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-detail-feedback.component.spec.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-detail-feedback.component.spec.ts @@ -39,13 +39,13 @@ describe('ProductDetailFeedbackComponent', () => { mockProductFeedbackService = jasmine.createSpyObj( 'ProductFeedbackService', [ - 'initFeedbacks', + 'fetchFeedbacks', 'findProductFeedbackOfUser', 'loadMoreFeedbacks', 'areAllFeedbacksLoaded', 'totalElements' ], - {feedbacks: signal([] as Feedback[]), sort: signal('updatedAt,desc')} + { feedbacks: signal([] as Feedback[]), sort: signal('updatedAt,desc') } ); mockProductStarRatingService = jasmine.createSpyObj( 'ProductStarRatingService', diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.spec.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.spec.ts index df7abd888..d326b0869 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.spec.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.spec.ts @@ -62,20 +62,26 @@ describe('ProductFeedbackService', () => { it('should initialize feedbacks', () => { const mockResponse = { - _embedded: { feedbacks: [{ content: 'Great product!', rating: 5, productId: '123' }] }, + _embedded: { + feedbacks: [{ content: 'Great product!', rating: 5, productId: '123' }] + }, page: { totalPages: 2, totalElements: 5 } }; productDetailService.productId.and.returnValue('123'); - service.initFeedbacks(); - const req = httpMock.expectOne('api/feedback/product/123?page=0&size=8&sort=newest'); + service.fetchFeedbacks(); + const req = httpMock.expectOne( + 'api/feedback/product/123?page=0&size=8&sort=newest' + ); expect(req.request.method).toBe('GET'); req.flush(mockResponse); expect(service.totalPages()).toBe(2); expect(service.totalElements()).toBe(5); - expect(service.feedbacks()).toEqual([{ content: 'Great product!', rating: 5, productId: '123' }]); + expect(service.feedbacks()).toEqual([ + { content: 'Great product!', rating: 5, productId: '123' } + ]); }); it('should load more feedbacks', () => { @@ -85,17 +91,27 @@ describe('ProductFeedbackService', () => { const additionalFeedback: Feedback[] = [ { content: 'Another review', rating: 4, productId: '123' } ]; - + productDetailService.productId.and.returnValue('123'); - service.initFeedbacks(); - const initReq = httpMock.expectOne('api/feedback/product/123?page=0&size=8&sort=newest'); - initReq.flush({ _embedded: { feedbacks: initialFeedback }, page: { totalPages: 2, totalElements: 5 } }); - + service.fetchFeedbacks(); + const initReq = httpMock.expectOne( + 'api/feedback/product/123?page=0&size=8&sort=newest' + ); + initReq.flush({ + _embedded: { feedbacks: initialFeedback }, + page: { totalPages: 2, totalElements: 5 } + }); + service.loadMoreFeedbacks(); - const loadMoreReq = httpMock.expectOne('api/feedback/product/123?page=1&size=8&sort=newest'); + const loadMoreReq = httpMock.expectOne( + 'api/feedback/product/123?page=1&size=8&sort=newest' + ); loadMoreReq.flush({ _embedded: { feedbacks: additionalFeedback } }); - expect(service.feedbacks()).toEqual([...initialFeedback, ...additionalFeedback]); + expect(service.feedbacks()).toEqual([ + ...initialFeedback, + ...additionalFeedback + ]); }); it('should change sort and fetch feedbacks', () => { diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.ts index d6fe89da0..dba2bac09 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.ts @@ -70,7 +70,7 @@ export class ProductFeedbackService { }) .pipe( tap(() => { - this.initFeedbacks(); + this.fetchFeedbacks(); this.findProductFeedbackOfUser().subscribe(); this.productStarRatingService.fetchData(); }), @@ -100,7 +100,9 @@ export class ProductFeedbackService { return this.http .get(requestURL, { params: requestParams, - context: new HttpContext().set(SkipLoading, true).set(ForwardingError, true) + context: new HttpContext() + .set(SkipLoading, true) + .set(ForwardingError, true) }) .pipe( tap(response => { @@ -152,11 +154,9 @@ export class ProductFeedbackService { ); } - initFeedbacks(): void { - this.page.set(0); - this.findProductFeedbacksByCriteria().subscribe(response => { - this.totalPages.set(response.page.totalPages); - this.totalElements.set(response.page.totalElements); + fetchFeedbacks(): void { + this.getInitFeedbacksObservable().subscribe(response => { + this.setInitFeedbacksObservable(response); }); } @@ -174,4 +174,13 @@ export class ProductFeedbackService { private clearTokenCookie(): void { this.cookieService.delete(TOKEN_KEY); } + + handleFeedbackApiResponse(response: FeedbackApiResponse) { + this.totalPages.set(response.page.totalPages); + this.totalElements.set(response.page.totalElements); + } + getInitFeedbacksObservable() { + this.page.set(0); + return this.findProductFeedbacksByCriteria(); + } } \ No newline at end of file diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-star-rating-panel/product-star-rating.service.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-star-rating-panel/product-star-rating.service.ts index c8af47d13..893c31cf3 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-star-rating-panel/product-star-rating.service.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-star-rating-panel/product-star-rating.service.ts @@ -7,7 +7,7 @@ import { signal, WritableSignal } from '@angular/core'; -import { tap } from 'rxjs'; +import { Observable, tap } from 'rxjs'; import { StarRatingCounting } from '../../../../../shared/models/star-rating-counting.model'; import { ProductDetailService } from '../../product-detail.service'; import { SkipLoading } from '../../../../../core/interceptors/api.interceptor'; @@ -28,16 +28,7 @@ export class ProductStarRatingService { ); fetchData(productId: string = this.productDetailService.productId()): void { - const requestURL = `api/feedback/product/${productId}/rating`; - this.http - .get(requestURL, {context: new HttpContext().set(SkipLoading, true)}) - .pipe( - tap(data => { - this.sortByStar(data); - this.starRatings.set(data); - }) - ) - .subscribe(); + this.getRatingObservable(productId).subscribe(); } private sortByStar(starRatings: StarRatingCounting[]): void { @@ -64,4 +55,18 @@ export class ProductStarRatingService { return Math.round(reviewNumber * 10) / 10; } + + getRatingObservable(id: string): Observable { + const requestURL = `api/feedback/product/${id}/rating`; + return this.http + .get(requestURL, { + context: new HttpContext().set(SkipLoading, true) + }) + .pipe( + tap(data => { + this.sortByStar(data); + this.starRatings.set(data); + }) + ); + } } 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 3435d1a81..53d760ea1 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 @@ -157,20 +157,18 @@ export class ProductDetailComponent { const productId = this.route.snapshot.params[ROUTER.ID]; this.productDetailService.productId.set(productId); if (productId) { - const observable = forkJoin({ - productDetail: this.getProductContentObservable(productId) - }); - // const productDetail = this.getProductContentObservable(productId); forkJoin({ - productDetail: this.getProductContentObservable(productId) + productDetail: this.getProductDetailObservable(productId), + productFeedBack: this.productFeedbackService.getInitFeedbacksObservable(), + rating: this.productStarRatingService.getRatingObservable(productId), + }).subscribe(res => { - this.getProductContent(res.productDetail); + this.handleProductDetail(res.productDetail); + this.productFeedbackService.handleFeedbackApiResponse(res.productFeedBack); + res.rating; + this.updateDropdownSelection(); + this.checkMediaSize(); }); - this.getProductContent(productId); - this.productFeedbackService.initFeedbacks(); - this.productStarRatingService.fetchData(); - this.updateDropdownSelection(); - this.checkMediaSize(); this.getUserFeedBack(); } } @@ -189,7 +187,7 @@ export class ProductDetailComponent { }); } - getProductContentObservable(productId: string) { + getProductDetailObservable(productId: string): Observable { const isShowDevVersion = CommonUtils.getCookieValue( this.cookieService, SHOW_DEV_VERSION, @@ -198,7 +196,7 @@ export class ProductDetailComponent { return this.getProductById(productId, isShowDevVersion); } - getProductContent(productDetail: ProductDetail) { + handleProductDetail(productDetail: ProductDetail) { this.productDetail.set(productDetail); this.productModuleContent.set(productDetail.productModuleContent); this.metaProductJsonUrl = productDetail.metaProductJsonUrl; From 04c6628fc2a061e8089ca9ecc2e9744487aac273 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Mon, 16 Dec 2024 21:09:48 +0700 Subject: [PATCH 15/26] finalize service --- .../product-feedback.service.ts | 2 +- .../product-detail.component.html | 1 - .../product-detail.component.ts | 29 ++++++++--------- .../app/modules/product/product.service.ts | 31 +++---------------- 4 files changed, 18 insertions(+), 45 deletions(-) diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.ts index dba2bac09..b8a363c5e 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.ts @@ -156,7 +156,7 @@ export class ProductFeedbackService { fetchFeedbacks(): void { this.getInitFeedbacksObservable().subscribe(response => { - this.setInitFeedbacksObservable(response); + this.handleFeedbackApiResponse(response); }); } 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 9cd167eb5..05aa4f76a 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 @@ -1,6 +1,5 @@
- 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 53d760ea1..f8f6728b7 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 @@ -149,6 +149,7 @@ export class ProductDetailComponent { } ngOnInit(): void { + this.loadingService.showLoading(LoadingComponentId.DETAIL_PAGE); this.router.navigate([], { relativeTo: this.route, queryParamsHandling: 'merge', @@ -161,30 +162,26 @@ export class ProductDetailComponent { productDetail: this.getProductDetailObservable(productId), productFeedBack: this.productFeedbackService.getInitFeedbacksObservable(), rating: this.productStarRatingService.getRatingObservable(productId), - + userFeedback: this.productFeedbackService.findProductFeedbackOfUser() }).subscribe(res => { this.handleProductDetail(res.productDetail); this.productFeedbackService.handleFeedbackApiResponse(res.productFeedBack); res.rating; this.updateDropdownSelection(); this.checkMediaSize(); + this.route.queryParams.subscribe(params => { + this.showPopup = params['showPopup'] === 'true'; + if (this.showPopup && this.authService.getToken()) { + this.appModalService + .openAddFeedbackDialog() + .then(() => this.removeQueryParam()) + .catch(() => this.removeQueryParam()); + } + }); + this.loadingService.hideLoading(LoadingComponentId.DETAIL_PAGE); }); - this.getUserFeedBack(); - } - } - getUserFeedBack() { - this.productFeedbackService.findProductFeedbackOfUser().subscribe(() => { - this.route.queryParams.subscribe(params => { - this.showPopup = params['showPopup'] === 'true'; - if (this.showPopup && this.authService.getToken()) { - this.appModalService - .openAddFeedbackDialog() - .then(() => this.removeQueryParam()) - .catch(() => this.removeQueryParam()); - } - }); - }); + } } getProductDetailObservable(productId: string): Observable { diff --git a/marketplace-ui/src/app/modules/product/product.service.ts b/marketplace-ui/src/app/modules/product/product.service.ts index 511a2ac42..7154e9624 100644 --- a/marketplace-ui/src/app/modules/product/product.service.ts +++ b/marketplace-ui/src/app/modules/product/product.service.ts @@ -53,13 +53,7 @@ export class ProductService { version: string ): Observable { return this.httpClient.get( - `${API_URI.PRODUCT_DETAILS}/${productId}/${version}`, - { - context: new HttpContext().set( - LoadingComponent, - LoadingComponentId.PRODUCT_DETAIL_INFORMATION - ) - } + `${API_URI.PRODUCT_DETAILS}/${productId}/${version}` ); } @@ -68,13 +62,7 @@ export class ProductService { version: string ): Observable { return this.httpClient.get( - `${API_URI.PRODUCT_DETAILS}/${productId}/${version}/bestmatch`, - { - context: new HttpContext().set( - LoadingComponent, - LoadingComponentId.DETAIL_PAGE - ) - } + `${API_URI.PRODUCT_DETAILS}/${productId}/${version}/bestmatch` ); } @@ -84,13 +72,7 @@ export class ProductService { ): Observable { return this.httpClient .get( - `${API_URI.PRODUCT_DETAILS}/${productId}?isShowDevVersion=${isShowDevVersion}`, - { - context: new HttpContext().set( - LoadingComponent, - LoadingComponentId.DETAIL_PAGE - ) - } + `${API_URI.PRODUCT_DETAILS}/${productId}?isShowDevVersion=${isShowDevVersion}` ) .pipe(delay(5000)); } @@ -104,12 +86,7 @@ export class ProductService { const params = new HttpParams() .append('designerVersion', designerVersion) .append('isShowDevVersion', showDevVersion); - return this.httpClient.get(url, { - params, - context: new HttpContext() - .set(SkipLoading, true) - .set(LoadingComponent, LoadingComponentId.PRODUCT_DETAIL_INFORMATION) - }); + return this.httpClient.get(url, { params }); } sendRequestToUpdateInstallationCount( From afdd13549fe3a1f1cbff780a49094230577267f6 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Mon, 16 Dec 2024 22:02:04 +0700 Subject: [PATCH 16/26] update test case --- .../app/core/services/loading/loading.service.spec.ts | 9 +++++---- .../product-detail/product-detail.component.spec.ts | 4 ++-- .../src/app/modules/product/product.service.spec.ts | 4 ++-- .../src/app/modules/product/product.service.ts | 5 ++--- .../loading-spinner/loading-spinner.component.spec.ts | 5 ----- 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/marketplace-ui/src/app/core/services/loading/loading.service.spec.ts b/marketplace-ui/src/app/core/services/loading/loading.service.spec.ts index d6640c9e5..d10ebcdc4 100644 --- a/marketplace-ui/src/app/core/services/loading/loading.service.spec.ts +++ b/marketplace-ui/src/app/core/services/loading/loading.service.spec.ts @@ -1,6 +1,7 @@ import { TestBed } from '@angular/core/testing'; import { LoadingService } from './loading.service'; +import { LoadingComponentId } from '../../../shared/enums/loading-component-id'; describe('LoadingService', () => { let service: LoadingService; @@ -15,12 +16,12 @@ describe('LoadingService', () => { }); it('show should update isLoading to true', () => { - service.show(); - expect(service.isLoading()).toBeTrue(); + service.showLoading(LoadingComponentId.DETAIL_PAGE); + expect(service.loadingStates()[LoadingComponentId.DETAIL_PAGE]).toBeTrue(); }) it('hide should update isLoading to false', () => { - service.hide(); - expect(service.isLoading()).toBeFalse(); + service.hideLoading(LoadingComponentId.DETAIL_PAGE); + expect(service.loadingStates()[LoadingComponentId.DETAIL_PAGE]).toBeFalse(); }) }); 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 b3e14d5ac..969ed839a 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 @@ -663,9 +663,9 @@ describe('ProductDetailComponent', () => { expect(infoTab).toBeTruthy(); }); - it('should call checkMediaSize on ngAfterViewInit', fakeAsync(() => { + it('should call checkMediaSize on ngOnInit', fakeAsync(() => { spyOn(component, 'checkMediaSize'); - component.ngAfterViewInit(); + component.ngOnInit(); tick(); expect(component.checkMediaSize).toHaveBeenCalled(); })); diff --git a/marketplace-ui/src/app/modules/product/product.service.spec.ts b/marketplace-ui/src/app/modules/product/product.service.spec.ts index ab17ec697..fdac44320 100644 --- a/marketplace-ui/src/app/modules/product/product.service.spec.ts +++ b/marketplace-ui/src/app/modules/product/product.service.spec.ts @@ -195,8 +195,8 @@ describe('ProductService', () => { expect(req.request.method).toBe('GET'); req.flush(mockResponse); - expect(loadingServiceSpy.show).not.toHaveBeenCalled(); - expect(loadingServiceSpy.hide).not.toHaveBeenCalled(); + expect(loadingServiceSpy.showLoading).not.toHaveBeenCalled(); + expect(loadingServiceSpy.hideLoading).not.toHaveBeenCalled(); }); it('getProductDetailsWithVersion should return a product detail', () => { diff --git a/marketplace-ui/src/app/modules/product/product.service.ts b/marketplace-ui/src/app/modules/product/product.service.ts index 7154e9624..b6783daf0 100644 --- a/marketplace-ui/src/app/modules/product/product.service.ts +++ b/marketplace-ui/src/app/modules/product/product.service.ts @@ -1,6 +1,6 @@ import { HttpClient, HttpContext, HttpParams } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { delay, Observable } from 'rxjs'; +import { Observable } from 'rxjs'; import { LoadingService } from '../../core/services/loading/loading.service'; import { RequestParam } from '../../shared/enums/request-param'; import { ProductApiResponse } from '../../shared/models/apis/product-response.model'; @@ -73,8 +73,7 @@ export class ProductService { return this.httpClient .get( `${API_URI.PRODUCT_DETAILS}/${productId}?isShowDevVersion=${isShowDevVersion}` - ) - .pipe(delay(5000)); + ); } sendRequestToProductDetailVersionAPI( diff --git a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.spec.ts b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.spec.ts index 3578b8ac9..aad88c557 100644 --- a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.spec.ts +++ b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.spec.ts @@ -20,10 +20,6 @@ describe('LoadingSpinnerComponent', () => { expect(component).toBeTruthy(); }); - it('should have isFixPosition set to true by default', () => { - expect(component.isFixPosition).toBe(true); - }); - it('should apply position-fixed class when isFixPosition is true', () => { const containerElement = fixture.debugElement.query(By.css('.spinner-container')); expect(containerElement.nativeElement.classList.contains('position-fixed')).toBe(true); @@ -31,7 +27,6 @@ describe('LoadingSpinnerComponent', () => { }); it('should apply position-absolute class when isFixPosition is false', () => { - component.isFixPosition = false; fixture.detectChanges(); const containerElement = fixture.debugElement.query(By.css('.spinner-container')); expect(containerElement.nativeElement.classList.contains('position-absolute')).toBe(true); From 9c03e41db642ab65c8d15bc0dfe4f69bc40583aa Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Tue, 17 Dec 2024 13:55:43 +0700 Subject: [PATCH 17/26] update test case --- .../product-feedback.service.ts | 7 +- ...ct-detail-version-action.component.spec.ts | 75 +++--- .../product-detail.component.spec.ts | 233 ++++++++++-------- .../product-detail.component.ts | 72 +++--- .../modules/product/product.service.spec.ts | 11 +- .../app/modules/product/product.service.ts | 5 +- .../loading-spinner.component.spec.ts | 34 +-- .../src/app/shared/mocks/mock-data.ts | 51 +++- .../app/shared/services/app-modal.service.ts | 6 +- 9 files changed, 280 insertions(+), 214 deletions(-) diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.ts index b8a363c5e..de118fb2a 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.ts @@ -86,7 +86,7 @@ export class ProductFeedbackService { ); } - private findProductFeedbacksByCriteria( + findProductFeedbacksByCriteria( productId: string = this.productDetailService.productId(), page: number = this.page(), sort: string = this.sort(), @@ -175,11 +175,12 @@ export class ProductFeedbackService { this.cookieService.delete(TOKEN_KEY); } - handleFeedbackApiResponse(response: FeedbackApiResponse) { + handleFeedbackApiResponse(response: FeedbackApiResponse): void { this.totalPages.set(response.page.totalPages); this.totalElements.set(response.page.totalElements); } - getInitFeedbacksObservable() { + + getInitFeedbacksObservable(): Observable { this.page.set(0); return this.findProductFeedbacksByCriteria(); } diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.spec.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.spec.ts index 8ca0ec60b..f7f156a7b 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.spec.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.spec.ts @@ -10,15 +10,12 @@ import { CookieService } from 'ngx-cookie-service'; import { ActivatedRoute, provideRouter, Router } from '@angular/router'; import { CommonUtils } from '../../../../shared/utils/common.utils'; import { ROUTER } from '../../../../shared/constants/router.constant'; -import { MatomoConfiguration, MatomoModule, MatomoRouterModule } from 'ngx-matomo-client'; import { MatomoTestingModule } from 'ngx-matomo-client/testing'; import { ProductDetailActionType } from '../../../../shared/enums/product-detail-action-type'; import { MATOMO_TRACKING_ENVIRONMENT } from '../../../../shared/constants/matomo.constant'; class MockElementRef implements ElementRef { - nativeElement = { - contains: jasmine.createSpy('contains') - }; + nativeElement = { contains: jasmine.createSpy('contains') }; } describe('ProductDetailVersionActionComponent', () => { @@ -31,10 +28,11 @@ describe('ProductDetailVersionActionComponent', () => { beforeEach(() => { productServiceMock = jasmine.createSpyObj('ProductService', [ - 'sendRequestToProductDetailVersionAPI', 'sendRequestToUpdateInstallationCount', 'sendRequestToGetProductVersionsForDesigner' + 'sendRequestToProductDetailVersionAPI', + 'sendRequestToUpdateInstallationCount', + 'sendRequestToGetProductVersionsForDesigner' ]); - const commonUtilsSpy = jasmine.createSpyObj('CommonUtils', ['getCookieValue']); - // const cookieServiceSpy = jasmine.createSpyObj('CookieService', ['get', 'set']); + const commonUtilsSpy = jasmine.createSpyObj('CommonUtils', [ 'getCookieValue' ]); const activatedRouteSpy = jasmine.createSpyObj('ActivatedRoute', [], { snapshot: { queryParams: {} @@ -43,7 +41,7 @@ describe('ProductDetailVersionActionComponent', () => { TestBed.configureTestingModule({ imports: [ - ProductDetailVersionActionComponent, + ProductDetailVersionActionComponent, TranslateModule.forRoot(), MatomoTestingModule.forRoot() ], @@ -67,9 +65,7 @@ describe('ProductDetailVersionActionComponent', () => { fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); - }); + it('should create', () => { expect(component).toBeTruthy(); }); it('first artifact should be chosen when select corresponding version', () => { const selectedVersion = 'Version 10.0.2'; @@ -91,11 +87,13 @@ describe('ProductDetailVersionActionComponent', () => { it('should update selectedVersion, artifacts, selectedArtifactName, and selectedArtifact, and call addVersionParamToRoute', () => { const version = '1.0'; - const artifacts = [{ - name: 'Example Artifact', - downloadUrl: 'https://example.com/download', - isProductArtifact: true - } as ItemDropdown]; + const artifacts = [ + { + name: 'Example Artifact', + downloadUrl: 'https://example.com/download', + isProductArtifact: true + } as ItemDropdown + ]; const versionMap = new Map(); versionMap.set(version, artifacts); @@ -172,7 +170,6 @@ describe('ProductDetailVersionActionComponent', () => { }); }); - it('all of state should be reset before call rest api', () => { const selectedVersion = 'Version 10.0.2'; const artifact = { @@ -235,6 +232,7 @@ describe('ProductDetailVersionActionComponent', () => { }); it('should send Api to get DevVersion', () => { + component.isDevVersionsDisplayed.set(false); spyOn(component.isDevVersionsDisplayed, 'set'); expect(component.isDevVersionsDisplayed()).toBeFalse(); mockApiWithExpectedResponse(); @@ -249,7 +247,8 @@ describe('ProductDetailVersionActionComponent', () => { const mockArtifact1 = { name: 'Example Artifact1', downloadUrl: 'https://example.com/download', - isProductArtifact: true, label: 'Example Artifact1' + isProductArtifact: true, + label: 'Example Artifact1' } as ItemDropdown; const mockArtifact2 = { name: 'Example Artifact2', @@ -291,42 +290,61 @@ describe('ProductDetailVersionActionComponent', () => { component.versions.set(['1.0', '1.1']); fixture.detectChanges(); component.getVersionInDesigner(); - expect(productServiceMock.sendRequestToGetProductVersionsForDesigner).not.toHaveBeenCalled(); + expect( + productServiceMock.sendRequestToGetProductVersionsForDesigner + ).not.toHaveBeenCalled(); }); it('should call productService and update versions if versions are empty', () => { const productId = '123'; component.versions.set([]); const mockVersions = [{ version: '1.0' }, { version: '2.0' }]; - productServiceMock.sendRequestToGetProductVersionsForDesigner.and.returnValue(of(mockVersions)); + productServiceMock.sendRequestToGetProductVersionsForDesigner.and.returnValue( + of(mockVersions) + ); // Act component.getVersionInDesigner(); // Assert - expect(productServiceMock.sendRequestToGetProductVersionsForDesigner).toHaveBeenCalledWith(productId); + expect( + productServiceMock.sendRequestToGetProductVersionsForDesigner + ).toHaveBeenCalledWith(productId); expect(component.versions()).toEqual(['Version 1.0', 'Version 2.0']); }); it('should handle empty response from productService', () => { component.versions.set([]); - productServiceMock.sendRequestToGetProductVersionsForDesigner.and.returnValue(of([])); + productServiceMock.sendRequestToGetProductVersionsForDesigner.and.returnValue( + of([]) + ); // Act component.getVersionInDesigner(); // Assert - expect(productServiceMock.sendRequestToGetProductVersionsForDesigner).toHaveBeenCalledWith(productId); + expect( + productServiceMock.sendRequestToGetProductVersionsForDesigner + ).toHaveBeenCalledWith(productId); expect(component.versions()).toEqual([]); }); it('should return the correct tracking environment based on the action type', () => { const testCases = [ - { actionType: ProductDetailActionType.STANDARD, expected: MATOMO_TRACKING_ENVIRONMENT.standard }, - { actionType: ProductDetailActionType.DESIGNER_ENV, expected: MATOMO_TRACKING_ENVIRONMENT.designerEnv }, - { actionType: ProductDetailActionType.CUSTOM_SOLUTION, expected: MATOMO_TRACKING_ENVIRONMENT.customSolution }, + { + actionType: ProductDetailActionType.STANDARD, + expected: MATOMO_TRACKING_ENVIRONMENT.standard + }, + { + actionType: ProductDetailActionType.DESIGNER_ENV, + expected: MATOMO_TRACKING_ENVIRONMENT.designerEnv + }, + { + actionType: ProductDetailActionType.CUSTOM_SOLUTION, + expected: MATOMO_TRACKING_ENVIRONMENT.customSolution + } ]; - + testCases.forEach(({ actionType, expected }) => { component.actionType = actionType; @@ -335,9 +353,8 @@ describe('ProductDetailVersionActionComponent', () => { }); }); - it('should return empty environment when action type is default', () => { + it('should return empty environment when action type is default', () => { const result = component.getTrackingEnvironmentBasedOnActionType(); - expect(result).toBe(''); }); }); 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 969ed839a..749209234 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 @@ -20,7 +20,8 @@ import { MOCK_PRODUCT_DETAIL, MOCK_CRON_JOB_PRODUCT_DETAIL, MOCK_PRODUCT_MODULE_CONTENT, - MOCK_PRODUCTS + MOCK_PRODUCTS, + MOCK_FEEDBACK_API_RESPONSE } from '../../../shared/mocks/mock-data'; import { ProductService } from '../product.service'; import { ProductDetailComponent } from './product-detail.component'; @@ -31,6 +32,13 @@ import { ProductDetailActionType } from '../../../shared/enums/product-detail-ac import { LanguageService } from '../../../core/services/language/language.service'; import { Language } from '../../../shared/enums/language.enum'; import { MatomoTestingModule } from 'ngx-matomo-client/testing'; +import { AuthService } from '../../../auth/auth.service'; +import { AppModalService } from '../../../shared/services/app-modal.service'; +import { ProductFeedbackService } from './product-detail-feedback/product-feedbacks-panel/product-feedback.service'; +import { ProductStarRatingService } from './product-detail-feedback/product-star-rating-panel/product-star-rating.service'; +import { FeedbackApiResponse } from '../../../shared/models/apis/feedback-response.model'; +import { StarRatingCounting } from '../../../shared/models/star-rating-counting.model'; +import { Feedback } from '../../../shared/models/feedback.model'; const products = MOCK_PRODUCTS._embedded.products; declare const viewport: Viewport; @@ -41,6 +49,10 @@ describe('ProductDetailComponent', () => { let routingQueryParamService: jasmine.SpyObj; let languageService: jasmine.SpyObj; let titleService: Title; + let mockProductFeedbackService: jasmine.SpyObj; + let mockProductStarRatingService: jasmine.SpyObj; + let mockAuthService: jasmine.SpyObj; + let mockAppModalService: jasmine.SpyObj; beforeEach(async () => { const routingQueryParamServiceSpy = jasmine.createSpyObj( @@ -48,9 +60,27 @@ describe('ProductDetailComponent', () => { ['getDesignerVersionFromSessionStorage', 'isDesignerEnv'] ); - const languageServiceSpy = jasmine.createSpyObj( - 'LanguageService', - ['selectedLanguage'] + const languageServiceSpy = jasmine.createSpyObj('LanguageService', [ + 'selectedLanguage' + ]); + + mockProductFeedbackService = jasmine.createSpyObj( + 'ProductFeedbackService', + [ + 'getInitFeedbacksObservable', + 'findProductFeedbacksByCriteria', + 'handleFeedbackApiResponse', + 'findProductFeedbackOfUser', + 'totalElements' + ] + ); + mockAuthService = jasmine.createSpyObj('AuthService', ['getToken']); + mockAppModalService = jasmine.createSpyObj('AppModalService', [ + 'openAddFeedbackDialog' + ]); + mockProductStarRatingService = jasmine.createSpyObj( + 'ProductStarRatingService', + ['getRatingObservable', 'starRatings', 'totalComments', 'reviewNumber'] ); await TestBed.configureTestingModule({ @@ -61,15 +91,24 @@ describe('ProductDetailComponent', () => { MatomoTestingModule.forRoot() ], providers: [ + ProductStarRatingService, provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting(), + { + provide: ProductStarRatingService, + useValue: mockProductStarRatingService + }, + { + provide: ProductFeedbackService, + useValue: mockProductFeedbackService + }, + { provide: AuthService, useValue: mockAuthService }, + { provide: AppModalService, useValue: mockAppModalService }, { provide: ActivatedRoute, useValue: { - snapshot: { - params: { id: products[0].id }, - queryParams: { type: TypeOption.CONNECTORS } - }, + snapshot: { params: { id: products[0].id } }, + queryParams: of({ type: TypeOption.CONNECTORS }), fragment: of('description') } }, @@ -100,6 +139,20 @@ describe('ProductDetailComponent', () => { ) as jasmine.SpyObj; titleService = TestBed.inject(Title); + mockProductFeedbackService.getInitFeedbacksObservable.and.returnValue( + of([MOCK_FEEDBACK_API_RESPONSE] as any as FeedbackApiResponse) + ); + + mockProductFeedbackService.findProductFeedbackOfUser.and.returnValue( + of({} as any as Feedback) + ); + mockAppModalService.openAddFeedbackDialog.and.returnValue( + Promise.resolve() + ); + mockAuthService.getToken.and.returnValue('token'); + mockProductStarRatingService.getRatingObservable.and.returnValue( + of([] as any as StarRatingCounting[]) + ); }); beforeEach(() => { @@ -115,9 +168,7 @@ describe('ProductDetailComponent', () => { }); it('should have title like the name DE', () => { - languageService.selectedLanguage.and.returnValue( - Language.DE - ); + languageService.selectedLanguage.and.returnValue(Language.DE); component.updateWebBrowserTitle(); fixture.detectChanges(); @@ -193,8 +244,7 @@ describe('ProductDetailComponent', () => { fixture.detectChanges(); const description = fixture.debugElement.query(By.css('#description')); expect(description).toBeFalsy(); - } - ) + }); it('should return true for description when in EN language it is not null and not undefined and not empty', () => { const mockContent: ProductModuleContent = { @@ -204,9 +254,7 @@ describe('ProductDetailComponent', () => { const selectedLanguage = Language.EN; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('description')).toBeTrue(); @@ -220,9 +268,7 @@ describe('ProductDetailComponent', () => { const selectedLanguage = Language.DE; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('description')).toBeTrue(); @@ -236,9 +282,7 @@ describe('ProductDetailComponent', () => { const selectedLanguage = Language.DE; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('description')).toBeTrue(); @@ -247,14 +291,12 @@ describe('ProductDetailComponent', () => { it('should return true for description when in DE language it is undefined but in EN language it has value', () => { const mockContent: ProductModuleContent = { ...MOCK_PRODUCT_MODULE_CONTENT, - description: { en: 'Test description'} + description: { en: 'Test description' } }; const selectedLanguage = Language.DE; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('description')).toBeTrue(); @@ -286,9 +328,7 @@ describe('ProductDetailComponent', () => { const selectedLanguage = Language.EN; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('description')).toBeFalse(); @@ -297,14 +337,12 @@ describe('ProductDetailComponent', () => { it('should return false for description when in EN language it is undefined', () => { const mockContent: ProductModuleContent = { ...MOCK_PRODUCT_MODULE_CONTENT, - description: { de: "Test description" } + description: { de: 'Test description' } }; const selectedLanguage = Language.EN; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('description')).toBeFalse(); @@ -318,9 +356,7 @@ describe('ProductDetailComponent', () => { const selectedLanguage = Language.EN; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('description')).toBeFalse(); @@ -334,9 +370,7 @@ describe('ProductDetailComponent', () => { const selectedLanguage = Language.EN; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('description')).toBeFalse(); @@ -350,9 +384,7 @@ describe('ProductDetailComponent', () => { const selectedLanguage = Language.EN; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('setup')).toBeTrue(); @@ -366,9 +398,7 @@ describe('ProductDetailComponent', () => { const selectedLanguage = Language.DE; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('setup')).toBeTrue(); @@ -382,9 +412,7 @@ describe('ProductDetailComponent', () => { const selectedLanguage = Language.DE; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('setup')).toBeTrue(); @@ -393,14 +421,12 @@ describe('ProductDetailComponent', () => { it('should return true for setup when in DE language it is undefined but in EN language it has value', () => { const mockContent: ProductModuleContent = { ...MOCK_PRODUCT_MODULE_CONTENT, - setup: { en: 'Test setup'} + setup: { en: 'Test setup' } }; const selectedLanguage = Language.DE; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('setup')).toBeTrue(); @@ -416,14 +442,12 @@ describe('ProductDetailComponent', () => { it('should return false for setup when in EN language it is an empty string', () => { const mockContent: ProductModuleContent = { ...MOCK_PRODUCT_MODULE_CONTENT, - setup: { en: '', de: "Test setup" } + setup: { en: '', de: 'Test setup' } }; const selectedLanguage = Language.EN; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('setup')).toBeFalse(); @@ -432,14 +456,12 @@ describe('ProductDetailComponent', () => { it('should return false for setup when in EN language it is undefined', () => { const mockContent: ProductModuleContent = { ...MOCK_PRODUCT_MODULE_CONTENT, - setup: { de: "Test setup" } + setup: { de: 'Test setup' } }; const selectedLanguage = Language.EN; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('setup')).toBeFalse(); @@ -453,9 +475,7 @@ describe('ProductDetailComponent', () => { const selectedLanguage = Language.EN; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('setup')).toBeFalse(); @@ -469,9 +489,7 @@ describe('ProductDetailComponent', () => { const selectedLanguage = Language.EN; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('setup')).toBeFalse(); @@ -485,9 +503,7 @@ describe('ProductDetailComponent', () => { const selectedLanguage = Language.EN; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('demo')).toBeTrue(); @@ -501,9 +517,7 @@ describe('ProductDetailComponent', () => { const selectedLanguage = Language.DE; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('demo')).toBeTrue(); @@ -517,9 +531,7 @@ describe('ProductDetailComponent', () => { const selectedLanguage = Language.DE; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('demo')).toBeTrue(); @@ -528,14 +540,12 @@ describe('ProductDetailComponent', () => { it('should return true for demo when in DE language it is undefined but in EN language it has value', () => { const mockContent: ProductModuleContent = { ...MOCK_PRODUCT_MODULE_CONTENT, - demo: { en: 'Test demo'} + demo: { en: 'Test demo' } }; const selectedLanguage = Language.DE; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('demo')).toBeTrue(); @@ -556,9 +566,7 @@ describe('ProductDetailComponent', () => { const selectedLanguage = Language.EN; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('demo')).toBeFalse(); @@ -567,14 +575,12 @@ describe('ProductDetailComponent', () => { it('should return false for demo when in EN language it is undefined', () => { const mockContent: ProductModuleContent = { ...MOCK_PRODUCT_MODULE_CONTENT, - demo: { de: "Test demo" } + demo: { de: 'Test demo' } }; const selectedLanguage = Language.EN; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('demo')).toBeFalse(); @@ -588,9 +594,7 @@ describe('ProductDetailComponent', () => { const selectedLanguage = Language.EN; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('demo')).toBeFalse(); @@ -604,15 +608,12 @@ describe('ProductDetailComponent', () => { const selectedLanguage = Language.EN; - languageService.selectedLanguage.and.returnValue( - selectedLanguage - ); + languageService.selectedLanguage.and.returnValue(selectedLanguage); component.productModuleContent.set(mockContent); expect(component.getContent('demo')).toBeFalse(); }); - it('should display dropdown horizontally on small viewport', () => { viewport.set(540); const tabGroup = fixture.debugElement.query(By.css('.row-tab')); @@ -737,7 +738,7 @@ describe('ProductDetailComponent', () => { it('should return DESIGNER_ENV as acction type in Designer Env', () => { routingQueryParamService.isDesignerEnv.and.returnValue(true); - component.updateProductDetailActionType({ sourceUrl: 'some-url'} as any); + component.updateProductDetailActionType({ sourceUrl: 'some-url' } as any); expect(component.productDetailActionType()).toBe( ProductDetailActionType.DESIGNER_ENV ); @@ -756,7 +757,6 @@ describe('ProductDetailComponent', () => { By.css('#app-product-installation-count-action') ); expect(installationCount).toBeFalsy(); - }); it('should return STANDARD as acction type when when productDetail.sourceUrl is defined', () => { @@ -771,7 +771,7 @@ describe('ProductDetailComponent', () => { 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, + ...MOCK_PRODUCT_MODULE_CONTENT }; component.productModuleContent.set(mockContent); @@ -780,14 +780,20 @@ describe('ProductDetailComponent', () => { it('should hide tab and tab content when all tabs have no content', () => { const mockContent: ProductModuleContent = { - ...MOCK_PRODUCT_MODULE_CONTENT, + ...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')); + 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(); @@ -795,18 +801,32 @@ describe('ProductDetailComponent', () => { }); it('should generate right text for the rate connector', () => { - const rateConnector = fixture.debugElement.query(By.css('.rate-connector-btn')); - expect(rateConnector.childNodes[0].nativeNode.textContent).toContain("common.feedback.rateFeedbackForConnectorBtnLabel"); + const rateConnector = fixture.debugElement.query( + By.css('.rate-connector-btn') + ); + expect(rateConnector.childNodes[0].nativeNode.textContent).toContain( + 'common.feedback.rateFeedbackForConnectorBtnLabel' + ); - const rateConnectorEmptyText = fixture.debugElement.query(By.css('.rate-empty-text')); - expect(rateConnectorEmptyText.childNodes[0].nativeNode.textContent).toContain("common.feedback.noFeedbackForConnectorLabel"); + const rateConnectorEmptyText = fixture.debugElement.query( + By.css('.rate-empty-text') + ); + expect( + rateConnectorEmptyText.childNodes[0].nativeNode.textContent + ).toContain('common.feedback.noFeedbackForConnectorLabel'); component.route.snapshot.params['id'] = 'cronjob'; - spyOn(component, 'getProductById').and.returnValue(of(MOCK_CRON_JOB_PRODUCT_DETAIL)); + spyOn(component, 'getProductById').and.returnValue( + of(MOCK_CRON_JOB_PRODUCT_DETAIL) + ); component.ngOnInit(); fixture.detectChanges(); - expect(rateConnector.childNodes[0].nativeNode.textContent).toContain("common.feedback.rateFeedbackForUtilityBtnLabel"); - expect(rateConnectorEmptyText.childNodes[0].nativeNode.textContent).toContain("common.feedback.noFeedbackForUtilityLabel"); + expect(rateConnector.childNodes[0].nativeNode.textContent).toContain( + 'common.feedback.rateFeedbackForUtilityBtnLabel' + ); + expect( + rateConnectorEmptyText.childNodes[0].nativeNode.textContent + ).toContain('common.feedback.noFeedbackForUtilityLabel'); }); it('maven tab should not display when product module content is missing', () => { @@ -816,6 +836,7 @@ describe('ProductDetailComponent', () => { let mavenTab = fixture.debugElement.query( By.css('app-product-detail-maven-content') ); + expect(mavenTab).toBeTruthy(); component.productModuleContent.set({} as any as ProductModuleContent); fixture.detectChanges(); 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 f8f6728b7..1ebd42e82 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 @@ -149,7 +149,6 @@ export class ProductDetailComponent { } ngOnInit(): void { - this.loadingService.showLoading(LoadingComponentId.DETAIL_PAGE); this.router.navigate([], { relativeTo: this.route, queryParamsHandling: 'merge', @@ -158,29 +157,32 @@ export class ProductDetailComponent { const productId = this.route.snapshot.params[ROUTER.ID]; this.productDetailService.productId.set(productId); if (productId) { + this.loadingService.showLoading(LoadingComponentId.DETAIL_PAGE); forkJoin({ productDetail: this.getProductDetailObservable(productId), - productFeedBack: this.productFeedbackService.getInitFeedbacksObservable(), + productFeedBack: + this.productFeedbackService.getInitFeedbacksObservable(), rating: this.productStarRatingService.getRatingObservable(productId), - userFeedback: this.productFeedbackService.findProductFeedbackOfUser() + userFeedback: this.productFeedbackService.findProductFeedbackOfUser(), + params: this.route.queryParams }).subscribe(res => { this.handleProductDetail(res.productDetail); - this.productFeedbackService.handleFeedbackApiResponse(res.productFeedBack); - res.rating; + this.productFeedbackService.handleFeedbackApiResponse( + res.productFeedBack + ); this.updateDropdownSelection(); this.checkMediaSize(); - this.route.queryParams.subscribe(params => { - this.showPopup = params['showPopup'] === 'true'; - if (this.showPopup && this.authService.getToken()) { - this.appModalService - .openAddFeedbackDialog() - .then(() => this.removeQueryParam()) - .catch(() => this.removeQueryParam()); - } - }); + this.showPopup = res.params['showPopup'] === 'true'; + console.log(this.showPopup); + + if (this.showPopup && this.authService.getToken()) { + this.appModalService + .openAddFeedbackDialog() + .then(() => this.removeQueryParam()) + .catch(() => this.removeQueryParam()); + } this.loadingService.hideLoading(LoadingComponentId.DETAIL_PAGE); }); - } } @@ -193,7 +195,7 @@ export class ProductDetailComponent { return this.getProductById(productId, isShowDevVersion); } - handleProductDetail(productDetail: ProductDetail) { + handleProductDetail(productDetail: ProductDetail): void { this.productDetail.set(productDetail); this.productModuleContent.set(productDetail.productModuleContent); this.metaProductJsonUrl = productDetail.metaProductJsonUrl; @@ -215,15 +217,15 @@ export class ProductDetailComponent { } } - onClickingBackToHomepageButton() { + onClickingBackToHomepageButton(): void { this.router.navigate([API_URI.APP]); } - onLogoError() { + onLogoError(): void { this.logoUrl = DEFAULT_IMAGE_URL; } - handleProductContentVersion() { + handleProductContentVersion(): void { if (this.isEmptyProductContent()) { return; } @@ -232,7 +234,7 @@ export class ProductDetailComponent { ); } - updateProductDetailActionType(productDetail: ProductDetail) { + updateProductDetailActionType(productDetail: ProductDetail): void { if (productDetail?.sourceUrl === undefined) { this.productDetailActionType.set(ProductDetailActionType.CUSTOM_SOLUTION); } else if (this.routingQueryParamService.isDesignerEnv()) { @@ -242,7 +244,7 @@ export class ProductDetailComponent { } } - scrollToTop() { + scrollToTop(): void { window.scrollTo({ left: 0, top: 0, behavior: 'instant' }); } @@ -298,7 +300,6 @@ export class ProductDetailComponent { ), dependency: content.isDependency }; - return conditions[value] ?? false; } @@ -307,7 +308,7 @@ export class ProductDetailComponent { return !content || Object.keys(content).length === 0; } - loadDetailTabs(selectedVersion: string) { + loadDetailTabs(selectedVersion: string): void { let version = selectedVersion || this.productDetail().newestReleaseVersion; version = version.replace(VERSION.displayPrefix, ''); this.productService @@ -319,17 +320,17 @@ export class ProductDetailComponent { }); } - onTabChange(event: string) { + onTabChange(event: string): void { this.setActiveTab(event); this.isTabDropdownShown.update(value => !value); this.onTabDropdownShown(); } - getSelectedTabLabel() { + getSelectedTabLabel(): string { return CommonUtils.getLabel(this.activeTab, PRODUCT_DETAIL_TABS); } - updateDropdownSelection() { + updateDropdownSelection(): void { const dropdown = document.getElementById( 'tab-group-dropdown' ) as HTMLSelectElement; @@ -338,7 +339,7 @@ export class ProductDetailComponent { } } - setActiveTab(tab: string) { + setActiveTab(tab: string): void { this.activeTab = tab; const hash = '#tab-' + tab; const path = window.location.pathname; @@ -357,16 +358,16 @@ export class ProductDetailComponent { localStorage.setItem(STORAGE_ITEM, JSON.stringify(savedTab)); } - onShowInfoContent() { + onShowInfoContent(): void { this.isDropdownOpen.update(value => !value); } - onTabDropdownShown() { + onTabDropdownShown(): void { this.isTabDropdownShown.set(!this.isTabDropdownShown()); } @HostListener('document:click', ['$event']) - handleClickOutside(event: MouseEvent) { + handleClickOutside(event: MouseEvent): void { const formSelect = this.elementRef.nativeElement.querySelector('.form-select'); @@ -380,11 +381,11 @@ export class ProductDetailComponent { } @HostListener('window:resize', ['$event']) - onResize() { + onResize(): void { this.checkMediaSize(); } - checkMediaSize() { + checkMediaSize(): void { const mediaQuery = window.matchMedia('(max-width: 767px)'); if (mediaQuery.matches) { this.isMobileMode.set(true); @@ -393,7 +394,7 @@ export class ProductDetailComponent { } } - onClickRateBtn() { + onClickRateBtn(): void { const productId = this.productDetailService.productId(); if (this.authService.getToken()) { this.appModalService.openAddFeedbackDialog(); @@ -402,7 +403,7 @@ export class ProductDetailComponent { } } - receiveInstallationCountData(data: number) { + receiveInstallationCountData(data: number): void { this.installationCount = data; } @@ -421,7 +422,7 @@ export class ProductDetailComponent { } } - getDisplayedTabsSignal() { + getDisplayedTabsSignal(): ItemDropdown[] { this.updateWebBrowserTitle(); const displayedTabs: ItemDropdown[] = []; for (const detailTab of this.detailTabs) { @@ -449,7 +450,6 @@ export class ProductDetailComponent { productDetail.vendorImage = vendorImage || vendorImageDarkMode; productDetail.vendorImageDarkMode = vendorImageDarkMode || vendorImage; } - return productDetail; } } diff --git a/marketplace-ui/src/app/modules/product/product.service.spec.ts b/marketplace-ui/src/app/modules/product/product.service.spec.ts index fdac44320..fddf9522a 100644 --- a/marketplace-ui/src/app/modules/product/product.service.spec.ts +++ b/marketplace-ui/src/app/modules/product/product.service.spec.ts @@ -20,25 +20,19 @@ describe('ProductService', () => { let products = MOCK_PRODUCTS._embedded.products; let service: ProductService; let httpMock: HttpTestingController; - let loadingServiceSpy: jasmine.SpyObj; beforeEach(() => { - const spyLoading = jasmine.createSpyObj('LoadingService', ['show', 'hide']); - TestBed.configureTestingModule({ imports: [], providers: [ ProductService, provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting(), - { provide: LoadingService, useValue: spyLoading } + LoadingService ] }); service = TestBed.inject(ProductService); httpMock = TestBed.inject(HttpTestingController); - loadingServiceSpy = TestBed.inject( - LoadingService - ) as jasmine.SpyObj; }); it('should be created', () => { @@ -194,9 +188,6 @@ describe('ProductService', () => { expect(req.request.method).toBe('GET'); req.flush(mockResponse); - - expect(loadingServiceSpy.showLoading).not.toHaveBeenCalled(); - expect(loadingServiceSpy.hideLoading).not.toHaveBeenCalled(); }); it('getProductDetailsWithVersion should return a product detail', () => { diff --git a/marketplace-ui/src/app/modules/product/product.service.ts b/marketplace-ui/src/app/modules/product/product.service.ts index b6783daf0..aaabb6183 100644 --- a/marketplace-ui/src/app/modules/product/product.service.ts +++ b/marketplace-ui/src/app/modules/product/product.service.ts @@ -7,10 +7,7 @@ import { ProductApiResponse } from '../../shared/models/apis/product-response.mo import { Criteria } from '../../shared/models/criteria.model'; import { ProductDetail } from '../../shared/models/product-detail.model'; import { VersionData } from '../../shared/models/vesion-artifact.model'; -import { - SkipLoading, - LoadingComponent -} from '../../core/interceptors/api.interceptor'; +import { LoadingComponent } from '../../core/interceptors/api.interceptor'; import { VersionAndUrl } from '../../shared/models/version-and-url'; import { API_URI } from '../../shared/constants/api.constant'; import { LoadingComponentId } from '../../shared/enums/loading-component-id'; diff --git a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.spec.ts b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.spec.ts index aad88c557..4a7fde2c6 100644 --- a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.spec.ts +++ b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.spec.ts @@ -1,6 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { LoadingSpinnerComponent } from './loading-spinner.component'; +import { LoadingComponentId } from '../../enums/loading-component-id'; describe('LoadingSpinnerComponent', () => { let component: LoadingSpinnerComponent; @@ -16,25 +17,30 @@ describe('LoadingSpinnerComponent', () => { fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); - }); + it('should create', () => expect(component).toBeTruthy()); - it('should apply position-fixed class when isFixPosition is true', () => { - const containerElement = fixture.debugElement.query(By.css('.spinner-container')); - expect(containerElement.nativeElement.classList.contains('position-fixed')).toBe(true); - expect(containerElement.nativeElement.classList.contains('position-absolute')).toBe(false); + it('should display when isLoading state is true', () => { + component.key = LoadingComponentId.LANDING_PAGE; + component.loadingService.showLoading(LoadingComponentId.LANDING_PAGE); + fixture.detectChanges(); + expect(component.isLoading()).toBeTrue(); }); - it('should apply position-absolute class when isFixPosition is false', () => { + it('should display when isLoading state is false', () => { + component.key = LoadingComponentId.LANDING_PAGE; + component.loadingService.hideLoading(LoadingComponentId.LANDING_PAGE); fixture.detectChanges(); - const containerElement = fixture.debugElement.query(By.css('.spinner-container')); - expect(containerElement.nativeElement.classList.contains('position-absolute')).toBe(true); - expect(containerElement.nativeElement.classList.contains('position-fixed')).toBe(false); + expect(component.isLoading()).toBeFalse(); }); - it('should contain a spinner-border element', () => { - const spinnerElement = fixture.debugElement.query(By.css('.spinner-border')); - expect(spinnerElement).toBeTruthy(); + it('container class should come from input', () => { + component.key = LoadingComponentId.LANDING_PAGE; + component.containerClasses = 'spinner-container'; + component.loadingService.showLoading(LoadingComponentId.LANDING_PAGE); + fixture.detectChanges(); + const containerElement = fixture.debugElement.query( + By.css('.spinner-container') + ); + expect(containerElement.nativeElement).toBeTruthy(); }); }); diff --git a/marketplace-ui/src/app/shared/mocks/mock-data.ts b/marketplace-ui/src/app/shared/mocks/mock-data.ts index cf319dc7e..382329eff 100644 --- a/marketplace-ui/src/app/shared/mocks/mock-data.ts +++ b/marketplace-ui/src/app/shared/mocks/mock-data.ts @@ -1,7 +1,9 @@ +import { FeedbackApiResponse } from '../models/apis/feedback-response.model'; import { ProductApiResponse } from '../models/apis/product-response.model'; import { ExternalDocument } from '../models/external-document.model'; import { ProductDetail } from '../models/product-detail.model'; import { ProductModuleContent } from '../models/product-module-content.model'; +import { StarRatingCounting } from '../models/star-rating-counting.model'; export const MOCK_PRODUCTS = { _embedded: { @@ -226,7 +228,8 @@ export const MOCK_CRON_JOB_PRODUCT_DETAIL: ProductDetail = { de: 'Das Cron-Job-Utility übernimmt die automatische Verwaltung deiner zeitgesteuerten Aufgaben.', en: 'Cron Job Utility handles your scheduled jobs autonomously.' }, - logoUrl: 'https://raw.githubusercontent.com/axonivy-market/market/feature/MARP-463-Multilingualism-for-Website/market/utils/cronjob/logo.png', + logoUrl: + 'https://raw.githubusercontent.com/axonivy-market/market/feature/MARP-463-Multilingualism-for-Website/market/utils/cronjob/logo.png', type: 'util', tags: ['utils'], vendor: 'Axon Ivy AG', @@ -234,7 +237,8 @@ export const MOCK_CRON_JOB_PRODUCT_DETAIL: ProductDetail = { newestReleaseVersion: 'v10.0.4', cost: 'Free', sourceUrl: 'https://github.com/axonivy-market/cronjob', - statusBadgeUrl: 'https://github.com/axonivy-market/cronjob/actions/workflows/ci.yml/badge.svg', + statusBadgeUrl: + 'https://github.com/axonivy-market/cronjob/actions/workflows/ci.yml/badge.svg', language: 'English', industry: 'Cross-Industry', compatibility: '10.0+', @@ -246,10 +250,10 @@ export const MOCK_CRON_JOB_PRODUCT_DETAIL: ProductDetail = { en: '**Cron Job** is a job-firing schedule that recurs based on calendar-like notions.\n\nThe [Quartz framework](http://www.quartz-scheduler.org/) is used as underlying scheduler framework.\n\nWith Cron Job, you can specify firing-schedules such as “every Friday at noon”, or “every weekday and 9:30 am”, or even “every 5 minutes between 9:00 am and 10:00 am on every Monday, Wednesday and Friday during January”.\n\nFor more details about Cron Expressions please refer to [Lesson 6: CronTrigger](http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/tutorial-lesson-06.html)' }, setup: { - en: 'No special setup is needed for this demo. Only start the Engine and watch out the logging which will be updated every 5 seconds with the following logging entry:\n\n```\n\nCron Job ist started at: 2023-01-27 10:43:20.\n\n```', + en: 'No special setup is needed for this demo. Only start the Engine and watch out the logging which will be updated every 5 seconds with the following logging entry:\n\n```\n\nCron Job ist started at: 2023-01-27 10:43:20.\n\n```' }, demo: { - en: 'In this demo, the CronByGlobalVariableTriggerStartEventBean is defined as the Java class to be executed in the Ivy Program Start element.\n\n![Program Start Element screenshot](https://raw.githubusercontent.com/axonivy-market/cronjob/v10.0.4/cronjob-product/ProgramStartElement.png)\n\nThis bean gets a cron expression via the variable defined as Cron expression and it will schedule by using the expression.\n\n![custom editor UI screenshot](https://raw.githubusercontent.com/axonivy-market/cronjob/v10.0.4/cronjob-product/customEditorUI.png)\n\nFor this demo, the Cron expression is defining the time to start the cron that simply fires every 5 seconds.\n\n```\n\n demoStartCronPattern: 0/5 * * * * ?\n\n```', + en: 'In this demo, the CronByGlobalVariableTriggerStartEventBean is defined as the Java class to be executed in the Ivy Program Start element.\n\n![Program Start Element screenshot](https://raw.githubusercontent.com/axonivy-market/cronjob/v10.0.4/cronjob-product/ProgramStartElement.png)\n\nThis bean gets a cron expression via the variable defined as Cron expression and it will schedule by using the expression.\n\n![custom editor UI screenshot](https://raw.githubusercontent.com/axonivy-market/cronjob/v10.0.4/cronjob-product/customEditorUI.png)\n\nFor this demo, the Cron expression is defining the time to start the cron that simply fires every 5 seconds.\n\n```\n\n demoStartCronPattern: 0/5 * * * * ?\n\n```' }, isDependency: true, name: 'cron job', @@ -280,7 +284,8 @@ export const MOCK_PRODUCT_DETAIL: ProductDetail = { de: "TODO Atlassian's Jira connector lets you track issues directly from the Axon Ivy platform." }, installationCount: 1, - logoUrl: 'https://raw.githubusercontent.com/axonivy-market/market/master/market/connector/jira/logo.png', + logoUrl: + 'https://raw.githubusercontent.com/axonivy-market/market/master/market/connector/jira/logo.png', type: 'connector', tags: ['helper'], vendor: 'FROX AG', @@ -289,7 +294,8 @@ export const MOCK_PRODUCT_DETAIL: ProductDetail = { newestReleaseVersion: 'v10.0.0', cost: 'Free', sourceUrl: 'https://github.com/axonivy-market/jira-connector', - statusBadgeUrl: 'https://github.com/axonivy-market/jira-connector/actions/workflows/ci.yml/badge.svg', + statusBadgeUrl: + 'https://github.com/axonivy-market/jira-connector/actions/workflows/ci.yml/badge.svg', language: 'English', industry: 'Cross-Industry', compatibility: '9.2+', @@ -300,17 +306,17 @@ export const MOCK_PRODUCT_DETAIL: ProductDetail = { en: "Axon Ivy's [Atlassian Jira Connector ](https://www.atlassian.com/software/jira) gives you full power to track issues within your process work. The connector:\n\n- Features three main functionalities (create comment, create issue, and get issue).\n- Provides access to the core API of Atlassian Jira.\n- Supports you with an easy-to-copy demo implementation to reduce your integration effort.\n- Enables low code citizen developers to integrate issue tracking tools without writing a single line of code." }, setup: { - en: 'Open the `Config/variables.yaml` in your Axon Ivy Designer and paste the\ncode below and adjust the values to your environment.\n\n```\nVariables:\n\n jira-connector:\n \n # Url to the Jira server\n Url: "https://localhost"\n\n # Username to connect to the Jira server\n Username: "admin"\n\n # Password to connect to the Jira server\n Password: "1234"\n```', + en: 'Open the `Config/variables.yaml` in your Axon Ivy Designer and paste the\ncode below and adjust the values to your environment.\n\n```\nVariables:\n\n jira-connector:\n \n # Url to the Jira server\n Url: "https://localhost"\n\n # Username to connect to the Jira server\n Username: "admin"\n\n # Password to connect to the Jira server\n Password: "1234"\n```' }, demo: { - en: '![jira-connector Demo 1](https://raw.githubusercontent.com/axonivy-market/jira-connector/v10.0.0/jira-connector-product/images/create-issue.png "Create Jira issue")\n![jira-connector Demo 2](https://raw.githubusercontent.com/axonivy-market/jira-connector/v10.0.0/jira-connector-product/images/create-comment.png "Craete Jira comment")', + en: '![jira-connector Demo 1](https://raw.githubusercontent.com/axonivy-market/jira-connector/v10.0.0/jira-connector-product/images/create-issue.png "Create Jira issue")\n![jira-connector Demo 2](https://raw.githubusercontent.com/axonivy-market/jira-connector/v10.0.0/jira-connector-product/images/create-comment.png "Craete Jira comment")' }, isDependency: true, name: 'Jira Connector', groupId: 'com.axonivy.connector.jira', artifactId: 'jira-connector', type: 'iar', - productId: 'jira-connector', + productId: 'jira-connector' }, mavenDropins: false, _links: { @@ -329,3 +335,30 @@ export const MOCK_EXTERNAL_DOCUMENT: ExternalDocument = { artifactName: 'Portal Guide', relativeLink: '/market-cache/portal/portal-guide/10.0.0/doc/index.html' }; + +export const MOCK_FEEDBACK_API_RESPONSE: FeedbackApiResponse = { + _embedded: { + feedbacks: [ + { + content: 'cool stuff', + rating: 5, + productId: 'portal' + } + ] + }, + _links: { + self: { href: '/feedbacks' } + }, + page: { + size: 10, + totalElements: 1, + totalPages: 1, + number: 0 + } +}; + +export const MOCK_RATING_STAR: StarRatingCounting[] = [ + { starRating: 0 }, + { starRating: 4 }, + { starRating: 3 } +]; diff --git a/marketplace-ui/src/app/shared/services/app-modal.service.ts b/marketplace-ui/src/app/shared/services/app-modal.service.ts index 999b2d5f7..332a285b8 100644 --- a/marketplace-ui/src/app/shared/services/app-modal.service.ts +++ b/marketplace-ui/src/app/shared/services/app-modal.service.ts @@ -10,7 +10,7 @@ import { SuccessDialogComponent } from '../../modules/product/product-detail/pro export class AppModalService { private readonly modalService = inject(NgbModal); - openShowFeedbacksDialog() { + openShowFeedbacksDialog(): void { this.modalService.open(ShowFeedbacksDialogComponent, { centered: true, modalDialogClass: 'show-feedbacks-modal-dialog', @@ -18,7 +18,7 @@ export class AppModalService { }); } - openAddFeedbackDialog() { + openAddFeedbackDialog(): Promise { const addFeedbackDialog = this.modalService.open( AddFeedbackDialogComponent, { @@ -30,7 +30,7 @@ export class AppModalService { return addFeedbackDialog.result; } - openSuccessDialog() { + openSuccessDialog(): void { this.modalService.open(SuccessDialogComponent, { fullscreen: 'md', centered: true, From ec32a56641cfa26ef81c5796093b120e37fae4d6 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Tue, 17 Dec 2024 14:08:08 +0700 Subject: [PATCH 18/26] format code --- .../app/core/interceptors/api.interceptor.ts | 5 +--- .../product-feedback.service.spec.ts | 30 +++++-------------- .../product-feedback.service.ts | 4 +-- ...roduct-detail-information-tab.component.ts | 6 ++-- ...oduct-detail-version-action.component.html | 5 ---- ...ct-detail-version-action.component.spec.ts | 4 ++- 6 files changed, 14 insertions(+), 40 deletions(-) diff --git a/marketplace-ui/src/app/core/interceptors/api.interceptor.ts b/marketplace-ui/src/app/core/interceptors/api.interceptor.ts index 44cc28a43..e59b5e72f 100644 --- a/marketplace-ui/src/app/core/interceptors/api.interceptor.ts +++ b/marketplace-ui/src/app/core/interceptors/api.interceptor.ts @@ -7,10 +7,7 @@ import { environment } from '../../../environments/environment'; import { inject } from '@angular/core'; import { catchError, EMPTY, finalize } from 'rxjs'; import { Router } from '@angular/router'; -import { - ERROR_CODES, - ERROR_PAGE_PATH -} from '../../shared/constants/common.constant'; +import { ERROR_CODES, ERROR_PAGE_PATH } from '../../shared/constants/common.constant'; import { LoadingService } from '../services/loading/loading.service'; export const REQUEST_BY = 'X-Requested-By'; diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.spec.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.spec.ts index d326b0869..817f6cfbb 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.spec.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.spec.ts @@ -62,26 +62,20 @@ describe('ProductFeedbackService', () => { it('should initialize feedbacks', () => { const mockResponse = { - _embedded: { - feedbacks: [{ content: 'Great product!', rating: 5, productId: '123' }] - }, + _embedded: { feedbacks: [{ content: 'Great product!', rating: 5, productId: '123' }] }, page: { totalPages: 2, totalElements: 5 } }; productDetailService.productId.and.returnValue('123'); service.fetchFeedbacks(); - const req = httpMock.expectOne( - 'api/feedback/product/123?page=0&size=8&sort=newest' - ); + const req = httpMock.expectOne( 'api/feedback/product/123?page=0&size=8&sort=newest' ); expect(req.request.method).toBe('GET'); req.flush(mockResponse); expect(service.totalPages()).toBe(2); expect(service.totalElements()).toBe(5); - expect(service.feedbacks()).toEqual([ - { content: 'Great product!', rating: 5, productId: '123' } - ]); + expect(service.feedbacks()).toEqual([{ content: 'Great product!', rating: 5, productId: '123' }]); }); it('should load more feedbacks', () => { @@ -94,24 +88,14 @@ describe('ProductFeedbackService', () => { productDetailService.productId.and.returnValue('123'); service.fetchFeedbacks(); - const initReq = httpMock.expectOne( - 'api/feedback/product/123?page=0&size=8&sort=newest' - ); - initReq.flush({ - _embedded: { feedbacks: initialFeedback }, - page: { totalPages: 2, totalElements: 5 } - }); + const initReq = httpMock.expectOne( 'api/feedback/product/123?page=0&size=8&sort=newest' ); + initReq.flush({ _embedded: { feedbacks: initialFeedback }, page: { totalPages: 2, totalElements: 5 } }); service.loadMoreFeedbacks(); - const loadMoreReq = httpMock.expectOne( - 'api/feedback/product/123?page=1&size=8&sort=newest' - ); + const loadMoreReq = httpMock.expectOne( 'api/feedback/product/123?page=1&size=8&sort=newest' ); loadMoreReq.flush({ _embedded: { feedbacks: additionalFeedback } }); - expect(service.feedbacks()).toEqual([ - ...initialFeedback, - ...additionalFeedback - ]); + expect(service.feedbacks()).toEqual([...initialFeedback, ...additionalFeedback]); }); it('should change sort and fetch feedbacks', () => { diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.ts index de118fb2a..a3182eac8 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.ts @@ -100,9 +100,7 @@ export class ProductFeedbackService { return this.http .get(requestURL, { params: requestParams, - context: new HttpContext() - .set(SkipLoading, true) - .set(ForwardingError, true) + context: new HttpContext().set(SkipLoading, true).set(ForwardingError, true) }) .pipe( tap(response => { diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts index 9cfc2a0ef..6c7a8da60 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts @@ -47,8 +47,7 @@ export class ProductDetailInformationTabComponent implements OnChanges { ngOnChanges(changes: SimpleChanges): void { let version = ''; const changedSelectedVersion = changes[SELECTED_VERSION]; - if ( - changedSelectedVersion && + if ( changedSelectedVersion && changedSelectedVersion.currentValue === changedSelectedVersion.previousValue ) { @@ -98,8 +97,7 @@ export class ProductDetailInformationTabComponent implements OnChanges { // To ensure the function always returns a boolean, you can explicitly coerce the result into a boolean using the !! operator or default it to false // Adding !! in case of changedProduct is undefined, it will return false instead of returning undefined isProductChanged(changedProduct: SimpleChange) { - return !!( - changedProduct?.previousValue && + return !!( changedProduct?.previousValue && Object.keys(changedProduct.previousValue).length > 0 && changedProduct.currentValue !== changedProduct.previousValue ); 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 361814c9a..aecd97f8b 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 @@ -131,17 +131,12 @@ (click)="onUpdateInstallationCountForDesigner()" onClick="function installInDesigner(event) { const selectedItemElement = document.querySelector('.install-designer-dropdown'); if (selectedItemElement) { -<<<<<<< HEAD - const metaDataJsonUrl = selectedItemElement.getAttribute('metaDataJsonUrl'); - install(metaDataJsonUrl); -======= const metaDataJsonUrl = selectedItemElement.getAttribute('metaDataJsonUrl'); try { install(metaDataJsonUrl); } catch (error) { event.stopImmediatePropagation(); } ->>>>>>> develop } } installInDesigner(event);" [ngClass]="themeService.isDarkMode() ? 'btn-light' : 'btn-primary'"> diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.spec.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.spec.ts index f7f156a7b..f0a81ac4c 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.spec.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.spec.ts @@ -15,7 +15,9 @@ import { ProductDetailActionType } from '../../../../shared/enums/product-detail import { MATOMO_TRACKING_ENVIRONMENT } from '../../../../shared/constants/matomo.constant'; class MockElementRef implements ElementRef { - nativeElement = { contains: jasmine.createSpy('contains') }; + nativeElement = { + contains: jasmine.createSpy('contains') + }; } describe('ProductDetailVersionActionComponent', () => { From c523f9e55d8ce920afd45cf5861ddb05f3fbaa4a Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Tue, 17 Dec 2024 14:21:49 +0700 Subject: [PATCH 19/26] update code --- ...oduct-detail-version-action.component.html | 0 .../app/core/interceptors/api.interceptor.ts | 2 +- ...ct-detail-version-action.component.spec.ts | 38 +++++-------------- .../src/app/shared/mocks/mock-data.ts | 8 +--- 4 files changed, 11 insertions(+), 37 deletions(-) delete mode 100644 marketplace-service/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.html diff --git a/marketplace-service/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.html b/marketplace-service/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.html deleted file mode 100644 index e69de29bb..000000000 diff --git a/marketplace-ui/src/app/core/interceptors/api.interceptor.ts b/marketplace-ui/src/app/core/interceptors/api.interceptor.ts index e59b5e72f..52b9213cd 100644 --- a/marketplace-ui/src/app/core/interceptors/api.interceptor.ts +++ b/marketplace-ui/src/app/core/interceptors/api.interceptor.ts @@ -4,11 +4,11 @@ import { HttpInterceptorFn } from '@angular/common/http'; import { environment } from '../../../environments/environment'; +import { LoadingService } from '../services/loading/loading.service'; import { inject } from '@angular/core'; import { catchError, EMPTY, finalize } from 'rxjs'; import { Router } from '@angular/router'; import { ERROR_CODES, ERROR_PAGE_PATH } from '../../shared/constants/common.constant'; -import { LoadingService } from '../services/loading/loading.service'; export const REQUEST_BY = 'X-Requested-By'; export const IVY = 'marketplace-website'; diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.spec.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.spec.ts index f0a81ac4c..f1c431ff1 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.spec.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.spec.ts @@ -15,7 +15,7 @@ import { ProductDetailActionType } from '../../../../shared/enums/product-detail import { MATOMO_TRACKING_ENVIRONMENT } from '../../../../shared/constants/matomo.constant'; class MockElementRef implements ElementRef { - nativeElement = { + nativeElement = { contains: jasmine.createSpy('contains') }; } @@ -292,64 +292,44 @@ describe('ProductDetailVersionActionComponent', () => { component.versions.set(['1.0', '1.1']); fixture.detectChanges(); component.getVersionInDesigner(); - expect( - productServiceMock.sendRequestToGetProductVersionsForDesigner - ).not.toHaveBeenCalled(); + expect(productServiceMock.sendRequestToGetProductVersionsForDesigner).not.toHaveBeenCalled(); }); it('should call productService and update versions if versions are empty', () => { const productId = '123'; component.versions.set([]); const mockVersions = [{ version: '1.0' }, { version: '2.0' }]; - productServiceMock.sendRequestToGetProductVersionsForDesigner.and.returnValue( - of(mockVersions) - ); + productServiceMock.sendRequestToGetProductVersionsForDesigner.and.returnValue(of(mockVersions)); // Act component.getVersionInDesigner(); // Assert - expect( - productServiceMock.sendRequestToGetProductVersionsForDesigner - ).toHaveBeenCalledWith(productId); + expect(productServiceMock.sendRequestToGetProductVersionsForDesigner).toHaveBeenCalledWith(productId); expect(component.versions()).toEqual(['Version 1.0', 'Version 2.0']); }); it('should handle empty response from productService', () => { component.versions.set([]); - productServiceMock.sendRequestToGetProductVersionsForDesigner.and.returnValue( - of([]) - ); + productServiceMock.sendRequestToGetProductVersionsForDesigner.and.returnValue(of([])); // Act component.getVersionInDesigner(); // Assert - expect( - productServiceMock.sendRequestToGetProductVersionsForDesigner - ).toHaveBeenCalledWith(productId); + expect(productServiceMock.sendRequestToGetProductVersionsForDesigner).toHaveBeenCalledWith(productId); expect(component.versions()).toEqual([]); }); it('should return the correct tracking environment based on the action type', () => { const testCases = [ - { - actionType: ProductDetailActionType.STANDARD, - expected: MATOMO_TRACKING_ENVIRONMENT.standard - }, - { - actionType: ProductDetailActionType.DESIGNER_ENV, - expected: MATOMO_TRACKING_ENVIRONMENT.designerEnv - }, - { - actionType: ProductDetailActionType.CUSTOM_SOLUTION, - expected: MATOMO_TRACKING_ENVIRONMENT.customSolution - } + { actionType: ProductDetailActionType.STANDARD, expected: MATOMO_TRACKING_ENVIRONMENT.standard }, + { actionType: ProductDetailActionType.DESIGNER_ENV, expected: MATOMO_TRACKING_ENVIRONMENT.designerEnv }, + { actionType: ProductDetailActionType.CUSTOM_SOLUTION, expected: MATOMO_TRACKING_ENVIRONMENT.customSolution } ]; testCases.forEach(({ actionType, expected }) => { component.actionType = actionType; - const result = component.getTrackingEnvironmentBasedOnActionType(); expect(result).toBe(expected); }); diff --git a/marketplace-ui/src/app/shared/mocks/mock-data.ts b/marketplace-ui/src/app/shared/mocks/mock-data.ts index 382329eff..e58cec921 100644 --- a/marketplace-ui/src/app/shared/mocks/mock-data.ts +++ b/marketplace-ui/src/app/shared/mocks/mock-data.ts @@ -355,10 +355,4 @@ export const MOCK_FEEDBACK_API_RESPONSE: FeedbackApiResponse = { totalPages: 1, number: 0 } -}; - -export const MOCK_RATING_STAR: StarRatingCounting[] = [ - { starRating: 0 }, - { starRating: 4 }, - { starRating: 3 } -]; +}; \ No newline at end of file From d5985a97ef663ab0a23a53f89eac8c3485f3b226 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Tue, 17 Dec 2024 14:49:25 +0700 Subject: [PATCH 20/26] update code --- .../src/app/core/services/loading/loading.service.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/marketplace-ui/src/app/core/services/loading/loading.service.ts b/marketplace-ui/src/app/core/services/loading/loading.service.ts index ea0a1fa11..4d5829825 100644 --- a/marketplace-ui/src/app/core/services/loading/loading.service.ts +++ b/marketplace-ui/src/app/core/services/loading/loading.service.ts @@ -5,7 +5,6 @@ import { Injectable, signal } from '@angular/core'; }) export class LoadingService { loadingStates = signal<{ [key: string]: boolean }>({}); - activeCallCount = signal(0); private setLoading(componentId: string, isLoading: boolean): void { this.loadingStates.update(states => { @@ -16,14 +15,10 @@ export class LoadingService { } showLoading(componentId: string): void { - this.activeCallCount.set(this.activeCallCount() + 1); this.setLoading(componentId, true); } hideLoading(componentId: string) { - if (this.activeCallCount() > 0) { - this.activeCallCount.set(this.activeCallCount() - 1); - } this.setLoading(componentId, false); } } From 9b9c23763f2f6c9c3eeb4ec739747a02467e4150 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Tue, 17 Dec 2024 18:09:38 +0700 Subject: [PATCH 21/26] handle sonnar --- .../product/product-detail/product-detail.component.ts | 3 +-- .../components/loading-spinner/loading-spinner.component.ts | 4 ++-- marketplace-ui/src/app/shared/pipes/product-type.pipe.ts | 6 +++++- marketplace-ui/src/app/shared/services/app-modal.service.ts | 2 +- .../src/app/shared/services/routing.query.param.service.ts | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) 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 1ebd42e82..5efe150f4 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 @@ -173,7 +173,6 @@ export class ProductDetailComponent { this.updateDropdownSelection(); this.checkMediaSize(); this.showPopup = res.params['showPopup'] === 'true'; - console.log(this.showPopup); if (this.showPopup && this.authService.getToken()) { this.appModalService @@ -422,7 +421,7 @@ export class ProductDetailComponent { } } - getDisplayedTabsSignal(): ItemDropdown[] { + getDisplayedTabsSignal(): ItemDropdown[] { this.updateWebBrowserTitle(); const displayedTabs: ItemDropdown[] = []; for (const detailTab of this.detailTabs) { diff --git a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts index a291897e3..cd3cc6d8b 100644 --- a/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts +++ b/marketplace-ui/src/app/shared/components/loading-spinner/loading-spinner.component.ts @@ -8,8 +8,8 @@ import { LoadingService } from '../../../core/services/loading/loading.service'; styleUrl: './loading-spinner.component.scss' }) export class LoadingSpinnerComponent { - @Input() key: string = ''; - @Input() containerClasses: string = ''; + @Input() key = ''; + @Input() containerClasses = ''; loadingService = inject(LoadingService); isLoading = computed(() => this.loadingService.loadingStates()[this.key]); } diff --git a/marketplace-ui/src/app/shared/pipes/product-type.pipe.ts b/marketplace-ui/src/app/shared/pipes/product-type.pipe.ts index f1a573d04..bdba94750 100644 --- a/marketplace-ui/src/app/shared/pipes/product-type.pipe.ts +++ b/marketplace-ui/src/app/shared/pipes/product-type.pipe.ts @@ -6,6 +6,10 @@ import { Pipe, PipeTransform } from '@angular/core'; }) export class ProductTypePipe implements PipeTransform { transform(type: string, _args?: []): string { - return type ? `common.filter.value.${type}` : ''; + let i18nKey = ''; + if (type) { + i18nKey = `common.filter.value.${type}`; + } + return i18nKey; } } diff --git a/marketplace-ui/src/app/shared/services/app-modal.service.ts b/marketplace-ui/src/app/shared/services/app-modal.service.ts index 332a285b8..12677724f 100644 --- a/marketplace-ui/src/app/shared/services/app-modal.service.ts +++ b/marketplace-ui/src/app/shared/services/app-modal.service.ts @@ -18,7 +18,7 @@ export class AppModalService { }); } - openAddFeedbackDialog(): Promise { + openAddFeedbackDialog() { const addFeedbackDialog = this.modalService.open( AddFeedbackDialogComponent, { diff --git a/marketplace-ui/src/app/shared/services/routing.query.param.service.ts b/marketplace-ui/src/app/shared/services/routing.query.param.service.ts index 47ccc4eee..d221636b6 100644 --- a/marketplace-ui/src/app/shared/services/routing.query.param.service.ts +++ b/marketplace-ui/src/app/shared/services/routing.query.param.service.ts @@ -76,6 +76,6 @@ export class RoutingQueryParamService { getNavigationStartEvent(): Observable { return this.router.events.pipe( filter(event => event instanceof NavigationStart) - ) as Observable; + ); } } From 76fa3b51aafdb86d8b2fa234f0e0f1fe7ca71b6b Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Tue, 17 Dec 2024 18:14:25 +0700 Subject: [PATCH 22/26] update type --- .../services/routing.query.param.service.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/marketplace-ui/src/app/shared/services/routing.query.param.service.ts b/marketplace-ui/src/app/shared/services/routing.query.param.service.ts index d221636b6..4d63e7777 100644 --- a/marketplace-ui/src/app/shared/services/routing.query.param.service.ts +++ b/marketplace-ui/src/app/shared/services/routing.query.param.service.ts @@ -1,4 +1,4 @@ -import { computed, Injectable, signal } from '@angular/core'; +import { computed, Injectable, Signal, signal } from '@angular/core'; import { DESIGNER_SESSION_STORAGE_VARIABLE } from '../constants/common.constant'; import { Router, Params, NavigationStart } from '@angular/router'; import { Observable } from 'rxjs'; @@ -11,9 +11,7 @@ export class RoutingQueryParamService { isDesignerEnv = computed(() => this.isDesigner()); designerVersion = signal(''); - constructor( - private readonly router: Router - ) { + constructor(private readonly router: Router) { this.getNavigationStartEvent().subscribe(() => { if (!this.isDesigner()) { this.isDesigner.set( @@ -25,7 +23,7 @@ export class RoutingQueryParamService { }); } - checkSessionStorageForDesignerVersion(params: Params) { + checkSessionStorageForDesignerVersion(params: Params): void { const versionParam = params[DESIGNER_SESSION_STORAGE_VARIABLE.ivyVersionParamName]; if (versionParam !== undefined) { @@ -37,7 +35,7 @@ export class RoutingQueryParamService { } } - checkSessionStorageForDesignerEnv(params: Params) { + checkSessionStorageForDesignerEnv(params: Params): void { const ivyViewerParam = params[DESIGNER_SESSION_STORAGE_VARIABLE.ivyViewerParamName]; if ( @@ -51,7 +49,7 @@ export class RoutingQueryParamService { } } - getDesignerVersionFromSessionStorage() { + getDesignerVersionFromSessionStorage(): string { if (this.designerVersion() === '') { this.designerVersion.set( sessionStorage.getItem( @@ -62,7 +60,7 @@ export class RoutingQueryParamService { return this.designerVersion(); } - isDesignerViewer() { + isDesignerViewer(): boolean { if (!this.isDesigner()) { this.isDesigner.set( sessionStorage.getItem( @@ -73,9 +71,9 @@ export class RoutingQueryParamService { return this.isDesigner(); } - getNavigationStartEvent(): Observable { + getNavigationStartEvent() { return this.router.events.pipe( filter(event => event instanceof NavigationStart) - ); + ) as Observable; } } From 8e76857a54b6a9b46eff571218fed8159ecdef19 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Tue, 17 Dec 2024 18:18:16 +0700 Subject: [PATCH 23/26] handle sonnar --- .../src/app/shared/services/routing.query.param.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketplace-ui/src/app/shared/services/routing.query.param.service.ts b/marketplace-ui/src/app/shared/services/routing.query.param.service.ts index 4d63e7777..c07d08dae 100644 --- a/marketplace-ui/src/app/shared/services/routing.query.param.service.ts +++ b/marketplace-ui/src/app/shared/services/routing.query.param.service.ts @@ -1,4 +1,4 @@ -import { computed, Injectable, Signal, signal } from '@angular/core'; +import { computed, Injectable, signal } from '@angular/core'; import { DESIGNER_SESSION_STORAGE_VARIABLE } from '../constants/common.constant'; import { Router, Params, NavigationStart } from '@angular/router'; import { Observable } from 'rxjs'; From 22717c64caa26c4d88e56123f027deffb3880786 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Tue, 17 Dec 2024 18:47:10 +0700 Subject: [PATCH 24/26] update code --- ...oduct-detail-version-action.component.html | 8 +-- ...product-detail-version-action.component.ts | 57 ++++++++++++------- 2 files changed, 41 insertions(+), 24 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 aecd97f8b..3369b6960 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 @@ -47,7 +47,6 @@ @if (isDropDownDisplayed()) {
-
- @if (isArtifactLoading()) { - - } +
}
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 856bf6037..e256b4e27 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 @@ -35,6 +35,8 @@ import { ROUTER } from '../../../../shared/constants/router.constant'; import { MatomoCategory, MatomoAction } from '../../../../shared/enums/matomo-tracking.enum'; import { MATOMO_TRACKING_ENVIRONMENT } from '../../../../shared/constants/matomo.constant'; import { MATOMO_DIRECTIVES } from 'ngx-matomo-client'; +import { LoadingComponentId } from '../../../../shared/enums/loading-component-id'; +import { LoadingService } from '../../../../core/services/loading/loading.service'; const showDevVersionCookieName = 'showDevVersions'; @@ -62,7 +64,7 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { protected ProductDetailActionType = ProductDetailActionType; protected MatomoCategory = MatomoCategory; protected MatomoAction = MatomoAction; - trackedEnvironmentForMatomo = '' + trackedEnvironmentForMatomo = ''; selectedVersion = model(''); versions: WritableSignal = signal([]); @@ -77,12 +79,16 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { artifacts: WritableSignal = signal([]); isDropDownDisplayed = signal(false); - isArtifactLoading = signal(false); + + protected LoadingComponentId = LoadingComponentId; + loadingContainerClasses = + 'd-flex justify-content-center position-absolute align-items-center w-100 h-100 fixed-top rounded overlay-background'; designerVersion = ''; selectedArtifact: string | undefined = ''; selectedArtifactName: string | undefined = ''; versionMap: Map = new Map(); + loadingService = inject(LoadingService); themeService = inject(ThemeService); productService = inject(ProductService); elementRef = inject(ElementRef); @@ -93,7 +99,9 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { router = inject(Router); route = inject(ActivatedRoute); - isDevVersionsDisplayed: WritableSignal = signal(this.getShowDevVersionFromCookie()); + isDevVersionsDisplayed: WritableSignal = signal( + this.getShowDevVersionFromCookie() + ); ngAfterViewInit() { const tooltipTriggerList = [].slice.call( @@ -113,7 +121,11 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { } private getShowDevVersionFromCookie() { - return CommonUtils.getCookieValue(this.cookieService, SHOW_DEV_VERSION, false); + return CommonUtils.getCookieValue( + this.cookieService, + SHOW_DEV_VERSION, + false + ); } private updateSelectedArtifact(version: string) { @@ -130,11 +142,13 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { } addVersionParamToRoute(selectedVersion: string) { - this.router.navigate([], { - relativeTo: this.route, - queryParams: { [ROUTER.VERSION]: selectedVersion }, - queryParamsHandling: 'merge' - }).then(); + this.router + .navigate([], { + relativeTo: this.route, + queryParams: { [ROUTER.VERSION]: selectedVersion }, + queryParamsHandling: 'merge' + }) + .then(); } onSelectVersionInDesigner(version: string) { @@ -149,7 +163,10 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { onShowDevVersion(event: Event) { event.preventDefault(); this.isDevVersionsDisplayed.update(oldValue => !oldValue); - this.cookieService.set(showDevVersionCookieName, this.isDevVersionsDisplayed().toString()); + this.cookieService.set( + showDevVersionCookieName, + this.isDevVersionsDisplayed().toString() + ); this.getVersionWithArtifact(true); } @@ -161,7 +178,7 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { } getVersionWithArtifact(ignoreRouteVersion = false) { - this.isArtifactLoading.set(true); + this.loadingService.showLoading(LoadingComponentId.PRODUCT_VERSION); this.sanitizeDataBeforeFetching(); this.productService .sendRequestToProductDetailVersionAPI( @@ -182,9 +199,11 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { } }); if (this.versions().length !== 0) { - this.onSelectVersion(this.getVersionFromRoute(ignoreRouteVersion) ?? this.versions()[0]); + this.onSelectVersion( + this.getVersionFromRoute(ignoreRouteVersion) ?? this.versions()[0] + ); } - this.isArtifactLoading.set(false); + this.loadingService.hideLoading(LoadingComponentId.PRODUCT_VERSION); }); } @@ -219,18 +238,18 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { } } - sanitizeDataBeforeFetching() { + sanitizeDataBeforeFetching(): void { this.versions.set([]); this.artifacts.set([]); this.selectedArtifact = ''; } - downloadArtifact() { + downloadArtifact(): void { this.onUpdateInstallationCount(); window.open(this.selectedArtifact, '_blank'); } - onUpdateInstallationCount() { + onUpdateInstallationCount(): void { this.productService .sendRequestToUpdateInstallationCount( this.productId, @@ -239,11 +258,11 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { .subscribe((data: number) => this.installationCount.emit(data)); } - onUpdateInstallationCountForDesigner() { + onUpdateInstallationCountForDesigner(): void { this.onUpdateInstallationCount(); } - onNavigateToContactPage() { + onNavigateToContactPage(): void { window.open( `https://www.axonivy.com/marketplace/contact/?market_solutions=${this.productId}`, '_blank' @@ -259,7 +278,7 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { case ProductDetailActionType.CUSTOM_SOLUTION: return MATOMO_TRACKING_ENVIRONMENT.customSolution; default: - return ''; + return ''; } } } From 1b5e37a50fbb8ab210f542937b700a058b241318 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Wed, 18 Dec 2024 16:27:45 +0700 Subject: [PATCH 25/26] handle feedback --- .../product-detail.component.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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 5efe150f4..36c65f9b6 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 @@ -163,8 +163,7 @@ export class ProductDetailComponent { productFeedBack: this.productFeedbackService.getInitFeedbacksObservable(), rating: this.productStarRatingService.getRatingObservable(productId), - userFeedback: this.productFeedbackService.findProductFeedbackOfUser(), - params: this.route.queryParams + userFeedback: this.productFeedbackService.findProductFeedbackOfUser() }).subscribe(res => { this.handleProductDetail(res.productDetail); this.productFeedbackService.handleFeedbackApiResponse( @@ -172,14 +171,15 @@ export class ProductDetailComponent { ); this.updateDropdownSelection(); this.checkMediaSize(); - this.showPopup = res.params['showPopup'] === 'true'; - - if (this.showPopup && this.authService.getToken()) { - this.appModalService - .openAddFeedbackDialog() - .then(() => this.removeQueryParam()) - .catch(() => this.removeQueryParam()); - } + this.route.queryParams.subscribe(params => { + this.showPopup = params['showPopup'] === 'true'; + if (this.showPopup && this.authService.getToken()) { + this.appModalService + .openAddFeedbackDialog() + .then(() => this.removeQueryParam()) + .catch(() => this.removeQueryParam()); + } + }); this.loadingService.hideLoading(LoadingComponentId.DETAIL_PAGE); }); } From d6755f6c755ae194acec0125f51244ca14149442 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Thu, 19 Dec 2024 13:40:47 +0700 Subject: [PATCH 26/26] handle feedback --- .../src/app/core/interceptors/api.interceptor.ts | 10 +--------- .../product-feedback.service.ts | 15 ++++----------- .../product-star-rating.service.ts | 5 +---- .../product-detail/product-detail.component.html | 2 +- .../loading-spinner.component.html | 2 +- 5 files changed, 8 insertions(+), 26 deletions(-) diff --git a/marketplace-ui/src/app/core/interceptors/api.interceptor.ts b/marketplace-ui/src/app/core/interceptors/api.interceptor.ts index 52b9213cd..32c30e316 100644 --- a/marketplace-ui/src/app/core/interceptors/api.interceptor.ts +++ b/marketplace-ui/src/app/core/interceptors/api.interceptor.ts @@ -13,11 +13,6 @@ import { ERROR_CODES, ERROR_PAGE_PATH } from '../../shared/constants/common.cons export const REQUEST_BY = 'X-Requested-By'; export const IVY = 'marketplace-website'; -/** SkipLoading: This option for exclude loading api - * @Example return httpClient.get('apiEndPoint', { context: new HttpContext().set(SkipLoading, true) }) - */ -export const SkipLoading = new HttpContextToken(() => false); - /** ForwardingError: This option for forwarding responce error to the caller * @Example return httpClient.get('apiEndPoint', { context: new HttpContext().set(ForwardingError, true) }) */ @@ -47,9 +42,6 @@ export const apiInterceptor: HttpInterceptorFn = (req, next) => { headers: addIvyHeaders(req.headers) }); - if (!req.context.get(SkipLoading)) { - loadingService.showLoading(req.context.get(LoadingComponent)); - } if (req.context.get(ForwardingError)) { return next(cloneReq); @@ -65,7 +57,7 @@ export const apiInterceptor: HttpInterceptorFn = (req, next) => { return EMPTY; }), finalize(() => { - if (!req.context.get(SkipLoading)) { + if (req.context.get(LoadingComponent)) { loadingService.hideLoading(req.context.get(LoadingComponent)); } }) diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.ts index a3182eac8..6859da78f 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback.service.ts @@ -14,10 +14,7 @@ import { import { catchError, Observable, of, tap, throwError } from 'rxjs'; import { CookieService } from 'ngx-cookie-service'; import { AuthService } from '../../../../../auth/auth.service'; -import { - ForwardingError, - SkipLoading -} from '../../../../../core/interceptors/api.interceptor'; +import { ForwardingError } from '../../../../../core/interceptors/api.interceptor'; import { FeedbackApiResponse } from '../../../../../shared/models/apis/feedback-response.model'; import { Feedback } from '../../../../../shared/models/feedback.model'; import { ProductDetailService } from '../../product-detail.service'; @@ -64,9 +61,7 @@ export class ProductFeedbackService { return this.http .post(FEEDBACK_API_URL, feedback, { headers, - context: new HttpContext() - .set(SkipLoading, true) - .set(ForwardingError, true) + context: new HttpContext().set(ForwardingError, true) }) .pipe( tap(() => { @@ -100,7 +95,7 @@ export class ProductFeedbackService { return this.http .get(requestURL, { params: requestParams, - context: new HttpContext().set(SkipLoading, true).set(ForwardingError, true) + context: new HttpContext().set(ForwardingError, true) }) .pipe( tap(response => { @@ -126,9 +121,7 @@ export class ProductFeedbackService { return this.http .get(requestURL, { params, - context: new HttpContext() - .set(SkipLoading, true) - .set(ForwardingError, true) + context: new HttpContext().set(ForwardingError, true) }) .pipe( tap(feedback => { diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-star-rating-panel/product-star-rating.service.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-star-rating-panel/product-star-rating.service.ts index 893c31cf3..e19805ce8 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-star-rating-panel/product-star-rating.service.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-star-rating-panel/product-star-rating.service.ts @@ -10,7 +10,6 @@ import { import { Observable, tap } from 'rxjs'; import { StarRatingCounting } from '../../../../../shared/models/star-rating-counting.model'; import { ProductDetailService } from '../../product-detail.service'; -import { SkipLoading } from '../../../../../core/interceptors/api.interceptor'; @Injectable({ providedIn: 'root' @@ -59,9 +58,7 @@ export class ProductStarRatingService { getRatingObservable(id: string): Observable { const requestURL = `api/feedback/product/${id}/rating`; return this.http - .get(requestURL, { - context: new HttpContext().set(SkipLoading, true) - }) + .get(requestURL, { context: new HttpContext() }) .pipe( tap(data => { this.sortByStar(data); 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 05aa4f76a..af2b27aac 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 @@ -1,7 +1,7 @@
+