diff options
Diffstat (limited to 'catalog-ui/src/app/ng2/pages')
13 files changed, 769 insertions, 177 deletions
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 @@ <ul *ngIf="isExpanded"> <li class="input-value"> <ng-container *ngIf="isViewOnly"> - {{valueObjRef}}<em class="empty-value" *ngIf="valueObjRef !== false && !valueObjRef">empty</em> + {{valueObjRef}}<em class="empty-value" *ngIf="valueObjRef !== false && !valueObjRef">{{'GENERAL_LABEL_EMPTY' | translate | lowercase}}</em> </ng-container> - <input *ngIf="!isViewOnly" [type]="getSimpleValueInputType()" name="value" - [(ngModel)]="valueObjRef" - (ngModelChange)="onValueChange($event)" - /> + <input *ngIf="!isViewOnly && isTypeLiteral(type.name)" type="text" name="value" + [(ngModel)]="valueObjRef" (ngModelChange)="onValueChange($event)"/> + <input *ngIf="!isViewOnly && isTypeNumber(type.name)" type="number" + [(ngModel)]="valueObjRef" (ngModelChange)="onValueChange($event)"/> + <select *ngIf="!isViewOnly && isTypeBoolean(type.name)" [(ngModel)]="valueObjRef" (ngModelChange)="onValueChange($event)"> + <option [value]="true">{{'GENERAL_LABEL_TRUE' | translate}}</option> + <option [value]="false">{{'GENERAL_LABEL_FALSE' | translate}}</option> + </select> </li> </ul> </ng-container> @@ -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<string> = new EventEmitter<string>(); @Output('onChildListItemDelete') onChildListItemDeleteEvent: EventEmitter<number> = new EventEmitter<number>(); - 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 @@ +<div class="add-property-container"> + <loader [display]="isLoading" [size]="'large'" [relative]="true" [loaderDelay]="500"></loader> + <form class="w-sdc-form" [formGroup]="formGroup"> + + <div class="side-by-side"> + <div class="i-sdc-form-item"> + <label class="i-sdc-form-label required">{{'PROPERTY_NAME_LABEL' | translate}}</label> + <input class="i-sdc-form-input" + type="text" + data-tests-id="property-name" + formControlName="name" + [readOnly]="readOnly"/> + </div> + <div class="i-sdc-form-item"> + <label class="i-sdc-form-label required">{{'PROPERTY_TYPE_LABEL' | translate}}</label> + <select formControlName="type" (change)="onTypeChange()" [attr.disabled]="readOnly ? readOnly : null"> + <option [ngValue]="null">{{'GENERAL_LABEL_SELECT' | translate}}</option> + <option *ngFor="let type of typeList" + [ngValue]="type">{{type}}</option> + </select> + </div> + <div class="i-sdc-form-item"> + <input type="checkbox" formControlName="required" [attr.disabled]="readOnly ? readOnly : null"/> {{'PROPERTY_REQUIRED_LABEL' | translate}} + </div> + <div class="i-sdc-form-item propertySchemaType" *ngIf="showSchema"> + <label class="i-sdc-form-label required">{{'PROPERTY_SCHEMA_LABEL' | translate}}</label> + <select formControlName="schema" (change)="onSchemaChange()" [attr.disabled]="readOnly ? readOnly : null"> + <option [ngValue]="null">{{'GENERAL_LABEL_SELECT' | translate}}</option> + <option *ngFor="let type of schemaTypeList" + [ngValue]="type">{{type}}</option> + </select> + </div> + </div> + + <div class="i-sdc-form-item"> + <label class="i-sdc-form-label">{{'PROPERTY_DESCRIPTION_LABEL' | translate}}</label> + <textarea class="i-sdc-form-textarea" + formControlName="description" + data-tests-id="property-description" + [readOnly]="readOnly"> + </textarea> + </div> + <div class="default-value-container i-sdc-form-item" *ngIf="showDefaultValue()"> + <label class="i-sdc-form-label">{{'PROPERTY_DEFAULT_VALUE_LABEL' | translate}}</label> + <ng-container *ngIf="!readOnly"> + <input type="checkbox" formControlName="hasDefaultValue" [attr.disabled]="readOnly ? readOnly : null"/> {{'PROPERTY_SET_DEFAULT_VALUE_MSG' | translate}} + </ng-container> + <ng-container *ngIf="hasDefaultValueForm.value"> + <ul> + <app-input-list-item + [name]="nameForm.value" + [type]="getDataType(typeForm.value)" + [dataTypeMap]="dataTypeMap" + [valueObjRef]="defaultValueForm.value" + [schema]="buildSchemaGroupProperty()" + [nestingLevel]="0" + [isViewOnly]="readOnly" + [allowDeletion]="false" + [isExpanded]="true" + (onValueChange)="onPropertyValueChange($event)"> + </app-input-list-item> + </ul> + </ng-container> + </div> + </form> +</div> 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<string, DataTypeModel>; + @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<DataTypeService> = { + findAllDataTypesByModel: jest.fn(args => { + return Promise.resolve(new Map()); + }) + }; + let translateServiceMock: Partial<TranslateService> = { + translate: jest.fn((str: string) => {}) + }; + let component: AddPropertyComponent; + let fixture: ComponentFixture<AddPropertyComponent>; + + 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<PropertyValidationEvent> = new EventEmitter<PropertyValidationEvent>(); + + 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<string, DataTypeModel>; + 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<Map<string, DataTypeModel>> { + 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<string, DataTypeModel>) => { + 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 @@ <input id="search-by-name" type="search" [placeholder]="'SEARCH_LABEL' | translate" [ngModel]="tableFilterTerm" (ngModelChange)="this.tableSearchTermUpdate.next($event)"/> <span class="sprite magnification-glass search-button"></span> </div> + <div class="add-btn" data-tests-id="add-link" *ngIf="!isViewOnly" (click)="onClickAddProperty()">Add Property</div> <div class="table-container-flex"> <div class="table" [ngClass]="{'view-mode': isViewOnly}"> <div class="head flex-container"> <div class="table-header head-row hand flex-item" *ngFor="let header of tableHeadersList" (click)="onUpdateSort(header.property)">{{header.title}} <span *ngIf="tableSortBy === header.property" class="table-header-sort-arrow" [ngClass]="{'down': tableColumnReverse, 'up': !tableColumnReverse}"></span> </div> - <div class="table-no-text-header head-row flex-item" *ngIf="!isViewOnly"><span class="delete-col-header"></span></div> </div> <div class="body"> - <div *ngIf="filteredProperties.length === 0" class="no-row-text"> - {{'PROPERTY_LIST_EMPTY_MESSAGE' | translate}} + <div *ngIf="filteredProperties.length === 0" class="no-row-text"> + {{'PROPERTY_LIST_EMPTY_MESSAGE' | translate}} + </div> + <div *ngFor="let property of filteredProperties" [attr.data-tests-id]="'property-row-' + property.name" class="flex-container data-row" (click)="onRowClick(property)"> + <div class="table-col-general flex-item text" [title]="property.name"> + <a [attr.data-tests-id]="'property-name-' + property.name" [ngClass]="{'disabled': isViewOnly}">{{property.name}}</a> </div> - <div *ngFor="let property of filteredProperties" [attr.data-tests-id]="'property-row-' + property.name" class="flex-container data-row"> - <div class="table-col-general flex-item text" [title]="property.name"> - <a [attr.data-tests-id]="'property-name-' + property.name" [ngClass]="{'disabled': isViewOnly}">{{property.name}}</a> - </div> - - <div class="table-col-general flex-item text" [title]="property.type"> - <span [attr.data-tests-id]="'property-type-' + property.name">{{property.type}}</span> - </div> - <div class="table-col-general flex-item text" [title]="property.getSchemaType() || ''"> - <span [attr.data-tests-id]="'property-schema-' + property.name">{{property.getSchemaType() || ''}}</span> - </div> - <div class="table-col-general flex-item text" [title]="property.description"> - <span [attr.data-tests-id]="'property-description-' + property.name" [title]="property.description">{{property.description}}</span> - </div> - <div class="table-btn-col flex-item" *ngIf="!isViewOnly"></div> + <div class="table-col-general flex-item text" [title]="property.type"> + <span [attr.data-tests-id]="'property-type-' + property.name">{{property.type}}</span> + </div> + <div class="table-col-general flex-item text" [title]="property.schemaType || ''"> + <span [attr.data-tests-id]="'property-schema-' + property.name">{{property.schemaType || ''}}</span> </div> + <div class="table-col-general flex-item text" [title]="property.required ? 'Yes' : 'No'"> + <span [attr.data-tests-id]="'property-required-' + property.name">{{property.required ? 'Yes' : 'No'}}</span> + </div> + <div class="table-col-general flex-item text" [title]="property.description || ''"> + <span [attr.data-tests-id]="'property-description-' + property.name" [title]="property.description">{{property.description}}</span> + </div> + </div> </div> </div> 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<ModalService> = {}; let testHostComponent: TestHostComponent; let testHostFixture: ComponentFixture<TestHostComponent>; let dataTypeServiceMock: Partial<DataTypeService> = { @@ -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<PropertyBEModel> = []; - filteredProperties: Array<PropertyBEModel> = []; - tableHeadersList: Array<TableHeader> = []; - tableSortBy: string = 'name'; - tableColumnReverse: boolean = false; - tableFilterTerm: string = undefined; - tableSearchTermUpdate = new Subject<string>(); - - 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<PropertyBEModel> = []; + filteredProperties: Array<PropertyBEModel> = []; + tableHeadersList: Array<TableHeader> = []; + tableSortBy: string = 'name'; + tableColumnReverse: boolean = false; + tableFilterTerm: string = undefined; + tableSearchTermUpdate = new Subject<string>(); + + 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 @@ </div> <div class="w-sdc-main-container-body-content" *ngIf="dataType"> <app-type-workspace-general *ngIf="currentMenu.state === 'general'" [dataType]="dataType"></app-type-workspace-general> - <app-type-workspace-properties *ngIf="currentMenu.state === 'properties'" [dataType]="dataType"></app-type-workspace-properties> + <app-type-workspace-properties *ngIf="currentMenu.state === 'properties'" [dataType]="dataType" [isViewOnly]="false"></app-type-workspace-properties> </div> </div> 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 { |