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