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/tosca-function/tosca-get-function | |
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/tosca-function/tosca-get-function')
4 files changed, 576 insertions, 0 deletions
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 |