diff --git a/src/app/product-store/app-detail/app-detail.component.ts b/src/app/product-store/app-detail/app-detail.component.ts index 3e25657..7433cac 100644 --- a/src/app/product-store/app-detail/app-detail.component.ts +++ b/src/app/product-store/app-detail/app-detail.component.ts @@ -19,6 +19,7 @@ import { MicroservicesAPIService, GetMicroserviceByAppIdRequestParams } from 'src/app/shared/generated' +import { convertToUniqueStringArray } from 'src/app/shared/utils' import { AppAbstract, ChangeMode } from '../app-search/app-search.component' @@ -57,7 +58,6 @@ export class AppDetailComponent implements OnChanges { @Input() displayDialog = false @Output() appChanged = new EventEmitter() - private debug = true public mfe: Microfrontend | undefined public ms: Microservice | undefined public formGroupMfe: FormGroup @@ -66,6 +66,7 @@ export class AppDetailComponent implements OnChanges { public hasCreatePermission = false public hasEditPermission = false public iconItems: SelectItem[] = [{ label: '', value: null }] // default value is empty + public convertToUniqueStringArray = convertToUniqueStringArray constructor( private user: UserService, @@ -139,11 +140,7 @@ export class AppDetailComponent implements OnChanges { this.loading = true this.mfeApi .getMicrofrontendByAppId({ appId: this.appAbstract?.appId } as GetMicrofrontendByAppIdRequestParams) - .pipe( - finalize(() => { - this.loading = false - }) - ) + .pipe(finalize(() => (this.loading = false))) .subscribe({ next: (data: any) => { if (data) { @@ -166,11 +163,7 @@ export class AppDetailComponent implements OnChanges { this.loading = true this.msApi .getMicroserviceByAppId({ appId: this.appAbstract?.appId } as GetMicroserviceByAppIdRequestParams) - .pipe( - finalize(() => { - this.loading = false - }) - ) + .pipe(finalize(() => (this.loading = false))) .subscribe({ next: (data: any) => { if (data) { @@ -229,7 +222,7 @@ export class AppDetailComponent implements OnChanges { } this.mfe = { ...this.formGroupMfe.value, id: this.mfe?.id } if (this.mfe) - this.mfe.classifications = this.makeStringArrayUnique(this.formGroupMfe.controls['classifications'].value) + this.mfe.classifications = this.convertToUniqueStringArray(this.formGroupMfe.controls['classifications'].value) this.changeMode === 'CREATE' ? this.createMfe() : this.updateMfe() } if (this.appAbstract?.appType === 'MS') { @@ -242,19 +235,6 @@ export class AppDetailComponent implements OnChanges { } } - private makeStringArrayUnique(unsorted: string | undefined): string[] | undefined { - if (!unsorted || unsorted?.length === 0) return undefined - let ar: Array = [] - unsorted - .toString() - .split(',') - .map((a) => ar?.push(a.trim())) - return ar.sort(this.sortStrings) - } - private sortStrings(a: string, b: string): number { - return (a as String).toUpperCase().localeCompare((b as String).toUpperCase()) - } - private createMfe() { this.mfeApi.createMicrofrontend({ createMicrofrontendRequest: this.mfe as CreateMicrofrontendRequest }).subscribe({ next: () => { diff --git a/src/app/product-store/product-detail/product-apps/product-apps.component.ts b/src/app/product-store/product-detail/product-apps/product-apps.component.ts index 30a5f08..ca8e28f 100644 --- a/src/app/product-store/product-detail/product-apps/product-apps.component.ts +++ b/src/app/product-store/product-detail/product-apps/product-apps.component.ts @@ -1,6 +1,6 @@ import { Component, Input, OnChanges, ViewChild } from '@angular/core' -import { SelectItem } from 'primeng/api' import { combineLatest, finalize, map, of, Observable, catchError } from 'rxjs' +import { SelectItem } from 'primeng/api' import { DataView } from 'primeng/dataview' import { DataViewControlTranslations, UserService } from '@onecx/portal-integration-angular' @@ -29,6 +29,7 @@ export class ProductAppsComponent implements OnChanges { private readonly debug = true // to be removed after finalization public exceptionKey = '' + public searchInProgress = false public apps$!: Observable public mfes$!: Observable public mss$!: Observable @@ -38,7 +39,6 @@ export class ProductAppsComponent implements OnChanges { public viewMode = 'grid' public sortField = 'appId' public sortOrder = 1 - public searchInProgress = false public displayDetailDialog = false public displayDeleteDialog = false public hasCreatePermission = false diff --git a/src/app/product-store/product-detail/product-detail.component.ts b/src/app/product-store/product-detail/product-detail.component.ts index 69bda51..610d286 100644 --- a/src/app/product-store/product-detail/product-detail.component.ts +++ b/src/app/product-store/product-detail/product-detail.component.ts @@ -16,8 +16,6 @@ export type ChangeMode = 'VIEW' | 'CREATE' | 'EDIT' | 'COPY' styleUrls: ['./product-detail.component.scss'] }) export class ProductDetailComponent implements OnInit { - @ViewChild(ProductPropertyComponent, { static: false }) productPropsComponent!: ProductPropertyComponent - public actions$: Observable | undefined public productName: string public product: ProductAndWorkspaces | undefined @@ -25,12 +23,13 @@ export class ProductDetailComponent implements OnInit { public changeMode: ChangeMode = 'CREATE' public loading = false public dateFormat = 'medium' - public actions: Action[] = [] public headerImageUrl?: string public productDeleteVisible = false public productDeleteMessage = '' public selectedTabIndex = 0 + @ViewChild(ProductPropertyComponent, { static: false }) productPropsComponent!: ProductPropertyComponent + constructor( private router: Router, private route: ActivatedRoute, @@ -190,7 +189,7 @@ export class ProductDetailComponent implements OnInit { if (this.changeMode === 'CREATE') this.close() } public onSave() { - this.productPropsComponent.onSubmit() + this.productPropsComponent.onSave() } public onCreate(data: any) { diff --git a/src/app/product-store/product-detail/product-props/product-props.component.html b/src/app/product-store/product-detail/product-props/product-props.component.html index 39ffdbf..d9d9569 100644 --- a/src/app/product-store/product-detail/product-props/product-props.component.html +++ b/src/app/product-store/product-detail/product-props/product-props.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/app/product-store/product-detail/product-props/product-props.component.spec.ts b/src/app/product-store/product-detail/product-props/product-props.component.spec.ts index 46d3e67..0a1522f 100644 --- a/src/app/product-store/product-detail/product-props/product-props.component.spec.ts +++ b/src/app/product-store/product-detail/product-props/product-props.component.spec.ts @@ -152,7 +152,7 @@ describe('ProductPropertyComponent', () => { expect(component.formGroup.reset).toHaveBeenCalled() }) - it('should call createProduct onSubmit in new mode', () => { + it('should call createProduct onSave in new mode', () => { apiServiceSpy.createProduct.and.returnValue(of({})) const formGroup = new FormGroup({ id: new FormControl('id'), @@ -169,13 +169,13 @@ describe('ProductPropertyComponent', () => { component.formGroup = formGroup as FormGroup component.changeMode = 'CREATE' - component.onSubmit() + component.onSave() expect(apiServiceSpy.createProduct).toHaveBeenCalled() expect(msgServiceSpy.success).toHaveBeenCalledWith({ summaryKey: 'ACTIONS.CREATE.PRODUCT.OK' }) }) - it('should call updateProduct onSubmit in edit mode', () => { + it('should call updateProduct onSave in edit mode', () => { apiServiceSpy.updateProduct.and.returnValue(of({})) const formGroup = new FormGroup({ id: new FormControl('id'), @@ -192,7 +192,7 @@ describe('ProductPropertyComponent', () => { component.formGroup = formGroup as FormGroup component.changeMode = 'EDIT' - component.onSubmit() + component.onSave() expect(apiServiceSpy.updateProduct).toHaveBeenCalled() expect(msgServiceSpy.success).toHaveBeenCalledWith({ summaryKey: 'ACTIONS.EDIT.PRODUCT.OK' }) @@ -216,7 +216,7 @@ describe('ProductPropertyComponent', () => { component.formGroup.controls['name'].setValue('') component.changeMode = 'EDIT' - component.onSubmit() + component.onSave() expect(component.formGroup.valid).toBeTrue() expect(msgServiceSpy.error).toHaveBeenCalledWith({ @@ -247,7 +247,7 @@ describe('ProductPropertyComponent', () => { component.formGroup.controls['name'].setValue('') component.changeMode = 'EDIT' - component.onSubmit() + component.onSave() expect(component.formGroup.valid).toBeTrue() expect(msgServiceSpy.error).toHaveBeenCalledWith({ @@ -262,7 +262,7 @@ describe('ProductPropertyComponent', () => { const focusSpy = jasmine.createSpy('focus') spyOn((component as any).elements.nativeElement, 'querySelector').and.returnValue({ focus: focusSpy }) - component.onSubmit() + component.onSave() expect(component.formGroup.valid).toBeFalse() expect(msgServiceSpy.error).toHaveBeenCalledWith({ summaryKey: 'VALIDATION.FORM_INVALID' }) @@ -286,7 +286,7 @@ describe('ProductPropertyComponent', () => { component.formGroup = formGroup as FormGroup component.changeMode = 'CREATE' - component.onSubmit() + component.onSave() expect(component.formGroup.valid).toBeTrue() expect(msgServiceSpy.error).toHaveBeenCalledWith({ @@ -294,7 +294,7 @@ describe('ProductPropertyComponent', () => { }) }) - it('should display error onSubmit if formGroup invalid', () => { + it('should display error onSave if formGroup invalid', () => { const formGroup = new FormGroup({ id: new FormControl(null, Validators.required), name: new FormControl('name'), @@ -309,7 +309,7 @@ describe('ProductPropertyComponent', () => { }) component.formGroup = formGroup as FormGroup - component.onSubmit() + component.onSave() expect(component.formGroup.valid).toBeFalse() expect(msgServiceSpy.error).toHaveBeenCalledWith({ diff --git a/src/app/product-store/product-detail/product-props/product-props.component.ts b/src/app/product-store/product-detail/product-props/product-props.component.ts index b7e4bbf..03985b3 100644 --- a/src/app/product-store/product-detail/product-props/product-props.component.ts +++ b/src/app/product-store/product-detail/product-props/product-props.component.ts @@ -14,7 +14,7 @@ import { UploadImageRequestParams } from 'src/app/shared/generated' import { IconService } from 'src/app/shared/iconservice' -import { dropDownSortItemsByLabel } from 'src/app/shared/utils' +import { dropDownSortItemsByLabel, convertToUniqueStringArray } from 'src/app/shared/utils' import { ChangeMode } from '../product-detail.component' export interface ProductDetailForm { @@ -56,8 +56,7 @@ export class ProductPropertyComponent implements OnChanges, OnInit { public fetchingLogoUrl: string | undefined public iconItems: SelectItem[] = [{ label: '', value: null }] public logoImageWasUploaded: boolean | undefined - - //private productNamePattern = '^(?!new$)(.*)$' // matching for valid product names + public convertToUniqueStringArray = convertToUniqueStringArray constructor( private icon: IconService, @@ -124,7 +123,7 @@ export class ProductPropertyComponent implements OnChanges, OnInit { /** CREATE/UPDATE product */ - public onSubmit() { + public onSave() { if (this.formGroup.valid) { this.changeMode === 'EDIT' ? this.updateProduct() : this.createProduct() } else { @@ -150,7 +149,7 @@ export class ProductPropertyComponent implements OnChanges, OnInit { basePath: this.formGroup.value['basePath'], displayName: this.formGroup.value['displayName'], iconName: this.formGroup.value['iconName'], - classifications: this.formGroup.value['classifications']?.toString().split(',') + classifications: this.convertToUniqueStringArray(this.formGroup.value['classifications']?.toString()) } as CreateProductRequest }) .subscribe({ @@ -178,7 +177,7 @@ export class ProductPropertyComponent implements OnChanges, OnInit { basePath: this.formGroup.value['basePath'], displayName: this.formGroup.value['displayName'], iconName: this.formGroup.value['iconName'], - classifications: this.formGroup.value['classifications']?.toString().split(',') + classifications: this.convertToUniqueStringArray(this.formGroup.value['classifications']?.toString()) } as UpdateProductRequest }) .subscribe({ diff --git a/src/app/product-store/product-detail/product.detail.component.spec.ts b/src/app/product-store/product-detail/product.detail.component.spec.ts index d6ac0b9..02efc5d 100644 --- a/src/app/product-store/product-detail/product.detail.component.spec.ts +++ b/src/app/product-store/product-detail/product.detail.component.spec.ts @@ -19,7 +19,7 @@ const product = { } class MockProductPropertyComponent { - onSubmit = jasmine.createSpy('onSubmit') + onSave = jasmine.createSpy('onSave') } describe('ProductDetailComponent', () => { @@ -208,7 +208,7 @@ describe('ProductDetailComponent', () => { component.onSave() - expect(component.productPropsComponent.onSubmit).toHaveBeenCalled() + expect(component.productPropsComponent.onSave).toHaveBeenCalled() }) it('should behave correctly onCreate', () => { diff --git a/src/app/product-store/product-search/product-search.component.html b/src/app/product-store/product-search/product-search.component.html index c2f11b8..4e6ad4d 100644 --- a/src/app/product-store/product-search/product-search.component.html +++ b/src/app/product-store/product-search/product-search.component.html @@ -39,7 +39,7 @@ { expect(routerSpy).toHaveBeenCalledWith(['./apps'], jasmine.any(Object)) }) + it('should sort products by display name', () => { + const p1 = { displayName: 'b product' } + const p2 = { displayName: 'a product' } + + const result = component.sortProductsByDisplayName(p1 as ProductAbstract, p2 as ProductAbstract) + + expect(result).toBe(1) + }) + it('should getImageUrl from existing product', () => { - const product = { - imageUrl: 'url' - } + const product = { imageUrl: 'url' } const result = component.getImageUrl(product) diff --git a/src/app/product-store/product-search/product-search.component.ts b/src/app/product-store/product-search/product-search.component.ts index 6793dd3..4dfb934 100644 --- a/src/app/product-store/product-search/product-search.component.ts +++ b/src/app/product-store/product-search/product-search.component.ts @@ -7,7 +7,12 @@ import { DataView } from 'primeng/dataview' import { Action, DataViewControlTranslations } from '@onecx/portal-integration-angular' -import { ImagesInternalAPIService, ProductPageResult, ProductsAPIService } from 'src/app/shared/generated' +import { + ImagesInternalAPIService, + ProductAbstract, + ProductPageResult, + ProductsAPIService +} from 'src/app/shared/generated' import { limitText } from 'src/app/shared/utils' export interface ProductSearchCriteria { @@ -26,7 +31,7 @@ export class ProductSearchComponent implements OnInit { public actions$: Observable | undefined public viewMode = 'grid' public filter: string | undefined - public sortField = 'name' + public sortField = 'displayName' public sortOrder = 1 public limitText = limitText @@ -66,7 +71,13 @@ export class ProductSearchComponent implements OnInit { finalize(() => (this.searchInProgress = false)) ) } + public sortProductsByDisplayName(a: ProductAbstract, b: ProductAbstract): number { + return (a.displayName as string).toUpperCase().localeCompare((b.displayName as string).toUpperCase()) + } + /** + * DIALOG + */ private prepareDialogTranslations(): void { this.translate .get([ @@ -138,6 +149,9 @@ export class ProductSearchComponent implements OnInit { ) } + /** + * UI EVENTS + */ public onLayoutChange(viewMode: string): void { this.viewMode = viewMode } diff --git a/src/app/shared/utils.spec.ts b/src/app/shared/utils.spec.ts index 5586d35..39b83c1 100644 --- a/src/app/shared/utils.spec.ts +++ b/src/app/shared/utils.spec.ts @@ -1,6 +1,12 @@ import { SelectItem } from 'primeng/api' -import { limitText, dropDownSortItemsByLabel, dropDownGetLabelByValue, sortByLocale } from './utils' +import { + limitText, + dropDownSortItemsByLabel, + dropDownGetLabelByValue, + sortByLocale, + convertToUniqueStringArray +} from './utils' describe('utils', () => { describe('limitText', () => { @@ -66,4 +72,14 @@ describe('utils', () => { expect(sortedStrings[0]).toEqual('str1') }) }) + + describe('convertToUniqueStringArray', () => { + it('should convert a comma-separated string to array with unique items', () => { + const s = 'c, b, a' + + const sortedArray = convertToUniqueStringArray(s) ?? [] + + expect(sortedArray[0]).toEqual('a') + }) + }) }) diff --git a/src/app/shared/utils.ts b/src/app/shared/utils.ts index e595319..f582682 100644 --- a/src/app/shared/utils.ts +++ b/src/app/shared/utils.ts @@ -32,3 +32,13 @@ export function dropDownGetLabelByValue(ddArray: SelectItem[], val: string): str export function sortByLocale(a: any, b: any): number { return a.toUpperCase().localeCompare(b.toUpperCase()) } + +export function convertToUniqueStringArray(unsorted: string | undefined | null): string[] | undefined { + if (!unsorted || unsorted?.length === 0) return undefined + let ar: Array = [] + unsorted + .toString() + .split(',') + .map((a) => ar?.push(a.trim())) + return ar.sort(sortByLocale) +}