From 5e71c18416adc5c136ea9053a6bbac819da18c60 Mon Sep 17 00:00:00 2001 From: "andre.schmid" Date: Wed, 12 Oct 2022 18:14:23 +0100 Subject: Implement create data type property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allows to add a new data type property and visualize the properties details. Change-Id: Ib7bcd4b0bd8213dbe8ee8a3762a0636e22dc67eb Issue-ID: SDC-4258 Signed-off-by: André Schmid --- .../input-list-item/input-list-item.component.html | 18 +- .../input-list-item/input-list-item.component.ts | 80 +++---- .../interface-operation-handler.module.ts | 51 ++--- .../add-property/add-property.component.html | 66 ++++++ .../add-property/add-property.component.less | 104 +++++++++ .../add-property/add-property.component.spec.ts | 88 ++++++++ .../add-property/add-property.component.ts | 233 ++++++++++++++++++++ .../type-workspace-properties.component.html | 37 ++-- .../type-workspace-properties.component.less | 10 +- .../type-workspace-properties.component.spec.ts | 5 +- .../type-workspace-properties.component.ts | 238 ++++++++++++++------- .../type-workspace/type-workspace.component.html | 2 +- .../pages/type-workspace/type-workspace.module.ts | 14 +- .../src/app/ng2/services/data-type.service.ts | 5 + catalog-ui/src/app/utils/constants.ts | 1 + catalog-ui/src/app/utils/tosca-type-helper.ts | 78 +++++++ 16 files changed, 853 insertions(+), 177 deletions(-) create mode 100644 catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/add-property/add-property.component.html create mode 100644 catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/add-property/add-property.component.less create mode 100644 catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/add-property/add-property.component.spec.ts create mode 100644 catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/add-property/add-property.component.ts create mode 100644 catalog-ui/src/app/utils/tosca-type-helper.ts (limited to 'catalog-ui/src/app') diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-list/input-list-item/input-list-item.component.html b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-list/input-list-item/input-list-item.component.html index 1e0804eb04..6a5bd5a007 100644 --- a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-list/input-list-item/input-list-item.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-list/input-list-item/input-list-item.component.html @@ -67,12 +67,16 @@ @@ -123,7 +127,7 @@ [type]="getDataType(schema.property.type)" [dataTypeMap]="dataTypeMap" [valueObjRef]="valueObjRef[i]" - [schema]="schema" + [schema]="buildSchemaGroupProperty()" [nestingLevel]="nestingLevel + 1" [listIndex]="i" [isListChild]="true" @@ -155,7 +159,7 @@ [type]="getDataType(schema.property.type)" [dataTypeMap]="dataTypeMap" [valueObjRef]="valueObjRef[key]" - [schema]="schema" + [schema]="buildSchemaGroupProperty()" [isMapChild]="true" [nestingLevel]="nestingLevel + 1" [isViewOnly]="isViewOnly" diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-list/input-list-item/input-list-item.component.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-list/input-list-item/input-list-item.component.ts index 88ff8deec6..145aad687c 100644 --- a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-list/input-list-item/input-list-item.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-list/input-list-item/input-list-item.component.ts @@ -21,12 +21,13 @@ import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; import {DataTypeModel} from '../../../../../../../models/data-types'; -import {SchemaPropertyGroupModel} from '../../../../../../../models/schema-property'; -import {DerivedPropertyType, PropertyBEModel} from '../../../../../../../models/properties-inputs/property-be-model'; +import {SchemaProperty, SchemaPropertyGroupModel} from '../../../../../../../models/schema-property'; +import {PropertyBEModel} from '../../../../../../../models/properties-inputs/property-be-model'; import {PROPERTY_DATA, PROPERTY_TYPES} from '../../../../../../../utils/constants'; import {ToscaFunction} from '../../../../../../../models/tosca-function'; -import {ToscaFunctionValidationEvent} from "../../../../../../../ng2/pages/properties-assignment/tosca-function/tosca-function.component"; +import {ToscaFunctionValidationEvent} from "../../../../../properties-assignment/tosca-function/tosca-function.component"; import {InstanceFeDetails} from "../../../../../../../models/instance-fe-details"; +import {ToscaTypeHelper} from "app/utils/tosca-type-helper"; @Component({ selector: 'app-input-list-item', @@ -41,6 +42,7 @@ export class InputListItemComponent implements OnInit { @Input() type: DataTypeModel; @Input() schema: SchemaPropertyGroupModel; @Input() nestingLevel: number; + @Input() isExpanded: boolean = false; @Input() isListChild: boolean = false; @Input() isMapChild: boolean = false; @Input() showToscaFunctionOption: boolean = false; @@ -53,12 +55,11 @@ export class InputListItemComponent implements OnInit { @Output('onDelete') onDeleteEvent: EventEmitter = new EventEmitter(); @Output('onChildListItemDelete') onChildListItemDeleteEvent: EventEmitter = new EventEmitter(); - isExpanded: boolean = false; mapEntryName: string; isToscaFunction: boolean = false; property: PropertyBEModel; - ngOnInit() { + ngOnInit(): void { if (!this.nestingLevel) { this.nestingLevel = 0; } @@ -93,26 +94,20 @@ export class InputListItemComponent implements OnInit { } } - getType(typeName: string): DerivedPropertyType { - if (PROPERTY_DATA.SIMPLE_TYPES.indexOf(typeName) > -1) { - return DerivedPropertyType.SIMPLE; - } else if (typeName === PROPERTY_TYPES.LIST) { - return DerivedPropertyType.LIST; - } else if (typeName === PROPERTY_TYPES.MAP) { - return DerivedPropertyType.MAP; - } else if (typeName === PROPERTY_TYPES.RANGE) { - return DerivedPropertyType.RANGE; - } else { - return DerivedPropertyType.COMPLEX; - } + isTypeSimple(typeName: string): boolean { + return ToscaTypeHelper.isTypeSimple(typeName) || this.isTypeDerivedFromSimple(typeName) && (this.isTypeWithoutProperties(typeName)); + } + + isTypeRange(typeName: string): boolean { + return ToscaTypeHelper.isTypeRange(typeName); } isTypeWithoutProperties(typeName: string): boolean { - if (this.dataTypeMap.get(typeName) === undefined) { - return true; - } - return this.dataTypeMap.get(typeName).properties === undefined || - this.dataTypeMap.get(typeName).properties.length == 0; + if (this.dataTypeMap.get(typeName) === undefined) { + return true; + } + return this.dataTypeMap.get(typeName).properties === undefined || + this.dataTypeMap.get(typeName).properties.length == 0; } isTypeDerivedFromSimple(typeName: string): boolean { @@ -134,27 +129,28 @@ export class InputListItemComponent implements OnInit { return true; } - isTypeSimple(typeName: string): boolean { - if (this.getType(typeName) == DerivedPropertyType.SIMPLE) { - return true; - } - return this.isTypeDerivedFromSimple(typeName) && (this.isTypeWithoutProperties(typeName)); + isTypeList(typeName: string): boolean { + return ToscaTypeHelper.isTypeList(typeName); } - isTypeRange(typeName: string): boolean { - return this.getType(typeName) == DerivedPropertyType.RANGE; + isTypeMap(typeName: string): boolean { + return ToscaTypeHelper.isTypeMap(typeName); } - isTypeList(typeName: string): boolean { - return this.getType(typeName) == DerivedPropertyType.LIST; + isTypeComplex(typeName: string): boolean { + return ToscaTypeHelper.isTypeComplex(typeName); } - isTypeMap(typeName: string): boolean { - return this.getType(typeName) == DerivedPropertyType.MAP; + isTypeNumber(type: string): boolean { + return ToscaTypeHelper.isTypeNumber(type); } - isTypeComplex(typeName: string): boolean { - return !this.isTypeSimple(typeName) && !this.isTypeList(typeName) && !this.isTypeMap(typeName) && !this.isTypeRange(typeName); + isTypeBoolean(type: string): boolean { + return ToscaTypeHelper.isTypeBoolean(type); + } + + isTypeLiteral(type: string): boolean { + return ToscaTypeHelper.isTypeLiteral(type); } expandAndCollapse() { @@ -180,7 +176,7 @@ export class InputListItemComponent implements OnInit { } onValueChange(value: any): void { - if (this.isNumber(this.type.name)) { + if (this.isTypeNumber(this.type.name)) { this.emitValueChangeEvent(this.parseNumber(value)); return; } @@ -297,14 +293,20 @@ export class InputListItemComponent implements OnInit { } getSimpleValueInputType() { - if (this.isNumber(this.type.name)){ + if (this.isTypeNumber(this.type.name)){ return 'number'; } return 'text'; } - isNumber(type: string): boolean { - return type === PROPERTY_TYPES.INTEGER || type === PROPERTY_TYPES.FLOAT; + buildSchemaGroupProperty(): SchemaPropertyGroupModel { + const schemaProperty = new SchemaProperty(); + if (this.schema.property.type === PROPERTY_TYPES.MAP || this.schema.property.type === PROPERTY_TYPES.LIST) { + schemaProperty.type = PROPERTY_TYPES.STRING; + } else { + schemaProperty.type = this.schema.property.type + } + return new SchemaPropertyGroupModel(schemaProperty); } private parseBoolean(value: any) { diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.module.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.module.ts index c829ff9e5a..b4fc9d1bdb 100644 --- a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.module.ts +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.module.ts @@ -36,31 +36,32 @@ import {PropertyTableModule} from "app/ng2/components/logic/properties-table/pro import {ToscaFunctionModule} from '../../../properties-assignment/tosca-function/tosca-function.module'; @NgModule({ - declarations: [ - InterfaceOperationHandlerComponent, - PropertyParamRowComponent, - AddInputComponent, - InputListComponent, - InputListItemComponent - ], - imports: [ - CommonModule, - SdcUiComponentsModule, - FormsModule, - FormElementsModule, - TranslateModule, - UiElementsModule, - PropertyTableModule, - ReactiveFormsModule, - ToscaFunctionModule - ], - exports: [ - PropertyParamRowComponent - ], - entryComponents: [ - InterfaceOperationHandlerComponent - ], - providers: [] + declarations: [ + InterfaceOperationHandlerComponent, + PropertyParamRowComponent, + AddInputComponent, + InputListComponent, + InputListItemComponent + ], + imports: [ + CommonModule, + SdcUiComponentsModule, + FormsModule, + FormElementsModule, + TranslateModule, + UiElementsModule, + PropertyTableModule, + ReactiveFormsModule, + ToscaFunctionModule + ], + exports: [ + PropertyParamRowComponent, + InputListItemComponent + ], + entryComponents: [ + InterfaceOperationHandlerComponent + ], + providers: [] }) export class InterfaceOperationHandlerModule { diff --git a/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/add-property/add-property.component.html b/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/add-property/add-property.component.html new file mode 100644 index 0000000000..af72e6d6d6 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/add-property/add-property.component.html @@ -0,0 +1,66 @@ +
+ +
+ +
+
+ + +
+
+ + +
+
+ {{'PROPERTY_REQUIRED_LABEL' | translate}} +
+
+ + +
+
+ +
+ + +
+
+ + + {{'PROPERTY_SET_DEFAULT_VALUE_MSG' | translate}} + + +
    + + +
+
+
+
+
diff --git a/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/add-property/add-property.component.less b/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/add-property/add-property.component.less new file mode 100644 index 0000000000..2c3300237c --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/add-property/add-property.component.less @@ -0,0 +1,104 @@ +/* + * - + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +.add-property-container { + max-width: 100%; +} + +.default-value-container { + overflow: scroll; + max-height: 300px; + max-width: 100%; + + ul { + margin: 0 0 0 20px; + list-style: none; + line-height: 2em; + } + + &::-webkit-scrollbar-track { + border: 0; + } +} + +li { + position: relative; + + &:before { + position: absolute; + left: -15px; + top: 0; + content: ''; + display: block; + border-left: 1px solid #ddd; + height: 1em; + border-bottom: 1px solid #ddd; + width: 10px; + } + + &:after { + position: absolute; + left: -15px; + bottom: -7px; + content: ''; + display: block; + border-left: 1px solid #ddd; + height: 100%; + } + + &.root { + margin: 0 0 0 -20px; + + &:before { + display: none; + } + + &:after { + display: none; + } + } + + &:last-child { + &:after { + display: none + } + } +} + +.input-label { + margin: 0; + font-weight: bold; +} + +.input-value { + display: flex; + flex-flow: row nowrap; + gap: 7px; + + input { + min-width: 150px; + max-width: 250px; + } +} + +.empty-value { + color: #aaaaaa; +} \ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/add-property/add-property.component.spec.ts b/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/add-property/add-property.component.spec.ts new file mode 100644 index 0000000000..7e2c312792 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/add-property/add-property.component.spec.ts @@ -0,0 +1,88 @@ +/* + * - + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; + +import {AddPropertyComponent} from './add-property.component'; +import {FormsModule, ReactiveFormsModule} from "@angular/forms"; +import {TranslateModule} from "../../../../shared/translator/translate.module"; +import {UiElementsModule} from "../../../../components/ui/ui-elements.module"; +import {Component, Input} from "@angular/core"; +import {DataTypeModel} from "../../../../../models/data-types"; +import {SchemaPropertyGroupModel} from "../../../../../models/schema-property"; +import {DataTypeService} from "../../../../services/data-type.service"; +import {TranslateService} from "../../../../shared/translator/translate.service"; + +@Component({selector: 'app-input-list-item', template: ''}) +class InputListItemStubComponent { + @Input() valueObjRef: any; + @Input() name: string; + @Input() dataTypeMap: Map; + @Input() type: DataTypeModel; + @Input() schema: SchemaPropertyGroupModel; + @Input() nestingLevel: number; + @Input() isExpanded: boolean = false; + @Input() isListChild: boolean = false; + @Input() isMapChild: boolean = false; + @Input() listIndex: number; + @Input() isViewOnly: boolean; + @Input() allowDeletion: boolean = false; +} + +describe('AddPropertyComponent', () => { + let dataTypeServiceMock: Partial = { + findAllDataTypesByModel: jest.fn(args => { + return Promise.resolve(new Map()); + }) + }; + let translateServiceMock: Partial = { + translate: jest.fn((str: string) => {}) + }; + let component: AddPropertyComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [AddPropertyComponent, InputListItemStubComponent], + imports: [ + FormsModule, + ReactiveFormsModule, + TranslateModule, + UiElementsModule + ], + providers: [ + {provide: DataTypeService, useValue: dataTypeServiceMock}, + {provide: TranslateService, useValue: translateServiceMock} + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AddPropertyComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/add-property/add-property.component.ts b/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/add-property/add-property.component.ts new file mode 100644 index 0000000000..dc1a0329ed --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/add-property/add-property.component.ts @@ -0,0 +1,233 @@ +/* + * - + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +import {Component, EventEmitter, Input, OnDestroy, OnInit, Output} from '@angular/core'; +import {PropertyBEModel} from "../../../../../models/properties-inputs/property-be-model"; +import {AbstractControl, FormControl, FormGroup, ValidationErrors, Validators} from "@angular/forms"; +import {PROPERTY_DATA} from "../../../../../utils/constants"; +import {DataTypeService} from "../../../../services/data-type.service"; +import {DataTypeModel} from "../../../../../models/data-types"; +import {Subscription} from "rxjs"; +import {ToscaTypeHelper} from "../../../../../utils/tosca-type-helper"; +import {SchemaProperty, SchemaPropertyGroupModel} from "../../../../../models/schema-property"; + +@Component({ + selector: 'app-add-property', + templateUrl: './add-property.component.html', + styleUrls: ['./add-property.component.less'] +}) +export class AddPropertyComponent implements OnInit, OnDestroy { + + @Input() property: PropertyBEModel; + @Input() readOnly: boolean = true; + @Input() model: string; + + @Output() onValidityChange: EventEmitter = new EventEmitter(); + + private valueChangesSub: Subscription; + private descriptionForm: FormControl = new FormControl(undefined); + private requiredForm: FormControl = new FormControl(false, Validators.required); + nameForm: FormControl = new FormControl(undefined, [Validators.required]); + typeForm: FormControl = new FormControl(undefined, Validators.required); + schemaForm: FormControl = new FormControl(undefined, (control: AbstractControl): ValidationErrors | null => { + if (this.typeNeedsSchema() && !control.value) { + return {required: true}; + } + return null; + }); + hasDefaultValueForm: FormControl = new FormControl(false, Validators.required); + defaultValueForm: FormControl = new FormControl(undefined); + formGroup: FormGroup = new FormGroup({ + 'name': this.nameForm, + 'description': this.descriptionForm, + 'type': this.typeForm, + 'required': this.requiredForm, + 'schema': this.schemaForm, + 'defaultValue': this.defaultValueForm, + 'hasDefaultValue': this.hasDefaultValueForm, + }); + + isLoading: boolean = false; + showSchema: boolean = false; + typeList: string[]; + dataTypeMap: Map; + dataType: DataTypeModel; + schemaTypeList: string[]; + + constructor(private dataTypeService: DataTypeService) { + } + + ngOnInit(): void { + this.isLoading = true; + this.initTypeAndSchemaDropdown().then(() => this.updateDataType()); + this.initForm(); + this.valueChangesSub = this.formGroup.valueChanges.subscribe(() => { + this.emitValidityChange(); + }); + } + + ngOnDestroy(): void { + if (this.valueChangesSub) { + this.valueChangesSub.unsubscribe(); + } + } + + onSchemaChange(): void { + this.resetDefaultValue(); + } + + onTypeChange(): void { + this.schemaForm.setValue(null); + this.showSchema = this.typeNeedsSchema(); + this.updateDataType(); + this.resetDefaultValue(); + } + + private updateDataType(): void { + this.dataType = this.dataTypeMap.get(this.typeForm.value); + } + + private initForm(): void { + if (!this.property) { + return; + } + + this.nameForm.setValue(this.property.name); + this.descriptionForm.setValue(this.property.description); + this.typeForm.setValue(this.property.type); + this.showSchema = this.typeNeedsSchema(); + this.requiredForm.setValue(this.property.required); + this.schemaForm.setValue(this.property.schemaType); + this.initDefaultValueForm(); + } + + private initDefaultValueForm() { + if (this.property.defaultValue == undefined) { + return; + } + let defaultValue; + if (!this.isTypeSimple() && typeof this.property.defaultValue === 'string') { + defaultValue = JSON.parse(this.property.defaultValue); + } else { + defaultValue = this.property.defaultValue; + } + this.defaultValueForm.setValue(defaultValue); + this.hasDefaultValueForm.setValue(true); + } + + private typeNeedsSchema() { + return PROPERTY_DATA.SCHEMA_TYPES.indexOf(this.typeForm.value) > -1; + } + + private initTypeAndSchemaDropdown(): Promise> { + const primitiveTypes: string[] = Array.from(PROPERTY_DATA.TYPES).sort((a, b) => a.localeCompare(b)); + const promise = this.dataTypeService.findAllDataTypesByModel(this.model); + promise.then((dataTypeMap: Map) => { + this.dataTypeMap = dataTypeMap; + const nonPrimitiveTypes: string[] = Array.from(dataTypeMap.keys()).filter(type => { + return primitiveTypes.indexOf(type) === -1; + }); + nonPrimitiveTypes.sort((a, b) => a.localeCompare(b)); + this.typeList = [...primitiveTypes, ...nonPrimitiveTypes]; + this.schemaTypeList = Array.from(this.typeList); + this.isLoading = false; + }); + return promise; + } + + private emitValidityChange(): void { + const isValid: boolean = this.formGroup.valid; + this.onValidityChange.emit({ + isValid: isValid, + property: isValid ? this.buildPropertyFromForm() : undefined + }); + } + + private buildPropertyFromForm(): PropertyBEModel { + const property = new PropertyBEModel(); + property.name = this.nameForm.value; + property.type = this.typeForm.value; + if (this.schemaForm.value) { + property.schemaType = this.schemaForm.value; + } + property.description = this.descriptionForm.value; + if (this.hasDefaultValueForm.value === true) { + property.defaultValue = this.defaultValueForm.value; + } + return property; + } + + public isTypeSimple(): boolean { + return ToscaTypeHelper.isTypeSimple(this.typeForm.value); + } + + public isTypeList(): boolean { + return ToscaTypeHelper.isTypeList(this.typeForm.value); + } + + public isTypeMap(): boolean { + return ToscaTypeHelper.isTypeMap(this.typeForm.value); + } + + public isTypeComplex(): boolean { + return ToscaTypeHelper.isTypeComplex(this.typeForm.value); + } + + private isTypeRange() { + return ToscaTypeHelper.isTypeRange(this.typeForm.value); + } + + onPropertyValueChange($event: any): void { + this.defaultValueForm.setValue($event.value); + } + + showDefaultValue(): boolean { + if (this.readOnly) { + return this.defaultValueForm.value != undefined && this.dataTypeMap && this.typeForm.valid && this.schemaForm.valid; + } + return this.dataTypeMap && this.typeForm.valid && this.schemaForm.valid; + } + + getDataType(type: string): DataTypeModel { + return this.dataTypeMap.get(type); + } + + private resetDefaultValue(): void { + this.defaultValueForm.reset(); + if (this.isTypeComplex() || this.isTypeMap()) { + this.defaultValueForm.setValue({}); + } else if (this.isTypeList() || this.isTypeRange()) { + this.defaultValueForm.setValue([]); + } + } + + buildSchemaGroupProperty(): SchemaPropertyGroupModel { + const schemaProperty = new SchemaProperty(); + schemaProperty.type = this.schemaForm.value + return new SchemaPropertyGroupModel(schemaProperty); + } + +} + +export class PropertyValidationEvent { + isValid: boolean; + property: PropertyBEModel; +} \ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/type-workspace-properties.component.html b/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/type-workspace-properties.component.html index 61c319eb6f..ec67a02a1b 100644 --- a/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/type-workspace-properties.component.html +++ b/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/type-workspace-properties.component.html @@ -25,35 +25,36 @@ +
Add Property
{{header.title}}
-
-
- {{'PROPERTY_LIST_EMPTY_MESSAGE' | translate}} +
+ {{'PROPERTY_LIST_EMPTY_MESSAGE' | translate}} +
+
+ -
- - -
- {{property.type}} -
-
- {{property.getSchemaType() || ''}} -
-
- {{property.description}} -
-
+
+ {{property.type}} +
+
+ {{property.schemaType || ''}}
+
+ {{property.required ? 'Yes' : 'No'}} +
+
+ {{property.description}} +
+
diff --git a/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/type-workspace-properties.component.less b/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/type-workspace-properties.component.less index 9c101e8ce3..fd43bd94a1 100644 --- a/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/type-workspace-properties.component.less +++ b/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/type-workspace-properties.component.less @@ -29,7 +29,7 @@ #left-top-bar { float: left; - width: 186px; + width: 200px; ::-webkit-input-placeholder { font-style: italic; @@ -56,7 +56,7 @@ -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; - width: 270px; + width: 290px; height: 32px; line-height: 32px; border: 1px solid @main_color_o; @@ -127,11 +127,15 @@ } .flex-item:nth-child(4) { + flex-grow: 3; + } + + .flex-item:nth-child(5) { flex-grow: 20; white-space: normal; } - .flex-item:nth-child(5) { + .flex-item:nth-child(6) { flex-grow: 3; padding-top: 10px; } diff --git a/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/type-workspace-properties.component.spec.ts b/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/type-workspace-properties.component.spec.ts index 6be572d16a..e6e9c12d14 100644 --- a/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/type-workspace-properties.component.spec.ts +++ b/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/type-workspace-properties.component.spec.ts @@ -30,9 +30,11 @@ import {Observable} from "rxjs/Observable"; import {DataTypeModel} from "../../../../models/data-types"; import {Component, ViewChild} from "@angular/core"; import {PropertyBEModel} from "../../../../models/properties-inputs/property-be-model"; +import {ModalService} from "../../../services/modal.service"; describe('TypeWorkspacePropertiesComponent', () => { const messages = require("../../../../../assets/languages/en_US.json"); + let modalService: Partial = {}; let testHostComponent: TestHostComponent; let testHostFixture: ComponentFixture; let dataTypeServiceMock: Partial = { @@ -62,7 +64,8 @@ describe('TypeWorkspacePropertiesComponent', () => { ], providers: [ {provide: DataTypeService, useValue: dataTypeServiceMock}, - {provide: TranslateService, useValue: translateServiceMock} + {provide: TranslateService, useValue: translateServiceMock}, + {provide: ModalService, useValue: modalService} ] }) .compileComponents(); diff --git a/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/type-workspace-properties.component.ts b/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/type-workspace-properties.component.ts index 931fd3d97c..90bc89ae08 100644 --- a/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/type-workspace-properties.component.ts +++ b/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/type-workspace-properties.component.ts @@ -23,93 +23,173 @@ import {Component, Input, OnInit} from '@angular/core'; import {DataTypeModel} from "../../../../models/data-types"; import {DataTypeService} from "../../../services/data-type.service"; import {PropertyBEModel} from "../../../../models/properties-inputs/property-be-model"; -import { Subject } from "rxjs"; +import {Subject} from "rxjs"; import {debounceTime, distinctUntilChanged} from "rxjs/operators"; +import {ModalService} from "../../../services/modal.service"; +import {ModalModel} from "../../../../models/modal"; +import {ButtonModel} from "../../../../models/button"; +import {TranslateService} from "../../../shared/translator/translate.service"; +import {AddPropertyComponent, PropertyValidationEvent} from "./add-property/add-property.component"; @Component({ - selector: 'app-type-workspace-properties', - templateUrl: './type-workspace-properties.component.html', - styleUrls: ['./type-workspace-properties.component.less'] + selector: 'app-type-workspace-properties', + templateUrl: './type-workspace-properties.component.html', + styleUrls: ['./type-workspace-properties.component.less'] }) export class TypeWorkspacePropertiesComponent implements OnInit { - @Input() isViewOnly = true; - @Input() dataType: DataTypeModel = new DataTypeModel(); - - properties: Array = []; - filteredProperties: Array = []; - tableHeadersList: Array = []; - tableSortBy: string = 'name'; - tableColumnReverse: boolean = false; - tableFilterTerm: string = undefined; - tableSearchTermUpdate = new Subject(); - - constructor(private dataTypeService: DataTypeService) { } - - ngOnInit(): void { - this.initTable(); - this.initProperties(); - this.tableSearchTermUpdate.pipe( - debounceTime(400), - distinctUntilChanged()) - .subscribe(searchTerm => { - this.filter(searchTerm); - }); - } - - private initTable(): void { - this.tableHeadersList = [ - {title: 'Name', property: 'name'}, - {title: 'Type', property: 'type'}, - {title: 'Schema', property: 'schema.property.type'}, - {title: 'Description', property: 'description'}, - ]; - - this.tableSortBy = this.tableHeadersList[0].property; - } - - private initProperties(): void { - this.dataTypeService.findAllProperties(this.dataType.uniqueId).subscribe(properties => { - this.properties = properties.map(value => new PropertyBEModel(value)); - this.filteredProperties = Array.from(this.properties); - this.sort(); - }); - } - - onUpdateSort(property: string): void { - if (this.tableSortBy === property) { - this.tableColumnReverse = !this.tableColumnReverse; - } else { - this.tableColumnReverse = false; - this.tableSortBy = property; + @Input() isViewOnly = true; + @Input() dataType: DataTypeModel = new DataTypeModel(); + + properties: Array = []; + filteredProperties: Array = []; + tableHeadersList: Array = []; + tableSortBy: string = 'name'; + tableColumnReverse: boolean = false; + tableFilterTerm: string = undefined; + tableSearchTermUpdate = new Subject(); + + constructor(private dataTypeService: DataTypeService, private modalService: ModalService, private translateService: TranslateService) { + } + + ngOnInit(): void { + this.initTable(); + this.initProperties(); + this.tableSearchTermUpdate.pipe( + debounceTime(400), + distinctUntilChanged()) + .subscribe(searchTerm => { + this.filter(searchTerm); + }); + } + + private initTable(): void { + this.tableHeadersList = [ + {title: 'Name', property: 'name'}, + {title: 'Type', property: 'type'}, + {title: 'Schema', property: 'schema.property.type'}, + {title: 'Required', property: 'required'}, + {title: 'Description', property: 'description'}, + ]; + + this.tableSortBy = this.tableHeadersList[0].property; } - this.sort(); - } - - private sort(): void { - const field = this.tableSortBy; - this.filteredProperties = this.filteredProperties.sort((property1, property2) => { - const result = property1[field] > property2[field] ? 1 : property1[field] < property2[field] ? -1 : 0; - return this.tableColumnReverse ? result * -1 : result; - }); - } - - private filter(searchTerm: string): void { - if (searchTerm) { - searchTerm = searchTerm.toLowerCase(); - this.filteredProperties = this.properties.filter(property => - property.name.toLowerCase().includes(searchTerm) - || property.type.toLowerCase().includes(searchTerm) - || (property.getSchemaType() && property.getSchemaType().toLowerCase().includes(searchTerm)) - || (property.description && property.description.toLowerCase().includes(searchTerm)) - ); - } else { - this.filteredProperties = Array.from(this.properties); + + private initProperties(): void { + this.dataTypeService.findAllProperties(this.dataType.uniqueId).subscribe(properties => { + this.properties = properties.map(value => { + const property = new PropertyBEModel(value); + if (property.defaultValue) { + property.defaultValue = JSON.parse(property.defaultValue); + } + + return property; + }); + this.filteredProperties = Array.from(this.properties); + this.sort(); + }); + } + + onUpdateSort(property: string): void { + if (this.tableSortBy === property) { + this.tableColumnReverse = !this.tableColumnReverse; + } else { + this.tableColumnReverse = false; + this.tableSortBy = property; + } + this.sort(); + } + + private sort(): void { + const field = this.tableSortBy; + this.filteredProperties.sort((property1, property2) => { + let result = 0; + if (property1[field] > property2[field]) { + result = 1; + } else if (property1[field] < property2[field]) { + result = -1; + } + return this.tableColumnReverse ? result * -1 : result; + }); + } + + private filter(searchTerm?: string): void { + if (searchTerm) { + searchTerm = searchTerm.toLowerCase(); + this.filteredProperties = this.properties.filter(property => + property.name.toLowerCase().includes(searchTerm) + || property.type.toLowerCase().includes(searchTerm) + || (property.getSchemaType() && property.getSchemaType().toLowerCase().includes(searchTerm)) + || (property.description && property.description.toLowerCase().includes(searchTerm)) + ); + } else { + this.filteredProperties = Array.from(this.properties); + } + this.sort(); + } + + private addProperty(property: PropertyBEModel) { + this.properties.push(property); + this.filter(); + } + + onClickAddProperty() { + this.openAddPropertyModal(); + } + + private openAddPropertyModal(property?: PropertyBEModel, readOnly: boolean = false) { + const modalTitle = this.translateService.translate('PROPERTY_ADD_MODAL_TITLE'); + const modalButtons = []; + let disableSaveButtonFlag = true; + let propertyFromModal: PropertyBEModel = undefined; + const modal = this.modalService.createCustomModal(new ModalModel( + 'md', + modalTitle, + null, + modalButtons, + null + )); + if (readOnly) { + modalButtons.push(new ButtonModel(this.translateService.translate('MODAL_CLOSE'), 'outline grey', () => { + this.modalService.closeCurrentModal(); + })); + } else { + modalButtons.push(new ButtonModel(this.translateService.translate('MODAL_SAVE'), 'blue', + () => { + disableSaveButtonFlag = true; + this.dataTypeService.createProperty(this.dataType.uniqueId, propertyFromModal).subscribe(property => { + this.addProperty(new PropertyBEModel(property)); + }); + this.modalService.closeCurrentModal(); + }, + (): boolean => { + return disableSaveButtonFlag + } + )); + + modalButtons.push(new ButtonModel(this.translateService.translate('MODAL_CANCEL'), 'outline grey', () => { + this.modalService.closeCurrentModal(); + })); + } + + this.modalService.addDynamicContentToModalAndBindInputs(modal, AddPropertyComponent, { + 'readOnly': readOnly, + 'property': property + }); + modal.instance.dynamicContent.instance.onValidityChange.subscribe((validationEvent: PropertyValidationEvent) => { + disableSaveButtonFlag = !validationEvent.isValid; + if (validationEvent.isValid) { + propertyFromModal = validationEvent.property; + } + }); + modal.instance.open(); + } + + onRowClick(property: PropertyBEModel) { + this.openAddPropertyModal(property, true); } - this.sort(); - } } interface TableHeader { - title: string; - property: string; + title: string; + property: string; } \ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace.component.html b/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace.component.html index 193c94e6ad..4d29e8673a 100644 --- a/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace.component.html +++ b/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace.component.html @@ -44,7 +44,7 @@
- +
diff --git a/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace.module.ts b/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace.module.ts index 021f84af09..5b2d3bf030 100644 --- a/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace.module.ts +++ b/catalog-ui/src/app/ng2/pages/type-workspace/type-workspace.module.ts @@ -31,7 +31,10 @@ import {UpgradeModule} from "@angular/upgrade/static"; import {FormsModule, ReactiveFormsModule} from "@angular/forms"; import {TranslateModule} from "../../shared/translator/translate.module"; import {DataTypeService} from "../../services/data-type.service"; -import { TypeWorkspacePropertiesComponent } from './type-workspace-properties/type-workspace-properties.component'; +import {TypeWorkspacePropertiesComponent} from './type-workspace-properties/type-workspace-properties.component'; +import {ModalService} from "../../services/modal.service"; +import {AddPropertyComponent} from './type-workspace-properties/add-property/add-property.component'; +import {InterfaceOperationHandlerModule} from "../composition/interface-operatons/operation-creator/interface-operation-handler.module"; @NgModule({ imports: [ @@ -42,19 +45,22 @@ import { TypeWorkspacePropertiesComponent } from './type-workspace-properties/ty ReactiveFormsModule, TranslateModule, FormsModule, + InterfaceOperationHandlerModule, ], declarations: [ TypeWorkspaceComponent, WorkspaceMenuComponent, TypeWorkspaceGeneralComponent, - TypeWorkspacePropertiesComponent + TypeWorkspacePropertiesComponent, + AddPropertyComponent, ], providers: [ CacheService, WorkspaceMenuComponent, - DataTypeService + DataTypeService, + ModalService ], - entryComponents: [TypeWorkspaceComponent], + entryComponents: [TypeWorkspaceComponent, AddPropertyComponent], exports: [TypeWorkspaceComponent] }) export class TypeWorkspaceModule { diff --git a/catalog-ui/src/app/ng2/services/data-type.service.ts b/catalog-ui/src/app/ng2/services/data-type.service.ts index 74d48d35de..298ba90b31 100644 --- a/catalog-ui/src/app/ng2/services/data-type.service.ts +++ b/catalog-ui/src/app/ng2/services/data-type.service.ts @@ -83,6 +83,11 @@ export class DataTypeService { return this.httpClient.get>(url); } + public createProperty(id: string, property: PropertyBEModel): Observable { + const url = `${this.dataTypeUrl}/${id}/properties` + return this.httpClient.post(url, property); + } + public getConstraintsByParentTypeAndUniqueID(rootPropertyType, propertyName){ // const property = this.dataTypes[rootPropertyType].properties.filter(property => // property.name == propertyName); diff --git a/catalog-ui/src/app/utils/constants.ts b/catalog-ui/src/app/utils/constants.ts index 9d11f54db9..c8fb1966b4 100644 --- a/catalog-ui/src/app/utils/constants.ts +++ b/catalog-ui/src/app/utils/constants.ts @@ -156,6 +156,7 @@ export class PROPERTY_DATA { public static TYPES = [PROPERTY_TYPES.STRING, PROPERTY_TYPES.INTEGER, PROPERTY_TYPES.TIMESTAMP, PROPERTY_TYPES.FLOAT, PROPERTY_TYPES.BOOLEAN, PROPERTY_TYPES.JSON, PROPERTY_TYPES.SCALAR, PROPERTY_TYPES.SCALAR_FREQUENCY, PROPERTY_TYPES.SCALAR_SIZE, PROPERTY_TYPES.SCALAR_TIME, PROPERTY_TYPES.LIST, PROPERTY_TYPES.MAP, PROPERTY_TYPES.RANGE]; public static SIMPLE_TYPES = [PROPERTY_TYPES.STRING, PROPERTY_TYPES.INTEGER, PROPERTY_TYPES.TIMESTAMP, PROPERTY_TYPES.FLOAT, PROPERTY_TYPES.BOOLEAN, PROPERTY_TYPES.JSON, PROPERTY_TYPES.SCALAR, PROPERTY_TYPES.SCALAR_FREQUENCY, PROPERTY_TYPES.SCALAR_SIZE, PROPERTY_TYPES.SCALAR_TIME]; public static SIMPLE_TYPES_COMPARABLE = [PROPERTY_TYPES.STRING, PROPERTY_TYPES.INTEGER, PROPERTY_TYPES.FLOAT]; + public static SCHEMA_TYPES = [PROPERTY_TYPES.LIST, PROPERTY_TYPES.MAP]; public static SCALAR_TYPES = [PROPERTY_TYPES.SCALAR, PROPERTY_TYPES.SCALAR_FREQUENCY, PROPERTY_TYPES.SCALAR_SIZE, PROPERTY_TYPES.SCALAR_TIME]; public static ROOT_DATA_TYPE = "tosca.datatypes.Root"; public static OPENECOMP_ROOT = "org.openecomp.datatypes.Root"; diff --git a/catalog-ui/src/app/utils/tosca-type-helper.ts b/catalog-ui/src/app/utils/tosca-type-helper.ts new file mode 100644 index 0000000000..7faa90a7fe --- /dev/null +++ b/catalog-ui/src/app/utils/tosca-type-helper.ts @@ -0,0 +1,78 @@ +/* + * - + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +import {DerivedPropertyType} from "../models/properties-inputs/property-be-model"; +import {PROPERTY_DATA, PROPERTY_TYPES} from "./constants"; + +export class ToscaTypeHelper { + + private ToscaTypeHelper() { + //not designed to be instantiated + } + + public static getType(typeName: string): DerivedPropertyType { + if (PROPERTY_DATA.SIMPLE_TYPES.indexOf(typeName) > -1) { + return DerivedPropertyType.SIMPLE; + } else if (typeName === PROPERTY_TYPES.LIST) { + return DerivedPropertyType.LIST; + } else if (typeName === PROPERTY_TYPES.MAP) { + return DerivedPropertyType.MAP; + } else if (typeName === PROPERTY_TYPES.RANGE) { + return DerivedPropertyType.RANGE; + } else { + return DerivedPropertyType.COMPLEX; + } + } + + public static isTypeSimple(typeName: string): boolean { + return this.getType(typeName) == DerivedPropertyType.SIMPLE; + } + + public static isTypeList(typeName: string): boolean { + return this.getType(typeName) == DerivedPropertyType.LIST; + } + + public static isTypeMap(typeName: string): boolean { + return this.getType(typeName) == DerivedPropertyType.MAP; + } + + public static isTypeComplex(typeName: string): boolean { + return !this.isTypeSimple(typeName) && !this.isTypeList(typeName) && !this.isTypeMap(typeName); + } + + public static isTypeNumber(typeName: string): boolean { + return typeName === PROPERTY_TYPES.INTEGER || typeName === PROPERTY_TYPES.FLOAT; + } + + public static isTypeBoolean(typeName: string): boolean { + return typeName === PROPERTY_TYPES.BOOLEAN; + } + + public static isTypeLiteral(typeName: string): boolean { + return !this.isTypeNumber(typeName) && !this.isTypeBoolean(typeName) && !this.isTypeList(typeName) && !this.isTypeMap(typeName) + && !this.isTypeComplex(typeName) && !this.isTypeRange(typeName); + } + + public static isTypeRange(typeName: string): boolean { + return typeName === PROPERTY_TYPES.RANGE; + } + +} -- cgit 1.2.3-korg