Allow to edit or clear a TOSCA function value
[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, Input, OnInit} from '@angular/core';
21 import {ComponentMetadata, DataTypeModel, PropertyBEModel, PropertyModel} from 'app/models';
22 import {TopologyTemplateService} from "../../../services/component-services/topology-template.service";
23 import {WorkspaceService} from "../../workspace/workspace.service";
24 import {PropertiesService} from "../../../services/properties.service";
25 import {PROPERTY_DATA} from "../../../../utils/constants";
26 import {DataTypeService} from "../../../services/data-type.service";
27 import {ToscaGetFunctionType} from "../../../../models/tosca-get-function-type";
28 import {TranslateService} from "../../../shared/translator/translate.service";
29 import {ComponentGenericResponse} from '../../../services/responses/component-generic-response';
30 import {Observable} from 'rxjs/Observable';
31 import {PropertySource} from "../../../../models/property-source";
32 import {InstanceFeDetails} from "../../../../models/instance-fe-details";
33 import {ToscaGetFunction} from "../../../../models/tosca-get-function";
34
35 @Component({
36     selector: 'tosca-function',
37     templateUrl: './tosca-function.component.html',
38     styleUrls: ['./tosca-function.component.less'],
39 })
40 export class ToscaFunctionComponent implements OnInit {
41
42     @Input() property: PropertyBEModel;
43     @Input() componentInstanceMap: Map<string, InstanceFeDetails> = new Map<string, InstanceFeDetails>();
44     @Input() allowClear: boolean = true;
45
46     TOSCA_FUNCTION_GET_PROPERTY = ToscaGetFunctionType.GET_PROPERTY;
47
48     selectedProperty: PropertyDropdownValue;
49     isLoading: boolean = false;
50     propertyDropdownList: Array<PropertyDropdownValue> = [];
51     toscaFunctions: Array<string> = [];
52     propertySourceList: Array<string> = [];
53     instanceNameAndIdMap: Map<string, string> = new Map<string, string>();
54     dropdownValuesLabel: string;
55     dropDownErrorMsg: string;
56     propertySource: string
57     toscaGetFunction: ToscaGetFunction = new ToscaGetFunction(undefined);
58
59     private componentMetadata: ComponentMetadata;
60
61     constructor(private topologyTemplateService: TopologyTemplateService,
62                 private workspaceService: WorkspaceService,
63                 private propertiesService: PropertiesService,
64                 private dataTypeService: DataTypeService,
65                 private translateService: TranslateService) {
66     }
67
68     ngOnInit(): void {
69         this.componentMetadata = this.workspaceService.metadata;
70         this.loadToscaFunctions();
71         this.loadPropertySourceDropdown();
72         this.initToscaGetFunction();
73     }
74
75     private initToscaGetFunction(): void {
76         if (!this.property.isToscaGetFunction()) {
77             return;
78         }
79         this.toscaGetFunction = new ToscaGetFunction(this.property.toscaGetFunction);
80         if (this.toscaGetFunction.functionType === ToscaGetFunctionType.GET_PROPERTY) {
81             if (this.toscaGetFunction.propertySource === PropertySource.SELF) {
82                 this.propertySource = PropertySource.SELF;
83             } else {
84                 this.propertySource = this.toscaGetFunction.sourceName;
85             }
86         }
87         if (this.toscaGetFunction.propertyName) {
88             this.loadPropertyDropdown(() => {
89                 this.selectedProperty = this.propertyDropdownList.find(property => property.propertyName === this.toscaGetFunction.propertyName)
90             });
91         }
92
93     }
94
95     private loadToscaFunctions(): void {
96         this.toscaFunctions.push(ToscaGetFunctionType.GET_INPUT);
97         this.toscaFunctions.push(ToscaGetFunctionType.GET_PROPERTY);
98     }
99
100     private loadPropertySourceDropdown(): void {
101         this.propertySourceList.push(PropertySource.SELF);
102         this.componentInstanceMap.forEach((value, key) => {
103             const instanceName = value.name;
104             this.instanceNameAndIdMap.set(instanceName, key);
105             if (instanceName !== PropertySource.SELF) {
106                 this.addToPropertySource(instanceName);
107             }
108         });
109     }
110
111     private addToPropertySource(source: string): void {
112         this.propertySourceList.push(source);
113         this.propertySourceList.sort((a, b) => {
114             if (a === PropertySource.SELF) {
115                 return -1;
116             } else if (b === PropertySource.SELF) {
117                 return 1;
118             }
119
120             return a.localeCompare(b);
121         });
122     }
123
124     onToscaFunctionChange(): void {
125         this.resetPropertySource();
126         if (this.isGetInputSelected()) {
127             this.setSelfPropertySource();
128             this.loadPropertyDropdown();
129         }
130     }
131
132     private loadPropertyDropdown(onComplete: () => any = () => {}): void  {
133         this.loadPropertyDropdownLabel();
134         this.loadPropertyDropdownValues(onComplete);
135     }
136
137     private resetForm(): void {
138         this.toscaGetFunction = new ToscaGetFunction(undefined);
139         this.propertySource = undefined;
140         this.selectedProperty = undefined;
141     }
142
143     private resetPropertySource(): void {
144         this.toscaGetFunction.propertyUniqueId = undefined;
145         this.toscaGetFunction.propertyName = undefined;
146         this.toscaGetFunction.propertySource = undefined;
147         this.toscaGetFunction.sourceUniqueId = undefined;
148         this.toscaGetFunction.sourceName = undefined;
149         this.toscaGetFunction.propertyPathFromSource = undefined;
150         this.propertySource = undefined;
151         this.selectedProperty = undefined;
152     }
153
154     private loadPropertyDropdownLabel(): void {
155         if (!this.toscaGetFunction.functionType) {
156             return;
157         }
158         if (this.isGetInputSelected()) {
159             this.dropdownValuesLabel = this.translateService.translate('INPUT_DROPDOWN_LABEL');
160         } else if (this.isGetPropertySelected()) {
161             this.dropdownValuesLabel = this.translateService.translate('TOSCA_FUNCTION_PROPERTY_DROPDOWN_LABEL');
162         }
163     }
164
165     private loadPropertyDropdownValues(onComplete: () => any = () => {}): void {
166         if (!this.toscaGetFunction.functionType) {
167             return;
168         }
169         this.resetPropertyDropdown();
170         this.fillPropertyDropdownValues(onComplete);
171     }
172
173     private resetPropertyDropdown(): void {
174         this.dropDownErrorMsg = undefined;
175         this.selectedProperty = undefined;
176         this.propertyDropdownList = [];
177     }
178
179     private fillPropertyDropdownValues(onComplete: () => any = () => {}): void {
180         this.startLoading();
181         const propertiesObservable: Observable<ComponentGenericResponse> = this.getPropertyObservable();
182         propertiesObservable.subscribe( (response: ComponentGenericResponse) => {
183             const properties: PropertyBEModel[] = this.extractProperties(response);
184             if (!properties || properties.length === 0) {
185                 const msgCode = this.isGetInputSelected() ? 'TOSCA_FUNCTION_NO_INPUT_FOUND' : 'TOSCA_FUNCTION_NO_PROPERTY_FOUND';
186                 this.dropDownErrorMsg = this.translateService.translate(msgCode, {type: this.property.type});
187                 return;
188             }
189             this.addPropertiesToDropdown(properties);
190             if (this.propertyDropdownList.length == 0) {
191                 const msgCode = this.isGetInputSelected() ? 'TOSCA_FUNCTION_NO_INPUT_FOUND' : 'TOSCA_FUNCTION_NO_PROPERTY_FOUND';
192                 this.dropDownErrorMsg = this.translateService.translate(msgCode, {type: this.property.type});
193             }
194         }, (error) => {
195             console.error('An error occurred while loading properties.', error);
196         }, () => {
197             onComplete();
198             this.stopLoading();
199         });
200     }
201
202     private extractProperties(componentGenericResponse: ComponentGenericResponse): PropertyBEModel[] {
203         if (this.isGetInputSelected()) {
204             return componentGenericResponse.inputs;
205         }
206         if (this.isGetPropertySelected()) {
207             if (this.propertySource === PropertySource.SELF) {
208                 return componentGenericResponse.properties;
209             }
210             const componentInstanceProperties: PropertyModel[] = componentGenericResponse.componentInstancesProperties[this.instanceNameAndIdMap.get(this.propertySource)];
211             return this.removeSelectedProperty(componentInstanceProperties);
212         }
213     }
214
215     private getPropertyObservable(): Observable<ComponentGenericResponse> {
216         if (this.isGetInputSelected()) {
217             return this.topologyTemplateService.getComponentInputsValues(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
218         }
219         if (this.isGetPropertySelected()) {
220             if (this.propertySource === PropertySource.SELF) {
221                 return this.topologyTemplateService.findAllComponentProperties(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
222             }
223             return this.topologyTemplateService.getComponentInstanceProperties(this.componentMetadata.componentType, this.componentMetadata.uniqueId);
224         }
225     }
226
227     private removeSelectedProperty(componentInstanceProperties: PropertyModel[]): PropertyModel[] {
228         if (!componentInstanceProperties) {
229             return [];
230         }
231         return componentInstanceProperties.filter(property =>
232             (property.uniqueId !== this.property.uniqueId) ||
233             (property.uniqueId === this.property.uniqueId && property.resourceInstanceUniqueId !== this.property.parentUniqueId)
234         );
235     }
236
237     private addPropertyToDropdown(propertyDropdownValue: PropertyDropdownValue): void {
238         this.propertyDropdownList.push(propertyDropdownValue);
239         this.propertyDropdownList.sort((a, b) => a.propertyLabel.localeCompare(b.propertyLabel));
240     }
241
242     private addPropertiesToDropdown(properties: PropertyBEModel[]): void {
243         for (const property of properties) {
244             if (this.property.type === property.type) {
245                 this.addPropertyToDropdown({
246                     propertyName: property.name,
247                     propertyId: property.uniqueId,
248                     propertyLabel: property.name,
249                     propertyPath: [property.name]
250                 });
251             } else if (this.isComplexType(property.type)) {
252                 this.fillPropertyDropdownWithMatchingChildProperties(property);
253             }
254         }
255     }
256
257     private fillPropertyDropdownWithMatchingChildProperties(inputProperty: PropertyBEModel, parentPropertyList: Array<PropertyBEModel> = []): void {
258         const dataTypeFound: DataTypeModel = this.dataTypeService.getDataTypeByModelAndTypeName(this.componentMetadata.model, inputProperty.type);
259         if (!dataTypeFound || !dataTypeFound.properties) {
260             return;
261         }
262         parentPropertyList.push(inputProperty);
263         dataTypeFound.properties.forEach(dataTypeProperty => {
264             if (dataTypeProperty.type === this.property.type) {
265                 this.addPropertyToDropdown({
266                     propertyName: dataTypeProperty.name,
267                     propertyId: parentPropertyList[0].uniqueId,
268                     propertyLabel: parentPropertyList.map(property => property.name).join('->') + '->' + dataTypeProperty.name,
269                     propertyPath: [...parentPropertyList.map(property => property.name), dataTypeProperty.name]
270                 });
271             } else if (PROPERTY_DATA.SIMPLE_TYPES.indexOf(dataTypeProperty.type) === -1) {
272                 this.fillPropertyDropdownWithMatchingChildProperties(dataTypeProperty, [...parentPropertyList])
273             }
274         });
275     }
276
277     private isGetPropertySelected(): boolean {
278         return this.toscaGetFunction.functionType === ToscaGetFunctionType.GET_PROPERTY;
279     }
280
281     private isGetInputSelected(): boolean {
282         return this.toscaGetFunction.functionType === ToscaGetFunctionType.GET_INPUT;
283     }
284
285     private isComplexType(propertyType: string): boolean {
286         return PROPERTY_DATA.SIMPLE_TYPES.indexOf(propertyType) === -1;
287     }
288
289     private stopLoading(): void {
290         this.isLoading = false;
291     }
292
293     private startLoading(): void {
294         this.isLoading = true;
295     }
296
297     showDropdown(): boolean {
298         if (this.toscaGetFunction.functionType === ToscaGetFunctionType.GET_PROPERTY) {
299             return this.toscaGetFunction.propertySource && !this.isLoading && !this.dropDownErrorMsg;
300         }
301
302         return this.toscaGetFunction.functionType && !this.isLoading && !this.dropDownErrorMsg;
303     }
304
305     onPropertySourceChange(): void {
306         if (!this.toscaGetFunction.functionType || !this.propertySource) {
307             return;
308         }
309         this.toscaGetFunction.propertyUniqueId = undefined;
310         this.toscaGetFunction.propertyName = undefined;
311         this.toscaGetFunction.propertyPathFromSource = undefined;
312         if (this.propertySource === PropertySource.SELF) {
313             this.setSelfPropertySource();
314         } else {
315             this.toscaGetFunction.propertySource = PropertySource.INSTANCE;
316             this.toscaGetFunction.sourceName = this.propertySource;
317             this.toscaGetFunction.sourceUniqueId = this.instanceNameAndIdMap.get(this.propertySource);
318         }
319         this.loadPropertyDropdown();
320     }
321
322     private setSelfPropertySource(): void {
323         this.toscaGetFunction.propertySource = PropertySource.SELF;
324         this.toscaGetFunction.sourceName = this.componentMetadata.name;
325         this.toscaGetFunction.sourceUniqueId = this.componentMetadata.uniqueId;
326     }
327
328     onPropertyChange(): void {
329         this.toscaGetFunction.propertyUniqueId = this.selectedProperty.propertyId;
330         this.toscaGetFunction.propertyName = this.selectedProperty.propertyName;
331         this.toscaGetFunction.propertyPathFromSource = this.selectedProperty.propertyPath;
332     }
333
334     onClearValues() {
335         this.resetForm();
336     }
337
338     showClearButton(): boolean {
339         return this.allowClear && this.toscaGetFunction.functionType !== undefined;
340     }
341 }
342
343 export interface PropertyDropdownValue {
344     propertyName: string;
345     propertyId: string;
346     propertyLabel: string;
347     propertyPath: Array<string>;
348 }