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