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