Support TOSCA get_attribute function
[sdc.git] / catalog-ui / src / app / ng2 / pages / properties-assignment / tosca-function / tosca-function.component.ts
1 /*
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
8  *
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.
15  *
16  *  SPDX-License-Identifier: Apache-2.0
17  *  ============LICENSE_END=========================================================
18  */
19
20 import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
21 import {AttributeModel, 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, PROPERTY_TYPES} 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";
35
36 @Component({
37     selector: 'tosca-function',
38     templateUrl: './tosca-function.component.html',
39     styleUrls: ['./tosca-function.component.less'],
40 })
41 export class ToscaFunctionComponent implements OnInit {
42
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>();
48
49     toscaGetFunctionValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
50         const toscaGetFunction: ToscaGetFunction = control.value;
51         const hasAnyValue = Object.keys(toscaGetFunction).find(key => toscaGetFunction[key]);
52         if (!hasAnyValue) {
53             return null;
54         }
55         const errors: ValidationErrors = {};
56         if (!toscaGetFunction.sourceName) {
57             errors.sourceName = { required: true };
58         }
59         if (!toscaGetFunction.functionType) {
60             errors.functionType = { required: true };
61         }
62         if (!toscaGetFunction.sourceUniqueId) {
63             errors.sourceUniqueId = { required: true };
64         }
65         if (!toscaGetFunction.sourceName) {
66             errors.sourceName = { required: true };
67         }
68         if (!toscaGetFunction.propertyPathFromSource) {
69             errors.propertyPathFromSource = { required: true };
70         }
71         if (!toscaGetFunction.propertyName) {
72             errors.propertyName = { required: true };
73         }
74         if (!toscaGetFunction.propertySource) {
75             errors.propertySource = { required: true };
76         }
77         return errors ? errors : null;
78     };
79
80     toscaGetFunctionForm: FormControl = new FormControl(new ToscaGetFunction(undefined), [this.toscaGetFunctionValidator]);
81     formGroup: FormGroup = new FormGroup({
82         'toscaGetFunction': this.toscaGetFunctionForm
83     });
84
85     selectedProperty: PropertyDropdownValue;
86     isLoading: boolean = false;
87     propertyDropdownList: Array<PropertyDropdownValue> = [];
88     toscaFunctions: Array<string> = [];
89     propertySourceList: Array<string> = [];
90     instanceNameAndIdMap: Map<string, string> = new Map<string, string>();
91     dropdownValuesLabel: string;
92     dropDownErrorMsg: string;
93     propertySource: string
94     toscaGetFunction: ToscaGetFunction = new ToscaGetFunction(undefined);
95
96     private componentMetadata: ComponentMetadata;
97
98     constructor(private topologyTemplateService: TopologyTemplateService,
99                 private workspaceService: WorkspaceService,
100                 private propertiesService: PropertiesService,
101                 private dataTypeService: DataTypeService,
102                 private translateService: TranslateService) {
103     }
104
105     ngOnInit(): void {
106         this.componentMetadata = this.workspaceService.metadata;
107         this.loadToscaFunctions();
108         this.loadPropertySourceDropdown();
109         this.initToscaGetFunction();
110     }
111
112     private initToscaGetFunction(): void {
113         this.toscaGetFunctionForm.valueChanges.subscribe(toscaGetFunction => {
114             this.onValidityChange.emit(this.toscaGetFunctionForm.valid);
115             if (this.toscaGetFunctionForm.valid) {
116                 this.onValidFunction.emit(toscaGetFunction);
117             }
118         });
119         if (!this.property.isToscaGetFunction()) {
120             return;
121         }
122         this.toscaGetFunction = new ToscaGetFunction(this.property.toscaGetFunction);
123         this.toscaGetFunctionForm.setValue(this.toscaGetFunction);
124         if (this.isGetPropertySelected() || this.isGetAttributeSelected()) {
125             if (this.toscaGetFunction.propertySource === PropertySource.SELF) {
126                 this.propertySource = PropertySource.SELF;
127             } else {
128                 this.propertySource = this.toscaGetFunction.sourceName;
129             }
130         }
131         if (this.toscaGetFunction.propertyName) {
132             this.loadPropertyDropdown(() => {
133                 this.selectedProperty = this.propertyDropdownList.find(property => property.propertyName === this.toscaGetFunction.propertyName)
134             });
135         }
136     }
137
138     private loadToscaFunctions(): void {
139         this.toscaFunctions.push(ToscaGetFunctionType.GET_ATTRIBUTE);
140         this.toscaFunctions.push(ToscaGetFunctionType.GET_INPUT);
141         this.toscaFunctions.push(ToscaGetFunctionType.GET_PROPERTY);
142     }
143
144     private loadPropertySourceDropdown(): void {
145         this.propertySourceList.push(PropertySource.SELF);
146         this.componentInstanceMap.forEach((value, key) => {
147             const instanceName = value.name;
148             this.instanceNameAndIdMap.set(instanceName, key);
149             if (instanceName !== PropertySource.SELF) {
150                 this.addToPropertySource(instanceName);
151             }
152         });
153     }
154
155     private addToPropertySource(source: string): void {
156         this.propertySourceList.push(source);
157         this.propertySourceList.sort((a, b) => {
158             if (a === PropertySource.SELF) {
159                 return -1;
160             } else if (b === PropertySource.SELF) {
161                 return 1;
162             }
163
164             return a.localeCompare(b);
165         });
166     }
167
168     onToscaFunctionChange(): void {
169         this.resetPropertySource();
170         this.resetPropertyDropdown();
171         if (this.isGetInputSelected()) {
172             this.setSelfPropertySource();
173             this.loadPropertyDropdown();
174         }
175     }
176
177     private loadPropertyDropdown(onComplete?: () => any): void  {
178         this.loadPropertyDropdownLabel();
179         this.loadPropertyDropdownValues(onComplete);
180     }
181
182     private resetForm(): void {
183         this.toscaGetFunction = new ToscaGetFunction();
184         this.toscaGetFunctionForm.setValue(new ToscaGetFunction());
185         this.propertySource = undefined;
186         this.selectedProperty = undefined;
187     }
188
189     private resetPropertySource(): void {
190         this.toscaGetFunction.propertyUniqueId = undefined;
191         this.toscaGetFunction.propertyName = undefined;
192         this.toscaGetFunction.propertySource = undefined;
193         this.toscaGetFunction.sourceUniqueId = undefined;
194         this.toscaGetFunction.sourceName = undefined;
195         this.toscaGetFunction.propertyPathFromSource = undefined;
196         this.propertySource = undefined;
197         this.selectedProperty = undefined;
198
199         const toscaGetFunction1 = new ToscaGetFunction(undefined);
200         toscaGetFunction1.functionType = this.toscaGetFunction.functionType;
201         this.toscaGetFunctionForm.setValue(toscaGetFunction1);
202     }
203
204     private loadPropertyDropdownLabel(): void {
205         if (!this.toscaGetFunction.functionType) {
206             return;
207         }
208         if (this.isGetInputSelected()) {
209             this.dropdownValuesLabel = this.translateService.translate('INPUT_DROPDOWN_LABEL');
210         } else if (this.isGetPropertySelected()) {
211             this.dropdownValuesLabel = this.translateService.translate('TOSCA_FUNCTION_PROPERTY_DROPDOWN_LABEL');
212         } else if (this.isGetAttributeSelected()) {
213             this.dropdownValuesLabel = this.translateService.translate('TOSCA_FUNCTION_ATTRIBUTE_DROPDOWN_LABEL');
214         }
215     }
216
217     private loadPropertyDropdownValues(onComplete?: () => any): void {
218         if (!this.toscaGetFunction.functionType) {
219             return;
220         }
221         this.resetPropertyDropdown();
222         this.fillPropertyDropdownValues(onComplete);
223     }
224
225     private resetPropertyDropdown(): void {
226         this.dropDownErrorMsg = undefined;
227         this.selectedProperty = undefined;
228         this.propertyDropdownList = [];
229     }
230
231     private fillPropertyDropdownValues(onComplete?: () => any): void {
232         this.startLoading();
233         const propertiesObservable: Observable<ComponentGenericResponse> = this.getPropertyObservable();
234         propertiesObservable.subscribe( (response: ComponentGenericResponse) => {
235             const properties: Array<PropertyBEModel | AttributeModel> = this.extractProperties(response);
236             if (!properties || properties.length === 0) {
237                 const msgCode = this.getNotFoundMsgCode();
238                 this.dropDownErrorMsg = this.translateService.translate(msgCode, {type: this.propertyTypeToString()});
239                 return;
240             }
241             this.addPropertiesToDropdown(properties);
242             if (this.propertyDropdownList.length == 0) {
243                 const msgCode = this.getNotFoundMsgCode();
244                 this.dropDownErrorMsg = this.translateService.translate(msgCode, {type: this.propertyTypeToString()});
245             }
246         }, (error) => {
247             console.error('An error occurred while loading properties.', error);
248             this.stopLoading();
249         }, () => {
250             if (onComplete) {
251                 onComplete();
252             }
253             this.stopLoading();
254         });
255     }
256
257     private getNotFoundMsgCode(): string {
258         if (this.isGetInputSelected()) {
259             return 'TOSCA_FUNCTION_NO_INPUT_FOUND';
260         }
261         if (this.isGetAttributeSelected()) {
262             return 'TOSCA_FUNCTION_NO_ATTRIBUTE_FOUND';
263         }
264         if (this.isGetPropertySelected()) {
265             return 'TOSCA_FUNCTION_NO_PROPERTY_FOUND';
266         }
267
268         return undefined;
269     }
270
271     private propertyTypeToString() {
272         if (this.property.schemaType) {
273             return `${this.property.type} of ${this.property.schemaType}`;
274         }
275         return this.property.type;
276     }
277
278     private extractProperties(componentGenericResponse: ComponentGenericResponse): Array<PropertyBEModel | AttributeModel> {
279         if (this.isGetInputSelected()) {
280             return componentGenericResponse.inputs;
281         }
282         if (this.isGetPropertySelected()) {
283             if (this.propertySource === PropertySource.SELF) {
284                 return componentGenericResponse.properties;
285             }
286             const componentInstanceProperties: PropertyModel[] = componentGenericResponse.componentInstancesProperties[this.instanceNameAndIdMap.get(this.propertySource)];
287             return this.removeSelectedProperty(componentInstanceProperties);
288         }
289         if (this.propertySource === PropertySource.SELF) {
290             return componentGenericResponse.attributes;
291         }
292         return componentGenericResponse.componentInstancesAttributes[this.instanceNameAndIdMap.get(this.propertySource)];
293     }
294
295     private getPropertyObservable(): Observable<ComponentGenericResponse> {
296         if (this.isGetInputSelected()) {
297             return this.topologyTemplateService.getComponentInputsValues(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
298         }
299         if (this.isGetPropertySelected()) {
300             if (this.propertySource === PropertySource.SELF) {
301                 return this.topologyTemplateService.findAllComponentProperties(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
302             }
303             return this.topologyTemplateService.getComponentInstanceProperties(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
304         }
305         if (this.isGetAttributeSelected()) {
306             if (this.propertySource === PropertySource.SELF) {
307                 return this.topologyTemplateService.findAllComponentAttributes(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
308             }
309             return this.topologyTemplateService.findAllComponentInstanceAttributes(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
310         }
311     }
312
313     private removeSelectedProperty(componentInstanceProperties: PropertyModel[]): PropertyModel[] {
314         if (!componentInstanceProperties) {
315             return [];
316         }
317         return componentInstanceProperties.filter(property =>
318             (property.uniqueId !== this.property.uniqueId) ||
319             (property.uniqueId === this.property.uniqueId && property.resourceInstanceUniqueId !== this.property.parentUniqueId)
320         );
321     }
322
323     private addPropertyToDropdown(propertyDropdownValue: PropertyDropdownValue): void {
324         this.propertyDropdownList.push(propertyDropdownValue);
325         this.propertyDropdownList.sort((a, b) => a.propertyLabel.localeCompare(b.propertyLabel));
326     }
327
328     private addPropertiesToDropdown(properties: Array<PropertyBEModel | AttributeModel>): void {
329         for (const property of properties) {
330             if (this.hasSameType(property)) {
331                 this.addPropertyToDropdown({
332                     propertyName: property.name,
333                     propertyId: property.uniqueId,
334                     propertyLabel: property.name,
335                     propertyPath: [property.name]
336                 });
337             } else if (this.isComplexType(property.type)) {
338                 this.fillPropertyDropdownWithMatchingChildProperties(property);
339             }
340         }
341     }
342
343     private fillPropertyDropdownWithMatchingChildProperties(inputProperty: PropertyBEModel | AttributeModel,
344                                                             parentPropertyList: Array<PropertyBEModel | AttributeModel> = []): void {
345         const dataTypeFound: DataTypeModel = this.dataTypeService.getDataTypeByModelAndTypeName(this.componentMetadata.model, inputProperty.type);
346         if (!dataTypeFound || !dataTypeFound.properties) {
347             return;
348         }
349         parentPropertyList.push(inputProperty);
350         dataTypeFound.properties.forEach(dataTypeProperty => {
351             if (this.hasSameType(dataTypeProperty)) {
352                 this.addPropertyToDropdown({
353                     propertyName: dataTypeProperty.name,
354                     propertyId: parentPropertyList[0].uniqueId,
355                     propertyLabel: parentPropertyList.map(property => property.name).join('->') + '->' + dataTypeProperty.name,
356                     propertyPath: [...parentPropertyList.map(property => property.name), dataTypeProperty.name]
357                 });
358             } else if (this.isComplexType(dataTypeProperty.type)) {
359                 this.fillPropertyDropdownWithMatchingChildProperties(dataTypeProperty, [...parentPropertyList])
360             }
361         });
362     }
363
364     private hasSameType(property: PropertyBEModel | AttributeModel) {
365         if (this.typeHasSchema(this.property.type)) {
366             if (!property.schema || !property.schema.property) {
367                 return false;
368             }
369             return property.type === this.property.type && this.property.schema.property.type === property.schema.property.type;
370         }
371
372         return property.type === this.property.type;
373     }
374
375     private isGetPropertySelected(): boolean {
376         return this.toscaGetFunction.functionType === ToscaGetFunctionType.GET_PROPERTY;
377     }
378
379     private isGetAttributeSelected(): boolean {
380         return this.toscaGetFunction.functionType === ToscaGetFunctionType.GET_ATTRIBUTE;
381     }
382
383     private isGetInputSelected(): boolean {
384         return this.toscaGetFunction.functionType === ToscaGetFunctionType.GET_INPUT;
385     }
386
387     private isComplexType(propertyType: string): boolean {
388         return PROPERTY_DATA.SIMPLE_TYPES.indexOf(propertyType) === -1;
389     }
390
391     private typeHasSchema(propertyType: string): boolean {
392         return PROPERTY_TYPES.MAP === propertyType || PROPERTY_TYPES.LIST === propertyType;
393     }
394
395     private stopLoading(): void {
396         this.isLoading = false;
397     }
398
399     private startLoading(): void {
400         this.isLoading = true;
401     }
402
403     showPropertyDropdown(): boolean {
404         if (this.isGetPropertySelected() || this.isGetAttributeSelected()) {
405             return this.toscaGetFunction.propertySource && !this.isLoading && !this.dropDownErrorMsg;
406         }
407
408         return this.toscaGetFunction.functionType && !this.isLoading && !this.dropDownErrorMsg;
409     }
410
411     onPropertySourceChange(): void {
412         if (!this.toscaGetFunction.functionType || !this.propertySource) {
413             return;
414         }
415         this.toscaGetFunction.propertyUniqueId = undefined;
416         this.toscaGetFunction.propertyName = undefined;
417         this.toscaGetFunction.propertyPathFromSource = undefined;
418         if (this.propertySource === PropertySource.SELF) {
419             this.setSelfPropertySource();
420         } else {
421             this.toscaGetFunction.propertySource = PropertySource.INSTANCE;
422             this.toscaGetFunction.sourceName = this.propertySource;
423             this.toscaGetFunction.sourceUniqueId = this.instanceNameAndIdMap.get(this.propertySource);
424         }
425         this.toscaGetFunctionForm.setValue(this.toscaGetFunction);
426         this.loadPropertyDropdown();
427     }
428
429     private setSelfPropertySource(): void {
430         this.toscaGetFunction.propertySource = PropertySource.SELF;
431         this.toscaGetFunction.sourceName = this.componentMetadata.name;
432         this.toscaGetFunction.sourceUniqueId = this.componentMetadata.uniqueId;
433         this.toscaGetFunctionForm.setValue(this.toscaGetFunction);
434     }
435
436     onPropertyChange(): void {
437         this.toscaGetFunction.propertyUniqueId = this.selectedProperty.propertyId;
438         this.toscaGetFunction.propertyName = this.selectedProperty.propertyName;
439         this.toscaGetFunction.propertyPathFromSource = this.selectedProperty.propertyPath;
440         this.toscaGetFunctionForm.setValue(this.toscaGetFunction);
441     }
442
443     onClearValues() {
444         this.resetForm();
445     }
446
447     showClearButton(): boolean {
448         return this.allowClear && this.toscaGetFunction.functionType !== undefined;
449     }
450
451     showPropertySourceDropdown(): boolean {
452         return this.isGetPropertySelected() || this.isGetAttributeSelected();
453     }
454 }
455
456 export interface PropertyDropdownValue {
457     propertyName: string;
458     propertyId: string;
459     propertyLabel: string;
460     propertyPath: Array<string>;
461 }