2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021 Nordix Foundation
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 * SPDX-License-Identifier: Apache-2.0
17 * ============LICENSE_END=========================================================
20 import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
21 import {ComponentMetadata, DataTypeModel, PropertyBEModel, PropertyModel} from 'app/models';
22 import {TopologyTemplateService} from "../../../services/component-services/topology-template.service";
23 import {WorkspaceService} from "../../workspace/workspace.service";
24 import {PropertiesService} from "../../../services/properties.service";
25 import {PROPERTY_DATA} from "../../../../utils/constants";
26 import {DataTypeService} from "../../../services/data-type.service";
27 import {ToscaGetFunctionType} from "../../../../models/tosca-get-function-type";
28 import {TranslateService} from "../../../shared/translator/translate.service";
29 import {ComponentGenericResponse} from '../../../services/responses/component-generic-response';
30 import {Observable} from 'rxjs/Observable';
31 import {PropertySource} from "../../../../models/property-source";
32 import {InstanceFeDetails} from "../../../../models/instance-fe-details";
33 import {ToscaGetFunction} from "../../../../models/tosca-get-function";
34 import {AbstractControl, FormControl, FormGroup, ValidationErrors, ValidatorFn} from "@angular/forms";
37 selector: 'tosca-function',
38 templateUrl: './tosca-function.component.html',
39 styleUrls: ['./tosca-function.component.less'],
41 export class ToscaFunctionComponent implements OnInit {
43 @Input() property: PropertyBEModel;
44 @Input() componentInstanceMap: Map<string, InstanceFeDetails> = new Map<string, InstanceFeDetails>();
45 @Input() allowClear: boolean = true;
46 @Output() onValidFunction: EventEmitter<ToscaGetFunction> = new EventEmitter<ToscaGetFunction>();
47 @Output() onValidityChange: EventEmitter<boolean> = new EventEmitter<boolean>();
49 toscaGetFunctionValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
50 const toscaGetFunction: ToscaGetFunction = control.value;
51 const errors: ValidationErrors = {};
52 if (!toscaGetFunction.sourceName) {
53 errors.sourceName = { required: true };
55 if (!toscaGetFunction.functionType) {
56 errors.functionType = { required: true };
58 if (!toscaGetFunction.sourceUniqueId) {
59 errors.sourceUniqueId = { required: true };
61 if (!toscaGetFunction.sourceName) {
62 errors.sourceName = { required: true };
64 if (!toscaGetFunction.propertyPathFromSource) {
65 errors.propertyPathFromSource = { required: true };
67 if (!toscaGetFunction.propertyName) {
68 errors.propertyName = { required: true };
70 if (!toscaGetFunction.propertySource) {
71 errors.propertySource = { required: true };
73 return errors ? errors : null;
76 toscaGetFunctionForm: FormControl = new FormControl(new ToscaGetFunction(undefined), [this.toscaGetFunctionValidator]);
77 formGroup: FormGroup = new FormGroup({
78 'toscaGetFunction': this.toscaGetFunctionForm
81 TOSCA_FUNCTION_GET_PROPERTY = ToscaGetFunctionType.GET_PROPERTY;
83 selectedProperty: PropertyDropdownValue;
84 isLoading: boolean = false;
85 propertyDropdownList: Array<PropertyDropdownValue> = [];
86 toscaFunctions: Array<string> = [];
87 propertySourceList: Array<string> = [];
88 instanceNameAndIdMap: Map<string, string> = new Map<string, string>();
89 dropdownValuesLabel: string;
90 dropDownErrorMsg: string;
91 propertySource: string
92 toscaGetFunction: ToscaGetFunction = new ToscaGetFunction(undefined);
94 private componentMetadata: ComponentMetadata;
96 constructor(private topologyTemplateService: TopologyTemplateService,
97 private workspaceService: WorkspaceService,
98 private propertiesService: PropertiesService,
99 private dataTypeService: DataTypeService,
100 private translateService: TranslateService) {
104 this.componentMetadata = this.workspaceService.metadata;
105 this.loadToscaFunctions();
106 this.loadPropertySourceDropdown();
107 this.initToscaGetFunction();
108 this.toscaGetFunctionForm.valueChanges.subscribe(toscaGetFunction => {
109 this.onValidityChange.emit(this.toscaGetFunctionForm.valid);
110 if (this.toscaGetFunctionForm.valid) {
111 this.onValidFunction.emit(toscaGetFunction);
116 private initToscaGetFunction(): void {
117 if (!this.property.isToscaGetFunction()) {
120 this.toscaGetFunction = new ToscaGetFunction(this.property.toscaGetFunction);
121 this.toscaGetFunctionForm.setValue(this.toscaGetFunction);
122 if (this.toscaGetFunction.functionType === ToscaGetFunctionType.GET_PROPERTY) {
123 if (this.toscaGetFunction.propertySource === PropertySource.SELF) {
124 this.propertySource = PropertySource.SELF;
126 this.propertySource = this.toscaGetFunction.sourceName;
129 if (this.toscaGetFunction.propertyName) {
130 this.loadPropertyDropdown(() => {
131 this.selectedProperty = this.propertyDropdownList.find(property => property.propertyName === this.toscaGetFunction.propertyName)
136 private loadToscaFunctions(): void {
137 this.toscaFunctions.push(ToscaGetFunctionType.GET_INPUT);
138 this.toscaFunctions.push(ToscaGetFunctionType.GET_PROPERTY);
141 private loadPropertySourceDropdown(): void {
142 this.propertySourceList.push(PropertySource.SELF);
143 this.componentInstanceMap.forEach((value, key) => {
144 const instanceName = value.name;
145 this.instanceNameAndIdMap.set(instanceName, key);
146 if (instanceName !== PropertySource.SELF) {
147 this.addToPropertySource(instanceName);
152 private addToPropertySource(source: string): void {
153 this.propertySourceList.push(source);
154 this.propertySourceList.sort((a, b) => {
155 if (a === PropertySource.SELF) {
157 } else if (b === PropertySource.SELF) {
161 return a.localeCompare(b);
165 onToscaFunctionChange(): void {
166 this.resetPropertySource();
167 this.resetPropertyDropdown();
168 if (this.isGetInputSelected()) {
169 this.setSelfPropertySource();
170 this.loadPropertyDropdown();
174 private loadPropertyDropdown(onComplete?: () => any): void {
175 this.loadPropertyDropdownLabel();
176 this.loadPropertyDropdownValues(onComplete);
179 private resetForm(): void {
180 this.toscaGetFunction = new ToscaGetFunction(undefined);
181 this.toscaGetFunctionForm.setValue(new ToscaGetFunction(undefined));
182 this.propertySource = undefined;
183 this.selectedProperty = undefined;
186 private resetPropertySource(): void {
187 this.toscaGetFunction.propertyUniqueId = undefined;
188 this.toscaGetFunction.propertyName = undefined;
189 this.toscaGetFunction.propertySource = undefined;
190 this.toscaGetFunction.sourceUniqueId = undefined;
191 this.toscaGetFunction.sourceName = undefined;
192 this.toscaGetFunction.propertyPathFromSource = undefined;
193 this.propertySource = undefined;
194 this.selectedProperty = undefined;
196 const toscaGetFunction1 = new ToscaGetFunction(undefined);
197 toscaGetFunction1.functionType = this.toscaGetFunction.functionType;
198 this.toscaGetFunctionForm.setValue(toscaGetFunction1);
201 private loadPropertyDropdownLabel(): void {
202 if (!this.toscaGetFunction.functionType) {
205 if (this.isGetInputSelected()) {
206 this.dropdownValuesLabel = this.translateService.translate('INPUT_DROPDOWN_LABEL');
207 } else if (this.isGetPropertySelected()) {
208 this.dropdownValuesLabel = this.translateService.translate('TOSCA_FUNCTION_PROPERTY_DROPDOWN_LABEL');
212 private loadPropertyDropdownValues(onComplete?: () => any): void {
213 if (!this.toscaGetFunction.functionType) {
216 this.resetPropertyDropdown();
217 this.fillPropertyDropdownValues(onComplete);
220 private resetPropertyDropdown(): void {
221 this.dropDownErrorMsg = undefined;
222 this.selectedProperty = undefined;
223 this.propertyDropdownList = [];
226 private fillPropertyDropdownValues(onComplete?: () => any): void {
228 const propertiesObservable: Observable<ComponentGenericResponse> = this.getPropertyObservable();
229 propertiesObservable.subscribe( (response: ComponentGenericResponse) => {
230 const properties: PropertyBEModel[] = this.extractProperties(response);
231 if (!properties || properties.length === 0) {
232 const msgCode = this.isGetInputSelected() ? 'TOSCA_FUNCTION_NO_INPUT_FOUND' : 'TOSCA_FUNCTION_NO_PROPERTY_FOUND';
233 this.dropDownErrorMsg = this.translateService.translate(msgCode, {type: this.property.type});
236 this.addPropertiesToDropdown(properties);
237 if (this.propertyDropdownList.length == 0) {
238 const msgCode = this.isGetInputSelected() ? 'TOSCA_FUNCTION_NO_INPUT_FOUND' : 'TOSCA_FUNCTION_NO_PROPERTY_FOUND';
239 this.dropDownErrorMsg = this.translateService.translate(msgCode, {type: this.property.type});
242 console.error('An error occurred while loading properties.', error);
251 private extractProperties(componentGenericResponse: ComponentGenericResponse): PropertyBEModel[] {
252 if (this.isGetInputSelected()) {
253 return componentGenericResponse.inputs;
255 if (this.isGetPropertySelected()) {
256 if (this.propertySource === PropertySource.SELF) {
257 return componentGenericResponse.properties;
259 const componentInstanceProperties: PropertyModel[] = componentGenericResponse.componentInstancesProperties[this.instanceNameAndIdMap.get(this.propertySource)];
260 return this.removeSelectedProperty(componentInstanceProperties);
264 private getPropertyObservable(): Observable<ComponentGenericResponse> {
265 if (this.isGetInputSelected()) {
266 return this.topologyTemplateService.getComponentInputsValues(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
268 if (this.isGetPropertySelected()) {
269 if (this.propertySource === PropertySource.SELF) {
270 return this.topologyTemplateService.findAllComponentProperties(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
272 return this.topologyTemplateService.getComponentInstanceProperties(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
276 private removeSelectedProperty(componentInstanceProperties: PropertyModel[]): PropertyModel[] {
277 if (!componentInstanceProperties) {
280 return componentInstanceProperties.filter(property =>
281 (property.uniqueId !== this.property.uniqueId) ||
282 (property.uniqueId === this.property.uniqueId && property.resourceInstanceUniqueId !== this.property.parentUniqueId)
286 private addPropertyToDropdown(propertyDropdownValue: PropertyDropdownValue): void {
287 this.propertyDropdownList.push(propertyDropdownValue);
288 this.propertyDropdownList.sort((a, b) => a.propertyLabel.localeCompare(b.propertyLabel));
291 private addPropertiesToDropdown(properties: PropertyBEModel[]): void {
292 for (const property of properties) {
293 if (this.property.type === property.type) {
294 this.addPropertyToDropdown({
295 propertyName: property.name,
296 propertyId: property.uniqueId,
297 propertyLabel: property.name,
298 propertyPath: [property.name]
300 } else if (this.isComplexType(property.type)) {
301 this.fillPropertyDropdownWithMatchingChildProperties(property);
306 private fillPropertyDropdownWithMatchingChildProperties(inputProperty: PropertyBEModel, parentPropertyList: Array<PropertyBEModel> = []): void {
307 const dataTypeFound: DataTypeModel = this.dataTypeService.getDataTypeByModelAndTypeName(this.componentMetadata.model, inputProperty.type);
308 if (!dataTypeFound || !dataTypeFound.properties) {
311 parentPropertyList.push(inputProperty);
312 dataTypeFound.properties.forEach(dataTypeProperty => {
313 if (dataTypeProperty.type === this.property.type) {
314 this.addPropertyToDropdown({
315 propertyName: dataTypeProperty.name,
316 propertyId: parentPropertyList[0].uniqueId,
317 propertyLabel: parentPropertyList.map(property => property.name).join('->') + '->' + dataTypeProperty.name,
318 propertyPath: [...parentPropertyList.map(property => property.name), dataTypeProperty.name]
320 } else if (PROPERTY_DATA.SIMPLE_TYPES.indexOf(dataTypeProperty.type) === -1) {
321 this.fillPropertyDropdownWithMatchingChildProperties(dataTypeProperty, [...parentPropertyList])
326 private isGetPropertySelected(): boolean {
327 return this.toscaGetFunction.functionType === ToscaGetFunctionType.GET_PROPERTY;
330 private isGetInputSelected(): boolean {
331 return this.toscaGetFunction.functionType === ToscaGetFunctionType.GET_INPUT;
334 private isComplexType(propertyType: string): boolean {
335 return PROPERTY_DATA.SIMPLE_TYPES.indexOf(propertyType) === -1;
338 private stopLoading(): void {
339 this.isLoading = false;
342 private startLoading(): void {
343 this.isLoading = true;
346 showDropdown(): boolean {
347 if (this.toscaGetFunction.functionType === ToscaGetFunctionType.GET_PROPERTY) {
348 return this.toscaGetFunction.propertySource && !this.isLoading && !this.dropDownErrorMsg;
351 return this.toscaGetFunction.functionType && !this.isLoading && !this.dropDownErrorMsg;
354 onPropertySourceChange(): void {
355 if (!this.toscaGetFunction.functionType || !this.propertySource) {
358 this.toscaGetFunction.propertyUniqueId = undefined;
359 this.toscaGetFunction.propertyName = undefined;
360 this.toscaGetFunction.propertyPathFromSource = undefined;
361 if (this.propertySource === PropertySource.SELF) {
362 this.setSelfPropertySource();
364 this.toscaGetFunction.propertySource = PropertySource.INSTANCE;
365 this.toscaGetFunction.sourceName = this.propertySource;
366 this.toscaGetFunction.sourceUniqueId = this.instanceNameAndIdMap.get(this.propertySource);
368 this.toscaGetFunctionForm.setValue(this.toscaGetFunction);
369 this.loadPropertyDropdown();
372 private setSelfPropertySource(): void {
373 this.toscaGetFunction.propertySource = PropertySource.SELF;
374 this.toscaGetFunction.sourceName = this.componentMetadata.name;
375 this.toscaGetFunction.sourceUniqueId = this.componentMetadata.uniqueId;
376 this.toscaGetFunctionForm.setValue(this.toscaGetFunction);
379 onPropertyChange(): void {
380 this.toscaGetFunction.propertyUniqueId = this.selectedProperty.propertyId;
381 this.toscaGetFunction.propertyName = this.selectedProperty.propertyName;
382 this.toscaGetFunction.propertyPathFromSource = this.selectedProperty.propertyPath;
383 this.toscaGetFunctionForm.setValue(this.toscaGetFunction);
390 showClearButton(): boolean {
391 return this.allowClear && this.toscaGetFunction.functionType !== undefined;
395 export interface PropertyDropdownValue {
396 propertyName: string;
398 propertyLabel: string;
399 propertyPath: Array<string>;