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 0a1522f..9c97579 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
@@ -18,7 +18,6 @@ const mockForm = new FormGroup({
Validators.maxLength(255),
productNameValidator()
]),
- operator: new FormControl(null),
version: new FormControl(null, [Validators.required, Validators.maxLength(255)]),
description: new FormControl(null, [Validators.maxLength(255)]),
imageUrl: new FormControl(null, [Validators.maxLength(255)]),
@@ -157,7 +156,6 @@ describe('ProductPropertyComponent', () => {
const formGroup = new FormGroup({
id: new FormControl('id'),
name: new FormControl('name'),
- operator: new FormControl(null),
version: new FormControl('version'),
description: new FormControl(null),
imageUrl: new FormControl(null),
@@ -180,7 +178,6 @@ describe('ProductPropertyComponent', () => {
const formGroup = new FormGroup({
id: new FormControl('id'),
name: new FormControl('name'),
- operator: new FormControl(null),
version: new FormControl('version'),
description: new FormControl(null),
imageUrl: new FormControl(null),
@@ -203,7 +200,6 @@ describe('ProductPropertyComponent', () => {
const formGroup = new FormGroup({
id: new FormControl('id'),
name: new FormControl('name'),
- operator: new FormControl(null),
version: new FormControl('version'),
description: new FormControl(null),
imageUrl: new FormControl(null),
@@ -224,17 +220,26 @@ describe('ProductPropertyComponent', () => {
})
})
- it('should display unique constraint error if error code points to it', () => {
+ it('should display unique constraint error if name already exists', () => {
const error = {
error: {
- errorCode: 'PERSIST_ENTITY_FAILED'
+ errorCode: 'PERSIST_ENTITY_FAILED',
+ detail:
+ "could not execute statement [ERROR: duplicate key value violates unique constraint 'ui_product_name' ...",
+ params: [
+ {
+ key: 'constraint',
+ value:
+ "could not execute statement [ERROR: duplicate key value violates unique constraint 'ui_product_name' ..."
+ },
+ { key: 'constraintName', value: 'ui_product_name' }
+ ]
}
}
apiServiceSpy.updateProduct.and.returnValue(throwError(() => error))
const formGroup = new FormGroup({
id: new FormControl('id'),
name: new FormControl('name'),
- operator: new FormControl(null),
version: new FormControl('version'),
description: new FormControl(null),
imageUrl: new FormControl(null),
@@ -244,15 +249,56 @@ describe('ProductPropertyComponent', () => {
classifications: new FormControl(null)
})
component.formGroup = formGroup as FormGroup
+ component.changeMode = 'EDIT'
component.formGroup.controls['name'].setValue('')
+
+ component.onSave()
+
+ expect(component.formGroup.valid).toBeTrue()
+ expect(msgServiceSpy.error).toHaveBeenCalledWith({
+ summaryKey: 'ACTIONS.EDIT.PRODUCT.NOK',
+ detailKey: 'VALIDATION.PRODUCT.UNIQUE_CONSTRAINT.NAME'
+ })
+ })
+
+ it('should display unique constraint error if basepath already exists', () => {
+ const error = {
+ error: {
+ errorCode: 'PERSIST_ENTITY_FAILED',
+ detail:
+ "could not execute statement [ERROR: duplicate key value violates unique constraint 'ui_product_base_path' ...",
+ params: [
+ {
+ key: 'constraint',
+ value:
+ "could not execute statement [ERROR: duplicate key value violates unique constraint 'ui_product_base_path' ..."
+ },
+ { key: 'constraintName', value: 'ui_product_base_path' }
+ ]
+ }
+ }
+ apiServiceSpy.updateProduct.and.returnValue(throwError(() => error))
+ const formGroup = new FormGroup({
+ id: new FormControl('id'),
+ name: new FormControl('name'),
+ version: new FormControl('version'),
+ description: new FormControl(null),
+ imageUrl: new FormControl(null),
+ basePath: new FormControl('path'),
+ displayName: new FormControl('display'),
+ iconName: new FormControl('icon'),
+ classifications: new FormControl(null)
+ })
+ component.formGroup = formGroup as FormGroup
component.changeMode = 'EDIT'
+ component.formGroup.controls['basePath'].setValue('')
component.onSave()
expect(component.formGroup.valid).toBeTrue()
expect(msgServiceSpy.error).toHaveBeenCalledWith({
summaryKey: 'ACTIONS.EDIT.PRODUCT.NOK',
- detailKey: 'VALIDATION.PRODUCT.UNIQUE_CONSTRAINT'
+ detailKey: 'VALIDATION.PRODUCT.UNIQUE_CONSTRAINT.BASEPATH'
})
})
@@ -274,7 +320,6 @@ describe('ProductPropertyComponent', () => {
const formGroup = new FormGroup({
id: new FormControl('id'),
name: new FormControl('name'),
- operator: new FormControl(null),
version: new FormControl('version'),
description: new FormControl(null),
imageUrl: new FormControl(null),
@@ -298,7 +343,6 @@ describe('ProductPropertyComponent', () => {
const formGroup = new FormGroup({
id: new FormControl(null, Validators.required),
name: new FormControl('name'),
- operator: new FormControl(null),
version: new FormControl('version'),
description: new FormControl(null),
imageUrl: new FormControl(null),
@@ -416,7 +460,7 @@ describe('ProductPropertyComponent', () => {
it('should return an image url', () => {
component.formGroup.controls['imageUrl'].setValue('url')
- const result = component.getImageUrl()
+ const result = component.prepareImageUrl()
expect(result).toEqual('url')
})
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 03985b3..4db12ef 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
@@ -20,7 +20,6 @@ import { ChangeMode } from '../product-detail.component'
export interface ProductDetailForm {
id: FormControl
name: FormControl
- operator: FormControl
version: FormControl
description: FormControl
imageUrl: FormControl
@@ -74,7 +73,6 @@ export class ProductPropertyComponent implements OnChanges, OnInit {
productNameValidator()
]),
displayName: new FormControl(null, [Validators.required, Validators.minLength(2), Validators.maxLength(255)]),
- operator: new FormControl(null),
version: new FormControl(null, [Validators.required, Validators.maxLength(255)]),
description: new FormControl(null, [Validators.maxLength(255)]),
imageUrl: new FormControl(null, [Validators.maxLength(255)]),
@@ -88,29 +86,19 @@ export class ProductPropertyComponent implements OnChanges, OnInit {
ngOnInit(): void {
let productName = this.formGroup.controls['name'].value!
- let requestParametersGet: GetImageRequestParams = {
- refId: productName,
- refType: RefType.Logo
- }
- if (
- requestParametersGet.refId === undefined ||
- requestParametersGet.refId === '' ||
- requestParametersGet.refId === null
- ) {
+ if (!productName) {
this.logoImageWasUploaded = false
} else {
- this.imageApi.getImage(requestParametersGet).subscribe(() => {
+ this.imageApi.getImage({ refId: productName, refType: RefType.Logo }).subscribe(() => {
this.logoImageWasUploaded = true
})
}
- this.fetchingLogoUrl = this.getImageUrl()
+ this.fetchingLogoUrl = this.prepareImageUrl()
}
ngOnChanges(): void {
if (this.product) {
- this.formGroup.patchValue({
- ...this.product
- })
+ this.formGroup.patchValue({ ...this.product })
this.productId = this.changeMode !== 'COPY' ? this.product.id : undefined
this.productName = this.product.name // business key => manage the change!
} else {
@@ -135,17 +123,13 @@ export class ProductPropertyComponent implements OnChanges, OnInit {
}
private createProduct() {
- let imgUrl = this.formGroup.controls['imageUrl'].value
- if ((imgUrl == '' || imgUrl == null) && !this.logoImageWasUploaded) {
- imgUrl = 'http://pragmaticscrum.info/wp-content/uploads/2016/06/t1.jpg'
- }
this.productApi
.createProduct({
createProductRequest: {
name: this.formGroup.value['name'],
version: this.formGroup.value['version'],
description: this.formGroup.value['description'],
- imageUrl: imgUrl,
+ imageUrl: this.formGroup.controls['imageUrl'].value,
basePath: this.formGroup.value['basePath'],
displayName: this.formGroup.value['displayName'],
iconName: this.formGroup.value['iconName'],
@@ -162,10 +146,6 @@ export class ProductPropertyComponent implements OnChanges, OnInit {
}
private updateProduct() {
- let imgUrl = this.formGroup.controls['imageUrl'].value
- if ((imgUrl == '' || imgUrl == null) && !this.logoImageWasUploaded) {
- imgUrl = 'http://pragmaticscrum.info/wp-content/uploads/2016/06/t1.jpg'
- }
this.productApi
.updateProduct({
id: this.productId!,
@@ -173,7 +153,7 @@ export class ProductPropertyComponent implements OnChanges, OnInit {
name: this.formGroup.value['name'],
version: this.formGroup.value['version'],
description: this.formGroup.value['description'],
- imageUrl: imgUrl,
+ imageUrl: this.formGroup.controls['imageUrl'].value,
basePath: this.formGroup.value['basePath'],
displayName: this.formGroup.value['displayName'],
iconName: this.formGroup.value['iconName'],
@@ -193,7 +173,9 @@ export class ProductPropertyComponent implements OnChanges, OnInit {
if (err.error?.errorCode === 'PERSIST_ENTITY_FAILED') {
this.msgService.error({
summaryKey: 'ACTIONS.' + this.changeMode + '.PRODUCT.NOK',
- detailKey: 'VALIDATION.PRODUCT.UNIQUE_CONSTRAINT'
+ detailKey:
+ 'VALIDATION.PRODUCT.UNIQUE_CONSTRAINT.' +
+ (err.error?.detail.indexOf('ui_product_base_path') > 0 ? 'BASEPATH' : 'NAME')
})
} else {
this.msgService.error({ summaryKey: 'ACTIONS.' + this.changeMode + '.PRODUCT.NOK' })
@@ -259,7 +241,7 @@ export class ProductPropertyComponent implements OnChanges, OnInit {
}
}
- getImageUrl(): string {
+ prepareImageUrl(): string {
let imgUrl = this.formGroup.controls['imageUrl'].value
if (imgUrl == '' || imgUrl == null) {
return this.imageApi.configuration.basePath + '/images/' + this.formGroup.controls['name'].value + '/logo'
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 4e6ad4d..5038fdb 100644
--- a/src/app/product-store/product-search/product-search.component.html
+++ b/src/app/product-store/product-search/product-search.component.html
@@ -86,7 +86,7 @@
@@ -118,7 +118,7 @@
diff --git a/src/app/product-store/product-search/product-search.component.spec.ts b/src/app/product-store/product-search/product-search.component.spec.ts
index bc4136b..9d1ee49 100644
--- a/src/app/product-store/product-search/product-search.component.spec.ts
+++ b/src/app/product-store/product-search/product-search.component.spec.ts
@@ -195,20 +195,20 @@ describe('ProductSearchComponent', () => {
expect(result).toBe(1)
})
- it('should getImageUrl from existing product', () => {
+ it('should prepareImageUrl from existing product', () => {
const product = { imageUrl: 'url' }
- const result = component.getImageUrl(product)
+ const result = component.prepareImageUrl(product)
expect(result).toEqual(product.imageUrl)
})
- it('should getImageUrl from image api if not from existing product', () => {
+ it('should prepareImageUrl from image api if not from existing product', () => {
const product = {
id: 'id'
}
- const result = component.getImageUrl(product)
+ const result = component.prepareImageUrl(product)
expect(result).toEqual('http://onecx-product-store-bff:8080/images/undefined/logo')
})
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 4dfb934..1337427 100644
--- a/src/app/product-store/product-search/product-search.component.ts
+++ b/src/app/product-store/product-search/product-search.component.ts
@@ -179,7 +179,7 @@ export class ProductSearchComponent implements OnInit {
this.router.navigate(['./new'], { relativeTo: this.route })
}
- public getImageUrl(product: any): string {
+ public prepareImageUrl(product: any): string {
if (product.imageUrl) {
return product.imageUrl
} else {
diff --git a/src/app/shared/image-container/image-container.component.html b/src/app/shared/image-container/image-container.component.html
index 3e3b1de..7f8e9d7 100644
--- a/src/app/shared/image-container/image-container.component.html
+++ b/src/app/shared/image-container/image-container.component.html
@@ -1,58 +1,15 @@
-
+ [class]="'image-object image-' + (small ? 'sm' : 'lg') + ' sm:image-lg vertical-align-middle'"
+ [src]="defaultImageUrl"
+ alt="Logo"
+/>
diff --git a/src/app/shared/image-container/image-container.component.ts b/src/app/shared/image-container/image-container.component.ts
index 5918a62..3161e76 100644
--- a/src/app/shared/image-container/image-container.component.ts
+++ b/src/app/shared/image-container/image-container.component.ts
@@ -1,5 +1,6 @@
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'
import { prepareUrl } from 'src/app/shared/utils'
+import { environment } from 'src/environments/environment'
@Component({
selector: 'app-image-container',
@@ -11,6 +12,7 @@ export class ImageContainerComponent implements OnChanges {
@Input() public imageUrl: string | undefined
@Input() public small = false
+ public defaultImageUrl = environment.DEFAULT_LOGO_URL
public displayPlaceHolder = false
public onImageError() {
diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json
index 8dd9380..9d5f1a4 100644
--- a/src/assets/i18n/de.json
+++ b/src/assets/i18n/de.json
@@ -227,7 +227,7 @@
"OPERATOR": "Erzeugt durch",
"OPERATOR.MANUAL": "Manuell",
"OPERATOR.DEPLOYMENT": "Deployment Operator",
- "IMAGE_URL": "Image URL",
+ "IMAGE_URL": "Externe Image URL",
"BASE_PATH": "Basispfad",
"ICON_NAME": "Iconname",
"CLASSIFICATIONS": "Klassifizierungen",
@@ -239,7 +239,7 @@
"DESCRIPTION": "Beschreibung des Produkts",
"DISPLAY_NAME": "Anzeigename des Produkts",
"OPERATOR": "Wie wurde das Produkt angelegt? Wird bei der Erstellung des Produkts bestimmt.",
- "IMAGE_URL": "Image URL",
+ "IMAGE_URL": "URL für ein Image ausserhalb des Product Stores",
"BASE_PATH": "Der Basispfad wie das Produkt erreichbar ist. Muss eindeutig über alle Produkte sein",
"ICON_NAME": "Name eines Icons aus der PrimeNG Icon Bibliothek. Wird z.b. als Default bei Menüeinträgen benutzt",
"CLASSIFICATIONS": "Kommaseparierte Liste von Begriffen zur Klassifizierungen des Produkts, z.b. Game, Tool etc.. Kann zur Filterung benutzt werden",
@@ -268,7 +268,8 @@
"FORM_INVALID": "Die Daten sind nicht bereit zum Speichern.",
"APP.UNIQUE_CONSTRAINT.REMOTE_MODULE": "Eine App mit dieser Remote Konfiguration existiert bereits für das Produkt.",
"APP.UNIQUE_CONSTRAINT.APP_ID": "Eine App mit dieser ID existiert bereits für das Produkt.",
- "PRODUCT.UNIQUE_CONSTRAINT": "Ein Produkt mit diesem Namen existiert bereits.",
+ "PRODUCT.UNIQUE_CONSTRAINT.NAME": "Ein Produkt mit diesem Namen existiert bereits.",
+ "PRODUCT.UNIQUE_CONSTRAINT.BASEPATH": "Ein Produkt mit diesem Basepath existiert bereits.",
"PRODUCT.INVALID_NAME": "Dieser Name ist reserviert und kann nicht verwendet werden.",
"ERRORS": {
"PARSE_ERROR": "Parserfehler: Die enthaltene Struktur entspricht nicht dem erwarteten Format.",
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index ea5c2c3..ece8cd3 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -227,7 +227,7 @@
"OPERATOR": "Created by",
"OPERATOR.MANUAL": "Manual",
"OPERATOR.DEPLOYMENT": "Deployment Operator",
- "IMAGE_URL": "Image URL",
+ "IMAGE_URL": "Extern Image URL",
"BASE_PATH": "Base Path",
"ICON_NAME": "Icon Name",
"CLASSIFICATIONS": "Classifications",
@@ -238,8 +238,8 @@
"VERSION": "Version of the Product",
"DESCRIPTION": "Description of the Product",
"DISPLAY_NAME": "Display Name of the Product",
- "OPERATOR": "How the Product was created? Set at creation time.",
- "IMAGE_URL": "Image URL",
+ "OPERATOR": "How the Product was created? Set at creation time",
+ "IMAGE_URL": "URL for an image outside the Product Store",
"BASE_PATH": "The basic path through which the product can be accessed. It must be unique across all products",
"ICON_NAME": "Name of the icons from the PrimeNG icon library. Used, for example, as a default icon for menu entries",
"CLASSIFICATIONS": "Comma-separated list of terms to classify the product, e.g. Game, Tool etc. Can be used for filtering",
@@ -268,7 +268,8 @@
"FORM_INVALID": "The data are not ready to store.",
"APP.UNIQUE_CONSTRAINT.REMOTE_MODULE": "An app with this remote configuration already exists for the product.",
"APP.UNIQUE_CONSTRAINT.APP_ID": "An App with this id exists already for the Product.",
- "PRODUCT.UNIQUE_CONSTRAINT": "A Product with this name exists already.",
+ "PRODUCT.UNIQUE_CONSTRAINT.NAME": "A Product with this name exists already.",
+ "PRODUCT.UNIQUE_CONSTRAINT.BASEPATH": "A Product with this basepath exists already.",
"PRODUCT.INVALID_NAME": "This name is a reserved term and cannot be used.",
"ERRORS": {
"PARSE_ERROR": "Parse error: The contained structure does not match the expected format.",
diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts
index 9c02573..5071f7e 100644
--- a/src/environments/environment.prod.ts
+++ b/src/environments/environment.prod.ts
@@ -1,5 +1,6 @@
export const environment = {
production: true,
BASE_PATH: '/bff',
- apiPrefix: 'bff'
+ apiPrefix: 'bff',
+ DEFAULT_LOGO_URL: 'http://pragmaticscrum.info/wp-content/uploads/2016/06/t1.jpg'
}
diff --git a/src/environments/environment.ts b/src/environments/environment.ts
index 25bf5d6..4adbe3a 100644
--- a/src/environments/environment.ts
+++ b/src/environments/environment.ts
@@ -8,5 +8,6 @@ export const environment = {
KEYCLOAK_URL: 'http://keycloak-app/',
KEYCLOAK_REALM: 'OneCX',
skipRemoteConfigLoad: true,
- apiPrefix: 'bff'
+ apiPrefix: 'bff',
+ DEFAULT_LOGO_URL: 'http://pragmaticscrum.info/wp-content/uploads/2016/06/t1.jpg'
}