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 b77181b8b..d17b93f4e 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 @@ -21,6 +21,7 @@ import { LanguageService } from '../../../../core/services/language/language.ser import { ItemDropdown } from '../../../../shared/models/item-dropdown.model'; import { ProductDetail } from '../../../../shared/models/product-detail.model'; import { environment } from '../../../../../environments/environment'; +import { VERSION } from '../../../../shared/constants/common.constant'; @Component({ selector: 'app-product-version-action', standalone: true, @@ -166,7 +167,7 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { ) .subscribe(data => { data.forEach(item => { - const version = 'Version '.concat(item.version); + const version = VERSION.displayPrefix.concat(item.version); this.versions.update(currentVersions => [ ...currentVersions, version @@ -187,7 +188,7 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { if (this.versions().length === 0) { this.productService.sendRequestToGetProductVersionsForDesigner(this.productId ).subscribe(data => { - const versionMap = data.map((versionNumber: string) => 'Version '.concat(versionNumber)); + const versionMap = data.map((versionNumber: string) => VERSION.displayPrefix.concat(versionNumber)); this.versions.set(versionMap); }); } 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 99dd292cd..898a4f1e6 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 @@ -58,7 +58,7 @@

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

@@ -116,7 +116,7 @@

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 fbe69c34e..5a6199ffa 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 @@ -140,7 +140,7 @@ describe('ProductDetailComponent', () => { }); it('should call setActiveTab and updateDropdownSelection on onTabChange', () => { - const event ={ value: 'description' }; + const event = { value: 'description' }; spyOn(component, 'setActiveTab'); spyOn(component, 'updateDropdownSelection'); @@ -159,6 +159,15 @@ describe('ProductDetailComponent', () => { expect(component.getContent('description')).toBeTrue(); }); + it('should return false in tab visibility when product module content is missing', () => { + const mockEmptyContent: ProductModuleContent = {} as ProductModuleContent; + component.productModuleContent.set(mockEmptyContent); + expect(component.getContent('description')).toBeFalse(); + expect(component.getContent('demo')).toBeFalse(); + expect(component.getContent('setup')).toBeFalse(); + expect(component.getContent('dependency')).toBeFalse(); + }); + it('should return false for description when it is null or empty', () => { const mockContentWithEmptyDescription: ProductModuleContent = MOCK_PRODUCT_MODULE_CONTENT; @@ -195,6 +204,16 @@ describe('ProductDetailComponent', () => { expect(component.getContent('setup')).toBeFalse(); }); + it('should not display information when product detail is empty', () => { + const mockContentWithEmptySetup: ProductModuleContent = + {} as ProductModuleContent; + component.productModuleContent.set(mockContentWithEmptySetup); + expect(component.isEmptyProductContent()).toBeTrue(); + fixture.detectChanges(); + const description = fixture.debugElement.query(By.css('#description')); + expect(description).toBeFalsy(); + }); + it('should display dropdown horizontally on small viewport', () => { viewport.set(540); const tabGroup = fixture.debugElement.query(By.css('.row-tab')); @@ -297,4 +316,22 @@ describe('ProductDetailComponent', () => { component.onResize(); expect(component.checkMediaSize).toHaveBeenCalled(); }); + + it('should be empty selected version if product detail content is missing', () => { + component.productModuleContent.set({} as ProductModuleContent); + component.selectedVersion = ''; + component.handleProductContentVersion(); + expect(component.selectedVersion).toEqual(''); + }); + + it('should be formated selected version if open in designer', () => { + const mockContent: ProductModuleContent = { + ...MOCK_PRODUCT_MODULE_CONTENT, + tag: '10.0.11' + }; + component.productModuleContent.set(mockContent); + routingQueryParamService.isDesignerEnv.and.returnValue(true); + component.handleProductContentVersion(); + expect(component.selectedVersion).toEqual('Version 10.0.11'); + }); }); 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 20a79379e..232d12d5f 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 @@ -17,7 +17,10 @@ import { CommonModule } from '@angular/common'; import { ProductDetailInformationTabComponent } from './product-detail-information-tab/product-detail-information-tab.component'; import { ProductDetailVersionActionComponent } from './product-detail-version-action/product-detail-version-action.component'; import { ProductDetailMavenContentComponent } from './product-detail-maven-content/product-detail-maven-content.component'; -import { PRODUCT_DETAIL_TABS } from '../../../shared/constants/common.constant'; +import { + PRODUCT_DETAIL_TABS, + VERSION +} from '../../../shared/constants/common.constant'; import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap'; import { LanguageService } from '../../../core/services/language/language.service'; import { MultilingualismPipe } from '../../../shared/pipes/multilingualism.pipe'; @@ -35,6 +38,7 @@ import { RoutingQueryParamService } from '../../../shared/services/routing.query import { CommonDropdownComponent } from '../../../shared/components/common-dropdown/common-dropdown.component'; import { CommonUtils } from '../../../shared/utils/common.utils'; import { ItemDropdown } from '../../../shared/models/item-dropdown.model'; +import { ProductTypePipe } from '../../../shared/pipes/product-type.pipe'; export interface DetailTab { activeClass: string; @@ -58,6 +62,7 @@ const DEFAULT_ACTIVE_TAB = 'description'; ProductDetailMavenContentComponent, NgbNavModule, MultilingualismPipe, + ProductTypePipe, ProductDetailFeedbackComponent, ProductInstallationCountActionComponent, ProductTypeIconPipe, @@ -107,7 +112,6 @@ export class ProductDetailComponent { this.updateDropdownSelection(); } - constructor() { this.scrollToTop(); this.resizeObserver = new ResizeObserver(() => { @@ -122,17 +126,11 @@ export class ProductDetailComponent { this.getProductById(productId).subscribe(productDetail => { this.productDetail.set(productDetail); this.productModuleContent.set(productDetail.productModuleContent); - if (this.routingQueryParamService.isDesignerEnv()) { - this.selectedVersion = 'Version '.concat(this.convertTagToVersion((productDetail.productModuleContent.tag))); - } this.detailTabsForDropdown = this.getNotEmptyTabs(); this.productDetailService.productNames.set(productDetail.names); localStorage.removeItem(STORAGE_ITEM); this.installationCount = productDetail.installationCount; - this.selectedVersion = this.productModuleContent().tag; - if (this.selectedVersion.startsWith('v')) { - this.selectedVersion = this.selectedVersion.substring(1); - } + this.handleProductContentVersion(); }); this.productFeedbackService.initFeedbacks(); this.productStarRatingService.fetchData(); @@ -145,6 +143,18 @@ export class ProductDetailComponent { this.updateDropdownSelection(); } + handleProductContentVersion() { + if (this.isEmptyProductContent()) { + return; + } + this.selectedVersion = this.convertTagToVersion( + this.productModuleContent().tag + ); + if (this.routingQueryParamService.isDesignerEnv()) { + this.selectedVersion = VERSION.displayPrefix.concat(this.selectedVersion); + } + } + scrollToTop() { window.scrollTo({ left: 0, top: 0, behavior: 'instant' }); } @@ -177,20 +187,28 @@ export class ProductDetailComponent { } getContent(value: string): boolean { + if (this.isEmptyProductContent()) { + return false; + } const content = this.productModuleContent(); const conditions: { [key: string]: boolean } = { description: content.description !== null, demo: content.demo !== null, - setup: content.setup !== null , + setup: content.setup !== null, dependency: content.isDependency }; return conditions[value] ?? false; } + isEmptyProductContent(): boolean { + const content = this.productModuleContent(); + return !content || Object.keys(content).length === 0; + } + loadDetailTabs(selectedVersion: string) { let version = selectedVersion || this.productDetail().newestReleaseVersion; - version = version.replace("Version ","") + version = version.replace(VERSION.displayPrefix, ''); this.productService .getProductDetailsWithVersion(this.productDetail().id, version) .subscribe(updatedProductDetail => { @@ -240,10 +258,11 @@ export class ProductDetailComponent { @HostListener('document:click', ['$event']) handleClickOutside(event: MouseEvent) { + const formSelect = + this.elementRef.nativeElement.querySelector('.form-select'); if ( - !this.elementRef.nativeElement - .querySelector('.form-select') - .contains(event.target) && + formSelect && + !formSelect.contains(event.target) && this.isTabDropdownShown() ) { this.onTabDropdownShown(); @@ -288,8 +307,8 @@ export class ProductDetailComponent { return this.detailTabsForDropdown.filter(tab => this.getContent(tab.value)); } - convertTagToVersion(tag: string) : string { - if (tag !== '' && tag.startsWith('v')){ + convertTagToVersion(tag: string): string { + if (tag !== '' && tag.startsWith(VERSION.tagPrefix)) { return tag.substring(1); } return tag; diff --git a/marketplace-ui/src/app/modules/product/product-filter/product-filter.component.html b/marketplace-ui/src/app/modules/product/product-filter/product-filter.component.html index 5338fb5ae..bc9125742 100644 --- a/marketplace-ui/src/app/modules/product/product-filter/product-filter.component.html +++ b/marketplace-ui/src/app/modules/product/product-filter/product-filter.component.html @@ -10,9 +10,9 @@ 'border-light text-light': themeService.isDarkMode(), 'border-dark text-dark': !themeService.isDarkMode(), 'bg-light text-dark': - selectedTypeLabel === type.value && themeService.isDarkMode(), + selectedTypeLabel === type.label && themeService.isDarkMode(), 'bg-primary border-0 text-light': - selectedTypeLabel === type.value && !themeService.isDarkMode() + selectedTypeLabel === type.label && !themeService.isDarkMode() }">

{{ type.label | translate }} diff --git a/marketplace-ui/src/app/shared/constants/common.constant.ts b/marketplace-ui/src/app/shared/constants/common.constant.ts index 507ddba08..c37d19e69 100644 --- a/marketplace-ui/src/app/shared/constants/common.constant.ts +++ b/marketplace-ui/src/app/shared/constants/common.constant.ts @@ -190,3 +190,8 @@ export const DEFAULT_PAGEABLE_IN_REST_CLIENT: Pageable = { page: 0, size: 40 }; + +export const VERSION = { + tagPrefix: 'v', + displayPrefix: 'Version ' +}; diff --git a/marketplace-ui/src/app/shared/pipes/product-type.pipe.ts b/marketplace-ui/src/app/shared/pipes/product-type.pipe.ts new file mode 100644 index 000000000..7c922ba25 --- /dev/null +++ b/marketplace-ui/src/app/shared/pipes/product-type.pipe.ts @@ -0,0 +1,11 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + standalone: true, + name: 'productType' +}) +export class ProductTypePipe implements PipeTransform { + transform(type: string, _args?: []): string { + return `common.filter.value.${type}`; + } +} diff --git a/marketplace-ui/src/assets/i18n/de.yaml b/marketplace-ui/src/assets/i18n/de.yaml index 2fd8fd53e..847497ad7 100644 --- a/marketplace-ui/src/assets/i18n/de.yaml +++ b/marketplace-ui/src/assets/i18n/de.yaml @@ -19,8 +19,8 @@ common: label: Filter value: allTypes: Alle - connector: Konnektoren - util: Utilities + connector: Konnektor + util: Nützliches demos: Demos solution: Lösungen sort: @@ -97,9 +97,9 @@ common: loggedGithubAsLabel: Eingeloggt bei Github als reviewLabel: Überprüfung detailedReviews: Ausführliche Bewertungen - rateLinkLabel: Diesen Stecker bewerten + rateLinkLabel: Bewerte diesen Konnektor showMoreBtnLabel: Mehr anzeigen - noFeedbackMessage1: Es gibt noch keine Rückmeldungen für diesen Anschluss. - noFeedbackMessage2: Seien Sie der Erste, der seine Meinung mitteilt. - rateFeedbackBtnLabel: Diesen Stecker bewerten - reviewLabelNoYet: Noch keine Bewertungen + noFeedbackMessage1: Deine Meinung ist gefragt. + noFeedbackMessage2: '' + rateFeedbackBtnLabel: Bewerte diesen Konnektor + reviewLabelNoYet: Noch keine Bewertungen \ No newline at end of file