diff options
author | andre.schmid <andre.schmid@est.tech> | 2022-07-07 17:17:52 +0100 |
---|---|---|
committer | Michael Morris <michael.morris@est.tech> | 2022-07-18 14:22:12 +0000 |
commit | 68733163804ed2efed8223a04ab0a7a0714a8b33 (patch) | |
tree | 013f4d25042aa776120971a612a52d64db52f7a7 /catalog-ui/src/app/ng2/pages/properties-assignment | |
parent | 4e4ec8e9c21acf7f9210aaebf8f13a60542737fc (diff) |
Support for concat TOSCA function
Adds support for the concat TOSCA function in an instance property.
Refactors the TOSCA function structure so it can be more generic to
support other functions in the future.
Change-Id: I338e4138d26afe21779da57c4eeb3f2d486c20a9
Issue-ID: SDC-4095
Signed-off-by: andre.schmid <andre.schmid@est.tech>
Diffstat (limited to 'catalog-ui/src/app/ng2/pages/properties-assignment')
12 files changed, 1026 insertions, 413 deletions
diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts index 9f721d5cdd..2ae5ce8c09 100644 --- a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts @@ -61,13 +61,12 @@ import {UnsavedChangesComponent} from "app/ng2/components/ui/forms/unsaved-chang import {PropertyCreatorComponent} from "./property-creator/property-creator.component"; import {ModalService} from "../../services/modal.service"; import {DeclareListComponent} from "./declare-list/declare-list.component"; -import {ToscaFunctionComponent} from "./tosca-function/tosca-function.component"; +import {ToscaFunctionComponent, ToscaFunctionValidationEvent} from "./tosca-function/tosca-function.component"; import {CapabilitiesGroup, Capability} from "../../../models/capability"; import {ToscaPresentationData} from "../../../models/tosca-presentation"; import {Observable} from "rxjs"; import {TranslateService} from "../../shared/translator/translate.service"; -import {ToscaGetFunctionDtoBuilder} from '../../../models/tosca-get-function-dto'; -import {ToscaGetFunction} from "../../../models/tosca-get-function"; +import {ToscaFunction} from "../../../models/tosca-function"; const SERVICE_SELF_TITLE = "SELF"; @Component({ @@ -539,36 +538,37 @@ export class PropertiesAssignmentComponent { const modalTitle = this.translateService.translate('TOSCA_FUNCTION_MODAL_TITLE'); const modalButtons = []; let disableSaveButtonFlag = true; + const modal = this.modalService.createCustomModal(new ModalModel( + 'sm', + modalTitle, + null, + modalButtons, + null /* type */ + )); modalButtons.push(new ButtonModel(this.translateService.translate('MODAL_SAVE'), 'blue', () => { - const toscaGetFunction: ToscaGetFunction = modal.instance.dynamicContent.instance.toscaGetFunction; - if (toscaGetFunction.functionType) { - this.updateCheckedInstancePropertyGetFunctionValue(toscaGetFunction); + const toscaGetFunction: ToscaFunction = modal.instance.dynamicContent.instance.toscaFunctionForm.value; + if (toscaGetFunction) { + this.updateCheckedInstancePropertyFunctionValue(toscaGetFunction); } else { this.clearCheckedInstancePropertyValue(); } - modal.instance.close(); + this.modalService.closeCurrentModal(); }, (): boolean => { return disableSaveButtonFlag } )); const checkedInstanceProperty = this.buildCheckedInstanceProperty(); modalButtons.push(new ButtonModel(this.translateService.translate('MODAL_CANCEL'), 'outline grey', () => { - modal.instance.close(); + this.modalService.closeCurrentModal(); })); - const modal = this.modalService.createCustomModal(new ModalModel( - 'sm', - modalTitle, - null, - modalButtons, - null /* type */ - )); + this.modalService.addDynamicContentToModalAndBindInputs(modal, ToscaFunctionComponent, { 'property': checkedInstanceProperty, 'componentInstanceMap': this.componentInstanceMap }); - modal.instance.dynamicContent.instance.onValidityChange.subscribe(isValid => { - disableSaveButtonFlag = !isValid; + modal.instance.dynamicContent.instance.onValidityChange.subscribe((validationEvent: ToscaFunctionValidationEvent) => { + disableSaveButtonFlag = !validationEvent.isValid; }); modal.instance.open(); } @@ -577,23 +577,13 @@ export class PropertiesAssignmentComponent { const checkedInstanceProperty: PropertyBEModel = this.buildCheckedInstanceProperty(); checkedInstanceProperty.getInputValues = null; checkedInstanceProperty.value = null; - checkedInstanceProperty.toscaGetFunction = null; + checkedInstanceProperty.toscaFunction = null; this.updateInstanceProperty(checkedInstanceProperty); } - private updateCheckedInstancePropertyGetFunctionValue(toscaGetFunction: ToscaGetFunction) { - const toscaGetFunctionBuilder: ToscaGetFunctionDtoBuilder = - new ToscaGetFunctionDtoBuilder() - .withPropertyUniqueId(toscaGetFunction.propertyUniqueId) - .withFunctionType(toscaGetFunction.functionType) - .withPropertySource(toscaGetFunction.propertySource) - .withPropertyName(toscaGetFunction.propertyName) - .withSourceName(toscaGetFunction.sourceName) - .withSourceUniqueId(toscaGetFunction.sourceUniqueId) - .withPropertyPathFromSource(toscaGetFunction.propertyPathFromSource); - + private updateCheckedInstancePropertyFunctionValue(toscaFunction: ToscaFunction) { const checkedProperty: PropertyBEModel = this.buildCheckedInstanceProperty(); - checkedProperty.toscaGetFunction = toscaGetFunctionBuilder.build(); + checkedProperty.toscaFunction = toscaFunction; this.updateInstanceProperty(checkedProperty); } diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-concat-function/tosca-concat-function.component.html b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-concat-function/tosca-concat-function.component.html new file mode 100644 index 0000000000..6320058d9d --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-concat-function/tosca-concat-function.component.html @@ -0,0 +1,48 @@ +<!-- + ~ - + ~ ============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========================================================= + --> + +<div class="component-container"> + <ng-container [formGroup]="formGroup"> + <div formArrayName="concatParameterList"> + <div *ngFor="let parameter of parameters; let idx = index"> + <div *ngIf="idx > 0" class="text-center"><span class="concat-plus-icon"></span></div> + <div class="parameter-card"> + <div class="card-content"> + <ng-container *ngIf="parameter.type === STRING_FUNCTION_TYPE"> + <input type="text" [formControlName]="idx" [value]="parameter.value"/><br/> + </ng-container> + <ng-container *ngIf="parameter.type !== STRING_FUNCTION_TYPE"> + <tosca-function [property]="propertyInputList[idx]" [componentInstanceMap]="componentInstanceMap" [allowClear]="false" + (onValidityChange)="onFunctionValidityChange($event, idx)"> + </tosca-function> + </ng-container> + <div class="buttons-container"> + <span class="delete-icon" (click)="removeParameter(idx)"></span> + </div> + </div> + </div> + </div> + </div> + </ng-container> + <div class="buttons-container"> + <a class="add-link" (click)="addStringParameter()">String Value</a> <a class="add-link" (click)="addFunction()">String Value Expression</a> + </div> +</div> diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-concat-function/tosca-concat-function.component.less b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-concat-function/tosca-concat-function.component.less new file mode 100644 index 0000000000..b9c59831ad --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-concat-function/tosca-concat-function.component.less @@ -0,0 +1,98 @@ +/* + * - + * ============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 "../../../../../../assets/styles/mixins.less"; +@import "../../../../../../assets/styles/sprite.less"; + +.component-container { + max-height: 500px; + overflow: scroll; + padding: 0 5px; + &::-webkit-scrollbar-track { + border: 0; + } +} + +.buttons-container { + display: flex; + flex-direction: row; + justify-content: flex-end; + gap: 10px; + margin-bottom: 10px; + + .add-link { + .f-color.a(); + .f-type._14_m(); + cursor: pointer; + + &:before { + .sprite-new(); + .plus-icon(); + margin-right: 5px; + content: ""; + } + + &:hover { + .f-color.b(); + &:before { + .sprite-new(); + .plus-icon-hover(); + } + } + } + + .delete-icon { + .sprite-new(); + .delete-btn(); + cursor: pointer; + } +} + +.parameter-card { + border: 2px solid @main_color_o; + box-shadow: 0 0 0 0 rgba(0,0,0,0.2); + //padding: 10px; + border-radius: 2px; + transition: 0.3s; + margin-bottom: 5px; + &:hover { + box-shadow: 0 1px 8px 2px rgba(0,0,0,0.2); + } + + .card-content { + padding: 5px 10px; + } + + .text-center { + text-align: center; + } + + input { + border: solid 1px @main_color_o; + } +} + +.concat-plus-icon { + .sprite-new(); + background-position: -216px -1388px; + width: 14px; + height: 14px; +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-concat-function/tosca-concat-function.component.spec.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-concat-function/tosca-concat-function.component.spec.ts new file mode 100644 index 0000000000..5c9af47361 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-concat-function/tosca-concat-function.component.spec.ts @@ -0,0 +1,57 @@ +/* + * - + * ============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 {ToscaConcatFunctionComponent} from './tosca-concat-function.component'; +import {FormsModule, ReactiveFormsModule} from "@angular/forms"; +import {ToscaFunctionComponent} from "../tosca-function.component"; +import {TranslateModule} from "../../../../shared/translator/translate.module"; +import {ToscaGetFunctionComponent} from "../tosca-get-function/tosca-get-function.component"; +import {UiElementsModule} from "../../../../components/ui/ui-elements.module"; + +describe('ToscaConcatFunctionComponent', () => { + let component: ToscaConcatFunctionComponent; + let fixture: ComponentFixture<ToscaConcatFunctionComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ToscaConcatFunctionComponent, ToscaFunctionComponent, ToscaGetFunctionComponent], + imports: [ + FormsModule, + ReactiveFormsModule, + TranslateModule, + UiElementsModule + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ToscaConcatFunctionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-concat-function/tosca-concat-function.component.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-concat-function/tosca-concat-function.component.ts new file mode 100644 index 0000000000..d808c284a8 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-concat-function/tosca-concat-function.component.ts @@ -0,0 +1,140 @@ +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {FormArray, FormControl, FormGroup, Validators} from "@angular/forms"; +import {ToscaConcatFunction} from "../../../../../models/tosca-concat-function"; +import {ToscaFunctionParameter} from "../../../../../models/tosca-function-parameter"; +import {ToscaStringParameter} from "../../../../../models/tosca-string-parameter"; +import {ToscaFunctionType} from "../../../../../models/tosca-function-type.enum"; +import {PropertyBEModel} from "../../../../../models/properties-inputs/property-be-model"; +import {PROPERTY_TYPES} from "../../../../../utils/constants"; +import {InstanceFeDetails} from "../../../../../models/instance-fe-details"; +import {ToscaFunctionValidationEvent} from "../tosca-function.component"; + +@Component({ + selector: 'app-tosca-concat-function', + templateUrl: './tosca-concat-function.component.html', + styleUrls: ['./tosca-concat-function.component.less'] +}) +export class ToscaConcatFunctionComponent implements OnInit { + + @Input() toscaConcatFunction: ToscaConcatFunction; + @Input() componentInstanceMap: Map<string, InstanceFeDetails> = new Map<string, InstanceFeDetails>(); + @Output() onValidFunction: EventEmitter<ToscaConcatFunction> = new EventEmitter<ToscaConcatFunction>(); + @Output() onValidityChange: EventEmitter<ToscaConcatFunctionValidationEvent> = new EventEmitter<ToscaConcatFunctionValidationEvent>(); + + concatParameterFormArray: FormArray = new FormArray([], Validators.minLength(2)); + formGroup: FormGroup = new FormGroup( + { + 'concatParameterList': this.concatParameterFormArray + } + ); + + parameters: ToscaFunctionParameter[] = []; + propertyInputList: Array<PropertyBEModel> = []; + + stringProperty: PropertyBEModel + + STRING_FUNCTION_TYPE = ToscaFunctionType.STRING + + constructor() { + this.stringProperty = new PropertyBEModel(); + this.stringProperty.type = PROPERTY_TYPES.STRING + } + + ngOnInit() { + this.initForm(); + } + + private initForm() { + this.formGroup.valueChanges.subscribe(() => { + this.onValidityChange.emit({ + isValid: this.formGroup.valid, + toscaConcatFunction: this.formGroup.valid ? this.buildConcatFunctionFromForm() : undefined + }) + if (this.formGroup.valid) { + this.onValidFunction.emit(this.buildConcatFunctionFromForm()); + } + }); + if (!this.toscaConcatFunction) { + return; + } + + if (this.toscaConcatFunction.parameters) { + this.parameters = Array.from(this.toscaConcatFunction.parameters); + for (const parameter of this.parameters) { + if (parameter.type !== PROPERTY_TYPES.STRING) { + this.propertyInputList.push(this.createStringProperty(parameter)); + this.concatParameterFormArray.push( + new FormControl(parameter, [Validators.required, Validators.minLength(1)]) + ); + } else { + this.propertyInputList.push(undefined); + this.concatParameterFormArray.push( + new FormControl(parameter.value, [Validators.required, Validators.minLength(1)]) + ); + } + } + } + } + + private buildConcatFunctionFromForm(): ToscaConcatFunction { + const toscaConcatFunction1 = new ToscaConcatFunction(); + this.concatParameterFormArray.controls.forEach(control => { + const value = control.value; + if (typeof value === 'string') { + const stringParameter = new ToscaStringParameter(); + stringParameter.value = value; + toscaConcatFunction1.parameters.push(stringParameter); + } else { + toscaConcatFunction1.parameters.push(control.value); + } + }); + + return toscaConcatFunction1; + } + + addFunction() { + this.propertyInputList.push(this.createStringProperty()); + this.parameters.push({} as ToscaFunctionParameter); + this.concatParameterFormArray.push( + new FormControl(undefined, [Validators.required, Validators.minLength(1)]) + ); + } + + addStringParameter() { + this.parameters.push({ + type: ToscaFunctionType.STRING, + value: '' + }); + this.propertyInputList.push(undefined); + this.concatParameterFormArray.push( + new FormControl('', [Validators.required, Validators.minLength(1)]) + ); + } + + removeParameter(position) { + this.propertyInputList.splice(position, 1); + this.parameters.splice(position, 1); + this.concatParameterFormArray.removeAt(position); + } + + createStringProperty(toscaFunctionParameter?: ToscaFunctionParameter) { + const property = new PropertyBEModel(); + property.type = PROPERTY_TYPES.STRING; + property.toscaFunction = toscaFunctionParameter ? toscaFunctionParameter : undefined; + property.value = toscaFunctionParameter ? toscaFunctionParameter.value : undefined; + return property; + } + + onFunctionValidityChange(event: ToscaFunctionValidationEvent, index: number) { + if (event.isValid && event.toscaFunction) { + this.concatParameterFormArray.controls[index].setValue(event.toscaFunction) + } else { + this.concatParameterFormArray.controls[index].setValue(undefined); + } + } +} + +export interface ToscaConcatFunctionValidationEvent { + isValid: boolean, + toscaConcatFunction: ToscaConcatFunction, +} diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-function.component.html b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-function.component.html index b6b313d93c..e98f688eef 100644 --- a/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-function.component.html +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-function.component.html @@ -18,32 +18,27 @@ --> <div class="tosca-function"> - <loader [display]="isLoading" [loaderDelay]="500" [relative]="true" [size]="'large'"></loader> - <form class="w-sdc-form"> + <div class="w-sdc-form" [formGroup]="formGroup"> <div class="i-sdc-form-item"> <label class="i-sdc-form-label">{{'TOSCA_FUNCTION_LABEL' | translate}}</label> - <select [(ngModel)]="toscaGetFunction.functionType" (change)="onToscaFunctionChange()" name="toscaFunctionType"> + <select formControlName="toscaFunctionType"> <option *ngFor="let toscaFunction of toscaFunctions" [ngValue]="toscaFunction">{{toscaFunction | lowercase}}</option> </select> </div> - <div class="i-sdc-form-item" *ngIf="showPropertySourceDropdown()"> - <label class="i-sdc-form-label required">{{'TOSCA_FUNCTION_PROPERTY_SOURCE_LABEL' | translate}}</label> - <select name="propertySource" [(ngModel)]="propertySource" (change)="onPropertySourceChange()"> - <option *ngFor="let propertySource of propertySourceList" - [ngValue]="propertySource">{{propertySource}}</option> - </select> + <div *ngIf="isConcatSelected()"> + <app-tosca-concat-function [toscaConcatFunction]="toscaFunction" [componentInstanceMap]="componentInstanceMap" + (onValidityChange)="onConcatFunctionValidityChange($event)"></app-tosca-concat-function> </div> - <div *ngIf="showPropertyDropdown()" class="i-sdc-form-item"> - <label class="i-sdc-form-label required">{{dropdownValuesLabel}}</label> - <select [(ngModel)]="selectedProperty" name="selectedProperty" (change)="onPropertyChange()"> - <option *ngFor="let value of propertyDropdownList" [ngValue]="value">{{value.propertyLabel}}</option> - </select> + <div *ngIf="isGetFunctionSelected()"> + <app-tosca-get-function [property]="property" [toscaGetFunction]="toscaFunction" + [componentInstanceMap]="componentInstanceMap" + [functionType]="toscaFunctionTypeForm.value" + (onValidityChange)="onGetFunctionValidityChange($event)"></app-tosca-get-function> </div> - <div *ngIf="dropDownErrorMsg">{{dropDownErrorMsg}}</div> <div *ngIf="showClearButton()" class="button-container"> <button (click)="onClearValues()" class="tlv-btn red ng-star-inserted">{{'TOSCA_FUNCTION_CLEAR_VALUE_BUTTON' | translate}}</button> </div> - </form> + </div> <loader [display]="isLoading" [size]="'medium'" [relative]="true"></loader> </div> diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-function.component.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-function.component.ts index b71a61dc01..076e1182ad 100644 --- a/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-function.component.ts +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-function.component.ts @@ -18,20 +18,18 @@ */ import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; -import {AttributeModel, ComponentMetadata, DataTypeModel, PropertyBEModel, PropertyModel} from 'app/models'; +import {ComponentMetadata, PropertyBEModel} from 'app/models'; import {TopologyTemplateService} from "../../../services/component-services/topology-template.service"; import {WorkspaceService} from "../../workspace/workspace.service"; -import {PropertiesService} from "../../../services/properties.service"; -import {PROPERTY_DATA, PROPERTY_TYPES} from "../../../../utils/constants"; -import {DataTypeService} from "../../../services/data-type.service"; import {ToscaGetFunctionType} from "../../../../models/tosca-get-function-type"; -import {TranslateService} from "../../../shared/translator/translate.service"; -import {ComponentGenericResponse} from '../../../services/responses/component-generic-response'; -import {Observable} from 'rxjs/Observable'; -import {PropertySource} from "../../../../models/property-source"; import {InstanceFeDetails} from "../../../../models/instance-fe-details"; import {ToscaGetFunction} from "../../../../models/tosca-get-function"; -import {AbstractControl, FormControl, FormGroup, ValidationErrors, ValidatorFn} from "@angular/forms"; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {ToscaFunctionType} from "../../../../models/tosca-function-type.enum"; +import {ToscaGetFunctionValidationEvent} from "./tosca-get-function/tosca-get-function.component"; +import {ToscaFunction} from "../../../../models/tosca-function"; +import {ToscaConcatFunctionValidationEvent} from "./tosca-concat-function/tosca-concat-function.component"; +import {PROPERTY_TYPES} from "../../../../utils/constants"; @Component({ selector: 'tosca-function', @@ -44,418 +42,124 @@ export class ToscaFunctionComponent implements OnInit { @Input() componentInstanceMap: Map<string, InstanceFeDetails> = new Map<string, InstanceFeDetails>(); @Input() allowClear: boolean = true; @Output() onValidFunction: EventEmitter<ToscaGetFunction> = new EventEmitter<ToscaGetFunction>(); - @Output() onValidityChange: EventEmitter<boolean> = new EventEmitter<boolean>(); + @Output() onValidityChange: EventEmitter<ToscaFunctionValidationEvent> = new EventEmitter<ToscaFunctionValidationEvent>(); - toscaGetFunctionValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => { - const toscaGetFunction: ToscaGetFunction = control.value; - const hasAnyValue = Object.keys(toscaGetFunction).find(key => toscaGetFunction[key]); - if (!hasAnyValue) { - return null; - } - const errors: ValidationErrors = {}; - if (!toscaGetFunction.sourceName) { - errors.sourceName = { required: true }; - } - if (!toscaGetFunction.functionType) { - errors.functionType = { required: true }; - } - if (!toscaGetFunction.sourceUniqueId) { - errors.sourceUniqueId = { required: true }; - } - if (!toscaGetFunction.sourceName) { - errors.sourceName = { required: true }; - } - if (!toscaGetFunction.propertyPathFromSource) { - errors.propertyPathFromSource = { required: true }; - } - if (!toscaGetFunction.propertyName) { - errors.propertyName = { required: true }; - } - if (!toscaGetFunction.propertySource) { - errors.propertySource = { required: true }; - } - return errors ? errors : null; - }; - - toscaGetFunctionForm: FormControl = new FormControl(new ToscaGetFunction(undefined), [this.toscaGetFunctionValidator]); + toscaFunctionForm: FormControl = new FormControl(undefined, [Validators.required]); + toscaFunctionTypeForm: FormControl = new FormControl(undefined, Validators.required); formGroup: FormGroup = new FormGroup({ - 'toscaGetFunction': this.toscaGetFunctionForm + 'toscaFunction': this.toscaFunctionForm, + 'toscaFunctionType': this.toscaFunctionTypeForm, }); - selectedProperty: PropertyDropdownValue; isLoading: boolean = false; - propertyDropdownList: Array<PropertyDropdownValue> = []; + toscaFunction: ToscaFunction; toscaFunctions: Array<string> = []; - propertySourceList: Array<string> = []; - instanceNameAndIdMap: Map<string, string> = new Map<string, string>(); - dropdownValuesLabel: string; - dropDownErrorMsg: string; - propertySource: string - toscaGetFunction: ToscaGetFunction = new ToscaGetFunction(undefined); + private isInitialized: boolean = false; private componentMetadata: ComponentMetadata; constructor(private topologyTemplateService: TopologyTemplateService, - private workspaceService: WorkspaceService, - private propertiesService: PropertiesService, - private dataTypeService: DataTypeService, - private translateService: TranslateService) { + private workspaceService: WorkspaceService) { } ngOnInit(): void { this.componentMetadata = this.workspaceService.metadata; + this.toscaFunction = this.property.toscaFunction ? this.property.toscaFunction : undefined; this.loadToscaFunctions(); - this.loadPropertySourceDropdown(); - this.initToscaGetFunction(); - } - - private initToscaGetFunction(): void { - this.toscaGetFunctionForm.valueChanges.subscribe(toscaGetFunction => { - this.onValidityChange.emit(this.toscaGetFunctionForm.valid); - if (this.toscaGetFunctionForm.valid) { - this.onValidFunction.emit(toscaGetFunction); - } - }); - if (!this.property.isToscaGetFunction()) { - return; - } - this.toscaGetFunction = new ToscaGetFunction(this.property.toscaGetFunction); - this.toscaGetFunctionForm.setValue(this.toscaGetFunction); - if (this.isGetPropertySelected() || this.isGetAttributeSelected()) { - if (this.toscaGetFunction.propertySource === PropertySource.SELF) { - this.propertySource = PropertySource.SELF; - } else { - this.propertySource = this.toscaGetFunction.sourceName; - } - } - if (this.toscaGetFunction.propertyName) { - this.loadPropertyDropdown(() => { - this.selectedProperty = this.propertyDropdownList.find(property => property.propertyName === this.toscaGetFunction.propertyName) - }); - } - } - - private loadToscaFunctions(): void { - this.toscaFunctions.push(ToscaGetFunctionType.GET_ATTRIBUTE); - this.toscaFunctions.push(ToscaGetFunctionType.GET_INPUT); - this.toscaFunctions.push(ToscaGetFunctionType.GET_PROPERTY); - } - - private loadPropertySourceDropdown(): void { - this.propertySourceList.push(PropertySource.SELF); - this.componentInstanceMap.forEach((value, key) => { - const instanceName = value.name; - this.instanceNameAndIdMap.set(instanceName, key); - if (instanceName !== PropertySource.SELF) { - this.addToPropertySource(instanceName); - } - }); - } - - private addToPropertySource(source: string): void { - this.propertySourceList.push(source); - this.propertySourceList.sort((a, b) => { - if (a === PropertySource.SELF) { - return -1; - } else if (b === PropertySource.SELF) { - return 1; - } - - return a.localeCompare(b); - }); - } - - onToscaFunctionChange(): void { - this.resetPropertySource(); - this.resetPropertyDropdown(); - if (this.isGetInputSelected()) { - this.setSelfPropertySource(); - this.loadPropertyDropdown(); - } - } - - private loadPropertyDropdown(onComplete?: () => any): void { - this.loadPropertyDropdownLabel(); - this.loadPropertyDropdownValues(onComplete); - } - - private resetForm(): void { - this.toscaGetFunction = new ToscaGetFunction(); - this.toscaGetFunctionForm.setValue(new ToscaGetFunction()); - this.propertySource = undefined; - this.selectedProperty = undefined; - } - - private resetPropertySource(): void { - this.toscaGetFunction.propertyUniqueId = undefined; - this.toscaGetFunction.propertyName = undefined; - this.toscaGetFunction.propertySource = undefined; - this.toscaGetFunction.sourceUniqueId = undefined; - this.toscaGetFunction.sourceName = undefined; - this.toscaGetFunction.propertyPathFromSource = undefined; - this.propertySource = undefined; - this.selectedProperty = undefined; - - const toscaGetFunction1 = new ToscaGetFunction(undefined); - toscaGetFunction1.functionType = this.toscaGetFunction.functionType; - this.toscaGetFunctionForm.setValue(toscaGetFunction1); - } - - private loadPropertyDropdownLabel(): void { - if (!this.toscaGetFunction.functionType) { - return; - } - if (this.isGetInputSelected()) { - this.dropdownValuesLabel = this.translateService.translate('INPUT_DROPDOWN_LABEL'); - } else if (this.isGetPropertySelected()) { - this.dropdownValuesLabel = this.translateService.translate('TOSCA_FUNCTION_PROPERTY_DROPDOWN_LABEL'); - } else if (this.isGetAttributeSelected()) { - this.dropdownValuesLabel = this.translateService.translate('TOSCA_FUNCTION_ATTRIBUTE_DROPDOWN_LABEL'); - } - } - - private loadPropertyDropdownValues(onComplete?: () => any): void { - if (!this.toscaGetFunction.functionType) { - return; - } - this.resetPropertyDropdown(); - this.fillPropertyDropdownValues(onComplete); - } - - private resetPropertyDropdown(): void { - this.dropDownErrorMsg = undefined; - this.selectedProperty = undefined; - this.propertyDropdownList = []; - } - - private fillPropertyDropdownValues(onComplete?: () => any): void { - this.startLoading(); - const propertiesObservable: Observable<ComponentGenericResponse> = this.getPropertyObservable(); - propertiesObservable.subscribe( (response: ComponentGenericResponse) => { - const properties: Array<PropertyBEModel | AttributeModel> = this.extractProperties(response); - if (!properties || properties.length === 0) { - const msgCode = this.getNotFoundMsgCode(); - this.dropDownErrorMsg = this.translateService.translate(msgCode, {type: this.propertyTypeToString()}); + this.formGroup.valueChanges.subscribe(() => { + if (!this.isInitialized) { return; } - this.addPropertiesToDropdown(properties); - if (this.propertyDropdownList.length == 0) { - const msgCode = this.getNotFoundMsgCode(); - this.dropDownErrorMsg = this.translateService.translate(msgCode, {type: this.propertyTypeToString()}); - } - }, (error) => { - console.error('An error occurred while loading properties.', error); - this.stopLoading(); - }, () => { - if (onComplete) { - onComplete(); + this.emitValidityChange(); + if (this.formGroup.valid) { + this.onValidFunction.emit(this.toscaFunctionForm.value); } - this.stopLoading(); }); + this.initToscaGetFunction(); + this.emitValidityChange(); + this.isInitialized = true; } - private getNotFoundMsgCode(): string { - if (this.isGetInputSelected()) { - return 'TOSCA_FUNCTION_NO_INPUT_FOUND'; - } - if (this.isGetAttributeSelected()) { - return 'TOSCA_FUNCTION_NO_ATTRIBUTE_FOUND'; - } - if (this.isGetPropertySelected()) { - return 'TOSCA_FUNCTION_NO_PROPERTY_FOUND'; - } - - return undefined; - } - - private propertyTypeToString() { - if (this.property.schemaType) { - return `${this.property.type} of ${this.property.schemaType}`; - } - return this.property.type; - } - - private extractProperties(componentGenericResponse: ComponentGenericResponse): Array<PropertyBEModel | AttributeModel> { - if (this.isGetInputSelected()) { - return componentGenericResponse.inputs; - } - if (this.isGetPropertySelected()) { - if (this.propertySource === PropertySource.SELF) { - return componentGenericResponse.properties; - } - const componentInstanceProperties: PropertyModel[] = componentGenericResponse.componentInstancesProperties[this.instanceNameAndIdMap.get(this.propertySource)]; - return this.removeSelectedProperty(componentInstanceProperties); - } - if (this.propertySource === PropertySource.SELF) { - return componentGenericResponse.attributes; - } - return componentGenericResponse.componentInstancesAttributes[this.instanceNameAndIdMap.get(this.propertySource)]; - } - - private getPropertyObservable(): Observable<ComponentGenericResponse> { - if (this.isGetInputSelected()) { - return this.topologyTemplateService.getComponentInputsValues(this.componentMetadata.componentType, this.componentMetadata.uniqueId); - } - if (this.isGetPropertySelected()) { - if (this.propertySource === PropertySource.SELF) { - return this.topologyTemplateService.findAllComponentProperties(this.componentMetadata.componentType, this.componentMetadata.uniqueId); - } - return this.topologyTemplateService.getComponentInstanceProperties(this.componentMetadata.componentType, this.componentMetadata.uniqueId); - } - if (this.isGetAttributeSelected()) { - if (this.propertySource === PropertySource.SELF) { - return this.topologyTemplateService.findAllComponentAttributes(this.componentMetadata.componentType, this.componentMetadata.uniqueId); - } - return this.topologyTemplateService.findAllComponentInstanceAttributes(this.componentMetadata.componentType, this.componentMetadata.uniqueId); - } - } - - private removeSelectedProperty(componentInstanceProperties: PropertyModel[]): PropertyModel[] { - if (!componentInstanceProperties) { - return []; - } - return componentInstanceProperties.filter(property => - (property.uniqueId !== this.property.uniqueId) || - (property.uniqueId === this.property.uniqueId && property.resourceInstanceUniqueId !== this.property.parentUniqueId) - ); - } - - private addPropertyToDropdown(propertyDropdownValue: PropertyDropdownValue): void { - this.propertyDropdownList.push(propertyDropdownValue); - this.propertyDropdownList.sort((a, b) => a.propertyLabel.localeCompare(b.propertyLabel)); + private validate() { + return (!this.toscaFunctionForm.value && !this.toscaFunctionTypeForm.value) || this.formGroup.valid; } - private addPropertiesToDropdown(properties: Array<PropertyBEModel | AttributeModel>): void { - for (const property of properties) { - if (this.hasSameType(property)) { - this.addPropertyToDropdown({ - propertyName: property.name, - propertyId: property.uniqueId, - propertyLabel: property.name, - propertyPath: [property.name] - }); - } else if (this.isComplexType(property.type)) { - this.fillPropertyDropdownWithMatchingChildProperties(property); - } - } - } - - private fillPropertyDropdownWithMatchingChildProperties(inputProperty: PropertyBEModel | AttributeModel, - parentPropertyList: Array<PropertyBEModel | AttributeModel> = []): void { - const dataTypeFound: DataTypeModel = this.dataTypeService.getDataTypeByModelAndTypeName(this.componentMetadata.model, inputProperty.type); - if (!dataTypeFound || !dataTypeFound.properties) { + private initToscaGetFunction() { + if (!this.property.isToscaGetFunction()) { return; } - parentPropertyList.push(inputProperty); - dataTypeFound.properties.forEach(dataTypeProperty => { - if (this.hasSameType(dataTypeProperty)) { - this.addPropertyToDropdown({ - propertyName: dataTypeProperty.name, - propertyId: parentPropertyList[0].uniqueId, - propertyLabel: parentPropertyList.map(property => property.name).join('->') + '->' + dataTypeProperty.name, - propertyPath: [...parentPropertyList.map(property => property.name), dataTypeProperty.name] - }); - } else if (this.isComplexType(dataTypeProperty.type)) { - this.fillPropertyDropdownWithMatchingChildProperties(dataTypeProperty, [...parentPropertyList]) - } - }); + this.toscaFunctionForm.setValue(this.property.toscaFunction); + this.toscaFunctionTypeForm.setValue(this.property.toscaFunction.type); } - private hasSameType(property: PropertyBEModel | AttributeModel) { - if (this.typeHasSchema(this.property.type)) { - if (!property.schema || !property.schema.property) { - return false; - } - return property.type === this.property.type && this.property.schema.property.type === property.schema.property.type; + private loadToscaFunctions(): void { + this.toscaFunctions.push(ToscaFunctionType.GET_ATTRIBUTE); + this.toscaFunctions.push(ToscaFunctionType.GET_INPUT); + this.toscaFunctions.push(ToscaFunctionType.GET_PROPERTY); + if (this.property.type === PROPERTY_TYPES.STRING) { + this.toscaFunctions.push(ToscaFunctionType.CONCAT); } + } - return property.type === this.property.type; + private resetForm(): void { + this.formGroup.reset(); + this.toscaFunction = undefined; } private isGetPropertySelected(): boolean { - return this.toscaGetFunction.functionType === ToscaGetFunctionType.GET_PROPERTY; + return this.formGroup.get('toscaFunctionType').value === ToscaGetFunctionType.GET_PROPERTY; } private isGetAttributeSelected(): boolean { - return this.toscaGetFunction.functionType === ToscaGetFunctionType.GET_ATTRIBUTE; + return this.formGroup.get('toscaFunctionType').value === ToscaGetFunctionType.GET_ATTRIBUTE; } private isGetInputSelected(): boolean { - return this.toscaGetFunction.functionType === ToscaGetFunctionType.GET_INPUT; + return this.formGroup.get('toscaFunctionType').value === ToscaGetFunctionType.GET_INPUT; } - private isComplexType(propertyType: string): boolean { - return PROPERTY_DATA.SIMPLE_TYPES.indexOf(propertyType) === -1; + isConcatSelected(): boolean { + return this.formGroup.get('toscaFunctionType').value === ToscaFunctionType.CONCAT; } - private typeHasSchema(propertyType: string): boolean { - return PROPERTY_TYPES.MAP === propertyType || PROPERTY_TYPES.LIST === propertyType; + isGetFunctionSelected(): boolean { + return this.isGetInputSelected() || this.isGetPropertySelected() || this.isGetAttributeSelected(); } - private stopLoading(): void { - this.isLoading = false; + onClearValues() { + this.resetForm(); } - private startLoading(): void { - this.isLoading = true; + showClearButton(): boolean { + return this.allowClear && this.toscaFunctionTypeForm.value; } - showPropertyDropdown(): boolean { - if (this.isGetPropertySelected() || this.isGetAttributeSelected()) { - return this.toscaGetFunction.propertySource && !this.isLoading && !this.dropDownErrorMsg; + onConcatFunctionValidityChange(validationEvent: ToscaConcatFunctionValidationEvent) { + if (validationEvent.isValid) { + this.toscaFunctionForm.setValue(validationEvent.toscaConcatFunction); + } else { + this.toscaFunctionForm.setValue(undefined); } - - return this.toscaGetFunction.functionType && !this.isLoading && !this.dropDownErrorMsg; } - onPropertySourceChange(): void { - if (!this.toscaGetFunction.functionType || !this.propertySource) { - return; - } - this.toscaGetFunction.propertyUniqueId = undefined; - this.toscaGetFunction.propertyName = undefined; - this.toscaGetFunction.propertyPathFromSource = undefined; - if (this.propertySource === PropertySource.SELF) { - this.setSelfPropertySource(); + onGetFunctionValidityChange(validationEvent: ToscaGetFunctionValidationEvent) { + if (validationEvent.isValid) { + this.toscaFunctionForm.setValue(validationEvent.toscaGetFunction); } else { - this.toscaGetFunction.propertySource = PropertySource.INSTANCE; - this.toscaGetFunction.sourceName = this.propertySource; - this.toscaGetFunction.sourceUniqueId = this.instanceNameAndIdMap.get(this.propertySource); + this.toscaFunctionForm.setValue(undefined); } - this.toscaGetFunctionForm.setValue(this.toscaGetFunction); - this.loadPropertyDropdown(); - } - - private setSelfPropertySource(): void { - this.toscaGetFunction.propertySource = PropertySource.SELF; - this.toscaGetFunction.sourceName = this.componentMetadata.name; - this.toscaGetFunction.sourceUniqueId = this.componentMetadata.uniqueId; - this.toscaGetFunctionForm.setValue(this.toscaGetFunction); - } - - onPropertyChange(): void { - this.toscaGetFunction.propertyUniqueId = this.selectedProperty.propertyId; - this.toscaGetFunction.propertyName = this.selectedProperty.propertyName; - this.toscaGetFunction.propertyPathFromSource = this.selectedProperty.propertyPath; - this.toscaGetFunctionForm.setValue(this.toscaGetFunction); - } - - onClearValues() { - this.resetForm(); } - showClearButton(): boolean { - return this.allowClear && this.toscaGetFunction.functionType !== undefined; - } - - showPropertySourceDropdown(): boolean { - return this.isGetPropertySelected() || this.isGetAttributeSelected(); + private emitValidityChange() { + const isValid = this.validate(); + this.onValidityChange.emit({ + isValid: isValid, + toscaFunction: isValid ? this.toscaFunctionForm.value : undefined + }); } } -export interface PropertyDropdownValue { - propertyName: string; - propertyId: string; - propertyLabel: string; - propertyPath: Array<string>; -} +export class ToscaFunctionValidationEvent { + isValid: boolean; + toscaFunction: ToscaFunction; +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-function.module.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-function.module.ts index efe45c3d27..2db76cf544 100644 --- a/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-function.module.ts +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-function.module.ts @@ -19,20 +19,25 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { FormsModule } from '@angular/forms'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import { FormElementsModule } from 'app/ng2/components/ui/form-components/form-elements.module'; import { UiElementsModule } from 'app/ng2/components/ui/ui-elements.module'; import { TranslateModule } from '../../../shared/translator/translate.module'; import { ToscaFunctionComponent } from './tosca-function.component'; import { SdcUiComponentsModule } from 'onap-ui-angular'; +import { ToscaGetFunctionComponent } from './tosca-get-function/tosca-get-function.component'; +import { ToscaConcatFunctionComponent } from './tosca-concat-function/tosca-concat-function.component'; @NgModule({ declarations: [ ToscaFunctionComponent, + ToscaGetFunctionComponent, + ToscaConcatFunctionComponent, ], imports: [ CommonModule, FormsModule, + ReactiveFormsModule, FormElementsModule, UiElementsModule, TranslateModule, diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-get-function/tosca-get-function.component.html b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-get-function/tosca-get-function.component.html new file mode 100644 index 0000000000..6f19d5eff4 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-get-function/tosca-get-function.component.html @@ -0,0 +1,38 @@ +<!-- + ~ ============LICENSE_START======================================================= + ~ Copyright (C) 2021 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========================================================= + --> + +<div class="tosca-function"> + <form class="w-sdc-form" [formGroup]="formGroup"> + <div class="i-sdc-form-item" *ngIf="showPropertySourceDropdown()"> + <label class="i-sdc-form-label required">{{'TOSCA_FUNCTION_PROPERTY_SOURCE_LABEL' | translate}}</label> + <select formControlName="propertySource" (change)="onPropertySourceChange()"> + <option *ngFor="let propertySource of propertySourceList" + [ngValue]="propertySource">{{propertySource}}</option> + </select> + </div> + <div *ngIf="showPropertyDropdown()" class="i-sdc-form-item"> + <label class="i-sdc-form-label required">{{dropdownValuesLabel}}</label> + <select formControlName="selectedProperty"> + <option *ngFor="let value of propertyDropdownList" [ngValue]="value">{{value.propertyLabel}}</option> + </select> + </div> + <div *ngIf="dropDownErrorMsg">{{dropDownErrorMsg}}</div> + </form> + <loader [display]="isLoading" [size]="'medium'" [relative]="true"></loader> +</div> diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-get-function/tosca-get-function.component.less b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-get-function/tosca-get-function.component.less new file mode 100644 index 0000000000..b14edc25e9 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-get-function/tosca-get-function.component.less @@ -0,0 +1,21 @@ +/* + * - + * ============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========================================================= + */ + diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-get-function/tosca-get-function.component.spec.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-get-function/tosca-get-function.component.spec.ts new file mode 100644 index 0000000000..6c7d986150 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-get-function/tosca-get-function.component.spec.ts @@ -0,0 +1,74 @@ +/* + * - + * ============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 {ToscaGetFunctionComponent} from './tosca-get-function.component'; +import {FormsModule, ReactiveFormsModule} from "@angular/forms"; +import {TranslateModule} from "../../../../shared/translator/translate.module"; +import {UiElementsModule} from "../../../../components/ui/ui-elements.module"; +import {TopologyTemplateService} from "../../../../services/component-services/topology-template.service"; +import {WorkspaceService} from "../../../workspace/workspace.service"; +import {PropertiesService} from "../../../../services/properties.service"; +import {DataTypeService} from "../../../../services/data-type.service"; +import {TranslateService} from "../../../../shared/translator/translate.service"; +import {ComponentMetadata} from "../../../../../models/component-metadata"; + +describe('ToscaGetFunctionComponent', () => { + let component: ToscaGetFunctionComponent; + let fixture: ComponentFixture<ToscaGetFunctionComponent>; + let topologyTemplateServiceMock: Partial<TopologyTemplateService>; + let workspaceServiceMock: Partial<WorkspaceService> = { + metadata: new ComponentMetadata() + }; + let propertiesServiceMock: Partial<PropertiesService>; + let dataTypeServiceMock: Partial<DataTypeService>; + let translateServiceMock: Partial<TranslateService>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ToscaGetFunctionComponent], + imports: [ + FormsModule, + ReactiveFormsModule, + TranslateModule, + UiElementsModule + ], + providers: [ + {provide: TopologyTemplateService, useValue: topologyTemplateServiceMock}, + {provide: WorkspaceService, useValue: workspaceServiceMock}, + {provide: PropertiesService, useValue: propertiesServiceMock}, + {provide: DataTypeService, useValue: dataTypeServiceMock}, + {provide: TranslateService, useValue: translateServiceMock} + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ToscaGetFunctionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-get-function/tosca-get-function.component.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-get-function/tosca-get-function.component.ts new file mode 100644 index 0000000000..8f50cc14cd --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/tosca-function/tosca-get-function/tosca-get-function.component.ts @@ -0,0 +1,443 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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, OnChanges, OnInit, Output, SimpleChanges} from '@angular/core'; +import {AttributeModel, ComponentMetadata, DataTypeModel, PropertyBEModel, PropertyModel} from 'app/models'; +import {TopologyTemplateService} from "../../../../services/component-services/topology-template.service"; +import {WorkspaceService} from "../../../workspace/workspace.service"; +import {PropertiesService} from "../../../../services/properties.service"; +import {PROPERTY_DATA, PROPERTY_TYPES} from "../../../../../utils/constants"; +import {DataTypeService} from "../../../../services/data-type.service"; +import {ToscaGetFunctionType} from "../../../../../models/tosca-get-function-type"; +import {TranslateService} from "../../../../shared/translator/translate.service"; +import {ComponentGenericResponse} from '../../../../services/responses/component-generic-response'; +import {Observable} from 'rxjs/Observable'; +import {PropertySource} from "../../../../../models/property-source"; +import {InstanceFeDetails} from "../../../../../models/instance-fe-details"; +import {ToscaGetFunction} from "../../../../../models/tosca-get-function"; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {ToscaGetFunctionTypeConverter} from "../../../../../models/tosca-get-function-type-converter"; + +@Component({ + selector: 'app-tosca-get-function', + templateUrl: './tosca-get-function.component.html', + styleUrls: ['./tosca-get-function.component.less'] +}) +export class ToscaGetFunctionComponent implements OnInit, OnChanges { + + @Input() property: PropertyBEModel; + @Input() toscaGetFunction: ToscaGetFunction; + @Input() componentInstanceMap: Map<string, InstanceFeDetails> = new Map<string, InstanceFeDetails>(); + @Input() functionType: ToscaGetFunctionType; + @Output() onValidFunction: EventEmitter<ToscaGetFunction> = new EventEmitter<ToscaGetFunction>(); + @Output() onValidityChange: EventEmitter<ToscaGetFunctionValidationEvent> = new EventEmitter<ToscaGetFunctionValidationEvent>(); + + formGroup: FormGroup = new FormGroup({ + 'selectedProperty': new FormControl(undefined, Validators.required), + 'propertySource': new FormControl(undefined, Validators.required) + }); + + isLoading: boolean = false; + propertyDropdownList: Array<PropertyDropdownValue> = []; + propertySourceList: Array<string> = []; + instanceNameAndIdMap: Map<string, string> = new Map<string, string>(); + dropdownValuesLabel: string; + dropDownErrorMsg: string; + + private isInitialized: boolean = false; + private componentMetadata: ComponentMetadata; + + constructor(private topologyTemplateService: TopologyTemplateService, + private workspaceService: WorkspaceService, + private propertiesService: PropertiesService, + private dataTypeService: DataTypeService, + private translateService: TranslateService) { + } + + ngOnInit(): void { + this.componentMetadata = this.workspaceService.metadata; + this.formGroup.valueChanges.subscribe(() => { + if (!this.isInitialized) { + return; + } + this.onValidityChange.emit({ + isValid: this.formGroup.valid, + toscaGetFunction: this.formGroup.valid ? this.buildGetFunctionFromForm() : undefined + }); + if (this.formGroup.valid) { + this.onValidFunction.emit(this.buildGetFunctionFromForm()); + } + }); + this.loadPropertySourceDropdown(); + this.loadPropertyDropdownLabel(); + this.initToscaGetFunction().subscribe(() => { + this.isInitialized = true; + }); + + } + + ngOnChanges(_changes: SimpleChanges): void { + if (!this.isInitialized) { + return; + } + this.isInitialized = false; + this.resetForm(); + this.loadPropertySourceDropdown(); + this.loadPropertyDropdownLabel(); + this.initToscaGetFunction().subscribe(() => { + this.isInitialized = true; + }); + } + + private initToscaGetFunction(): Observable<void> { + return new Observable(subscriber => { + if (!this.toscaGetFunction) { + if (this.isGetInput()) { + this.setSelfPropertySource(); + this.loadPropertyDropdown(); + } + subscriber.next(); + return; + } + if (this.toscaGetFunction.propertySource == PropertySource.SELF) { + this.propertySource.setValue(PropertySource.SELF); + } else if (this.toscaGetFunction.propertySource == PropertySource.INSTANCE) { + this.propertySource + .setValue(this.propertySourceList.find(source => this.toscaGetFunction.sourceName === source)); + } + if (this.propertySource.valid) { + this.loadPropertyDropdown(() => { + this.selectedProperty + .setValue(this.propertyDropdownList.find(property => property.propertyName === this.toscaGetFunction.propertyName)); + subscriber.next(); + }); + } else { + subscriber.next(); + } + }); + } + + private buildGetFunctionFromForm() { + const toscaGetFunction = new ToscaGetFunction(); + toscaGetFunction.type = ToscaGetFunctionTypeConverter.convertToToscaFunctionType(this.functionType); + toscaGetFunction.functionType = this.functionType; + const propertySource = this.propertySource.value; + if (this.isPropertySourceSelf()) { + toscaGetFunction.propertySource = propertySource + toscaGetFunction.sourceName = this.componentMetadata.name; + toscaGetFunction.sourceUniqueId = this.componentMetadata.uniqueId; + } else { + toscaGetFunction.propertySource = PropertySource.INSTANCE; + toscaGetFunction.sourceName = propertySource; + toscaGetFunction.sourceUniqueId = this.instanceNameAndIdMap.get(propertySource); + } + + const selectedProperty: PropertyDropdownValue = this.selectedProperty.value; + toscaGetFunction.propertyUniqueId = selectedProperty.propertyId; + toscaGetFunction.propertyName = selectedProperty.propertyName; + toscaGetFunction.propertyPathFromSource = selectedProperty.propertyPath; + + return toscaGetFunction; + } + + private loadPropertySourceDropdown(): void { + if (this.isGetInput()) { + return; + } + this.propertySourceList = []; + this.propertySourceList.push(PropertySource.SELF); + this.componentInstanceMap.forEach((value, key) => { + const instanceName = value.name; + this.instanceNameAndIdMap.set(instanceName, key); + if (instanceName !== PropertySource.SELF) { + this.addToPropertySource(instanceName); + } + }); + } + + private addToPropertySource(source: string): void { + this.propertySourceList.push(source); + this.propertySourceList.sort((a, b) => { + if (a === PropertySource.SELF) { + return -1; + } else if (b === PropertySource.SELF) { + return 1; + } + + return a.localeCompare(b); + }); + } + + private loadPropertyDropdown(onComplete?: () => any): void { + this.loadPropertyDropdownLabel(); + this.loadPropertyDropdownValues(onComplete); + } + + private resetForm(): void { + this.formGroup.reset(); + } + + private loadPropertyDropdownLabel(): void { + if (!this.functionType) { + return; + } + if (this.isGetInput()) { + this.dropdownValuesLabel = this.translateService.translate('INPUT_DROPDOWN_LABEL'); + } else if (this.isGetProperty()) { + this.dropdownValuesLabel = this.translateService.translate('TOSCA_FUNCTION_PROPERTY_DROPDOWN_LABEL'); + } else if (this.isGetAttribute()) { + this.dropdownValuesLabel = this.translateService.translate('TOSCA_FUNCTION_ATTRIBUTE_DROPDOWN_LABEL'); + } + } + + private loadPropertyDropdownValues(onComplete?: () => any): void { + if (!this.functionType) { + return; + } + this.resetPropertyDropdown(); + this.fillPropertyDropdownValues(onComplete); + } + + private resetPropertyDropdown(): void { + this.dropDownErrorMsg = undefined; + this.selectedProperty.reset(); + this.propertyDropdownList = []; + } + + private fillPropertyDropdownValues(onComplete?: () => any): void { + this.startLoading(); + const propertiesObservable: Observable<ComponentGenericResponse> = this.getPropertyObservable(); + propertiesObservable.subscribe( (response: ComponentGenericResponse) => { + const properties: Array<PropertyBEModel | AttributeModel> = this.extractProperties(response); + if (!properties || properties.length === 0) { + const msgCode = this.getNotFoundMsgCode(); + this.dropDownErrorMsg = this.translateService.translate(msgCode, {type: this.propertyTypeToString()}); + return; + } + this.addPropertiesToDropdown(properties); + if (this.propertyDropdownList.length == 0) { + const msgCode = this.getNotFoundMsgCode(); + this.dropDownErrorMsg = this.translateService.translate(msgCode, {type: this.propertyTypeToString()}); + } + }, (error) => { + console.error('An error occurred while loading properties.', error); + this.stopLoading(); + }, () => { + if (onComplete) { + onComplete(); + } + this.stopLoading(); + }); + } + + private getNotFoundMsgCode(): string { + if (this.isGetInput()) { + return 'TOSCA_FUNCTION_NO_INPUT_FOUND'; + } + if (this.isGetAttribute()) { + return 'TOSCA_FUNCTION_NO_ATTRIBUTE_FOUND'; + } + if (this.isGetProperty()) { + return 'TOSCA_FUNCTION_NO_PROPERTY_FOUND'; + } + + return undefined; + } + + private propertyTypeToString() { + if (this.property.schemaType) { + return `${this.property.type} of ${this.property.schemaType}`; + } + return this.property.type; + } + + private extractProperties(componentGenericResponse: ComponentGenericResponse): Array<PropertyBEModel | AttributeModel> { + if (this.isGetInput()) { + return componentGenericResponse.inputs; + } + const propertySource = this.propertySource.value; + if (this.isGetProperty()) { + if (this.isPropertySourceSelf()) { + return componentGenericResponse.properties; + } + const componentInstanceProperties: PropertyModel[] = componentGenericResponse.componentInstancesProperties[this.instanceNameAndIdMap.get(propertySource)]; + return this.removeSelectedProperty(componentInstanceProperties); + } + if (this.isPropertySourceSelf()) { + return componentGenericResponse.attributes; + } + return componentGenericResponse.componentInstancesAttributes[this.instanceNameAndIdMap.get(propertySource)]; + } + + private isPropertySourceSelf() { + return this.propertySource.value === PropertySource.SELF; + } + + private getPropertyObservable(): Observable<ComponentGenericResponse> { + if (this.isGetInput()) { + return this.topologyTemplateService.getComponentInputsValues(this.componentMetadata.componentType, this.componentMetadata.uniqueId); + } + if (this.isGetProperty()) { + if (this.isPropertySourceSelf()) { + return this.topologyTemplateService.findAllComponentProperties(this.componentMetadata.componentType, this.componentMetadata.uniqueId); + } + return this.topologyTemplateService.getComponentInstanceProperties(this.componentMetadata.componentType, this.componentMetadata.uniqueId); + } + if (this.isGetAttribute()) { + if (this.isPropertySourceSelf()) { + return this.topologyTemplateService.findAllComponentAttributes(this.componentMetadata.componentType, this.componentMetadata.uniqueId); + } + return this.topologyTemplateService.findAllComponentInstanceAttributes(this.componentMetadata.componentType, this.componentMetadata.uniqueId); + } + } + + private removeSelectedProperty(componentInstanceProperties: PropertyModel[]): PropertyModel[] { + if (!componentInstanceProperties) { + return []; + } + return componentInstanceProperties.filter(property => + (property.uniqueId !== this.property.uniqueId) || + (property.uniqueId === this.property.uniqueId && property.resourceInstanceUniqueId !== this.property.parentUniqueId) + ); + } + + private addPropertyToDropdown(propertyDropdownValue: PropertyDropdownValue): void { + this.propertyDropdownList.push(propertyDropdownValue); + this.propertyDropdownList.sort((a, b) => a.propertyLabel.localeCompare(b.propertyLabel)); + } + + private addPropertiesToDropdown(properties: Array<PropertyBEModel | AttributeModel>): void { + for (const property of properties) { + if (this.hasSameType(property)) { + this.addPropertyToDropdown({ + propertyName: property.name, + propertyId: property.uniqueId, + propertyLabel: property.name, + propertyPath: [property.name] + }); + } else if (this.isComplexType(property.type)) { + this.fillPropertyDropdownWithMatchingChildProperties(property); + } + } + } + + private fillPropertyDropdownWithMatchingChildProperties(inputProperty: PropertyBEModel | AttributeModel, + parentPropertyList: Array<PropertyBEModel | AttributeModel> = []): void { + const dataTypeFound: DataTypeModel = this.dataTypeService.getDataTypeByModelAndTypeName(this.componentMetadata.model, inputProperty.type); + if (!dataTypeFound || !dataTypeFound.properties) { + return; + } + parentPropertyList.push(inputProperty); + dataTypeFound.properties.forEach(dataTypeProperty => { + if (this.hasSameType(dataTypeProperty)) { + this.addPropertyToDropdown({ + propertyName: dataTypeProperty.name, + propertyId: parentPropertyList[0].uniqueId, + propertyLabel: parentPropertyList.map(property => property.name).join('->') + '->' + dataTypeProperty.name, + propertyPath: [...parentPropertyList.map(property => property.name), dataTypeProperty.name] + }); + } else if (this.isComplexType(dataTypeProperty.type)) { + this.fillPropertyDropdownWithMatchingChildProperties(dataTypeProperty, [...parentPropertyList]) + } + }); + } + + private hasSameType(property: PropertyBEModel | AttributeModel) { + if (this.typeHasSchema(this.property.type)) { + if (!property.schema || !property.schema.property) { + return false; + } + return property.type === this.property.type && this.property.schema.property.type === property.schema.property.type; + } + + return property.type === this.property.type; + } + + private isGetProperty(): boolean { + return this.functionType === ToscaGetFunctionType.GET_PROPERTY; + } + + private isGetAttribute(): boolean { + return this.functionType === ToscaGetFunctionType.GET_ATTRIBUTE; + } + + private isGetInput(): boolean { + return this.functionType === ToscaGetFunctionType.GET_INPUT; + } + + private isComplexType(propertyType: string): boolean { + return PROPERTY_DATA.SIMPLE_TYPES.indexOf(propertyType) === -1; + } + + private typeHasSchema(propertyType: string): boolean { + return PROPERTY_TYPES.MAP === propertyType || PROPERTY_TYPES.LIST === propertyType; + } + + private stopLoading(): void { + this.isLoading = false; + } + + private startLoading(): void { + this.isLoading = true; + } + + showPropertyDropdown(): boolean { + if (this.isGetProperty() || this.isGetAttribute()) { + return this.propertySource.valid && !this.isLoading && !this.dropDownErrorMsg; + } + + return this.functionType && !this.isLoading && !this.dropDownErrorMsg; + } + + onPropertySourceChange(): void { + if (!this.functionType || !this.propertySource.valid) { + return; + } + this.loadPropertyDropdown(); + } + + showPropertySourceDropdown(): boolean { + return this.isGetProperty() || this.isGetAttribute(); + } + + private setSelfPropertySource(): void { + this.propertySource.setValue(PropertySource.SELF); + } + + private get propertySource(): FormControl { + return this.formGroup.get('propertySource') as FormControl; + } + + private get selectedProperty(): FormControl { + return this.formGroup.get('selectedProperty') as FormControl; + } + +} + +export interface PropertyDropdownValue { + propertyName: string; + propertyId: string; + propertyLabel: string; + propertyPath: Array<string>; +} + +export interface ToscaGetFunctionValidationEvent { + isValid: boolean, + toscaGetFunction: ToscaGetFunction, +}
\ No newline at end of file |