Provide tosca function capability to complex type fields in composition view
[sdc.git] / catalog-ui / src / app / ng2 / pages / properties-assignment / tosca-function / tosca-function.component.ts
1 /*
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2021 Nordix Foundation
4  *  ================================================================================
5  *  Licensed under the Apache License, Version 2.0 (the "License");
6  *  you may not use this file except in compliance with the License.
7  *  You may obtain a copy of the License at
8  *
9  *        http://www.apache.org/licenses/LICENSE-2.0
10  *  Unless required by applicable law or agreed to in writing, software
11  *  distributed under the License is distributed on an "AS IS" BASIS,
12  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *  See the License for the specific language governing permissions and
14  *  limitations under the License.
15  *
16  *  SPDX-License-Identifier: Apache-2.0
17  *  ============LICENSE_END=========================================================
18  */
19
20 import {Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges} from '@angular/core';
21 import {ComponentMetadata, PropertyBEModel, PropertyDeclareAPIModel, DerivedFEProperty} from 'app/models';
22 import {TopologyTemplateService} from "../../../services/component-services/topology-template.service";
23 import {WorkspaceService} from "../../workspace/workspace.service";
24 import {ToscaGetFunctionType} from "../../../../models/tosca-get-function-type";
25 import {InstanceFeDetails} from "../../../../models/instance-fe-details";
26 import {ToscaGetFunction} from "../../../../models/tosca-get-function";
27 import {FormControl, FormGroup, Validators} from "@angular/forms";
28 import {ToscaFunctionType} from "../../../../models/tosca-function-type.enum";
29 import {ToscaGetFunctionValidationEvent} from "./tosca-get-function/tosca-get-function.component";
30 import {ToscaFunction} from "../../../../models/tosca-function";
31 import {ToscaConcatFunctionValidationEvent} from "./tosca-concat-function/tosca-concat-function.component";
32 import {ToscaCustomFunctionValidationEvent} from "./tosca-custom-function/tosca-custom-function.component";
33 import {PROPERTY_TYPES} from "../../../../utils/constants";
34 import {YamlFunctionValidationEvent} from "./yaml-function/yaml-function.component";
35 import {ToscaConcatFunction} from "../../../../models/tosca-concat-function";
36 import {ToscaCustomFunction} from "../../../../models/tosca-custom-function";
37 import {YamlFunction} from "../../../../models/yaml-function";
38 import {CustomToscaFunction} from "../../../../models/default-custom-functions";
39
40 @Component({
41     selector: 'tosca-function',
42     templateUrl: './tosca-function.component.html',
43     styleUrls: ['./tosca-function.component.less'],
44 })
45 export class ToscaFunctionComponent implements OnInit, OnChanges {
46
47     @Input() property: PropertyBEModel;
48     @Input() overridingType: PROPERTY_TYPES;
49     @Input() inToscaFunction: ToscaFunction;
50     @Input() componentInstanceMap: Map<string, InstanceFeDetails> = new Map<string, InstanceFeDetails>();
51     @Input() customToscaFunctions: Array<CustomToscaFunction> = [];
52     @Input() allowClear: boolean = true;
53     @Input() compositionMap: boolean = false;
54     @Input() compositionMapKey: string = "";
55     @Output() onValidFunction: EventEmitter<ToscaGetFunction> = new EventEmitter<ToscaGetFunction>();
56     @Output() onValidityChange: EventEmitter<ToscaFunctionValidationEvent> = new EventEmitter<ToscaFunctionValidationEvent>();
57
58     toscaFunctionForm: FormControl = new FormControl(undefined, [Validators.required]);
59     toscaFunctionTypeForm: FormControl = new FormControl(undefined, Validators.required);
60     formGroup: FormGroup = new FormGroup({
61         'toscaFunction': this.toscaFunctionForm,
62         'toscaFunctionType': this.toscaFunctionTypeForm,
63     });
64
65     isLoading: boolean = false;
66     toscaFunction: ToscaFunction;
67     toscaFunctions: Array<string> = [];
68     toscaCustomFunctions: Array<String> = [];
69
70     private isInitialized: boolean = false;
71     private componentMetadata: ComponentMetadata;
72
73     constructor(private topologyTemplateService: TopologyTemplateService,
74                 private workspaceService: WorkspaceService) {
75     }
76
77     ngOnInit(): void {
78         this.componentMetadata = this.workspaceService.metadata;
79         this.toscaFunction = this.inToscaFunction ? this.inToscaFunction : this.property.toscaFunction ? this.property.toscaFunction : undefined;
80         this.loadToscaFunctions();
81         this.formGroup.valueChanges.subscribe(() => {
82             if (!this.isInitialized) {
83                 return;
84             }
85             if (this.formGroup.valid) {
86                 this.onValidFunction.emit(this.toscaFunctionForm.value);
87             }
88         });
89         this.initToscaFunction();
90         this.emitValidityChange();
91         this.isInitialized = true;
92     }
93
94     ngOnChanges(changes: SimpleChanges): void {
95         if (changes.property) {
96             this.resetForm();
97             this.toscaFunction = this.inToscaFunction ? this.inToscaFunction : this.property.toscaFunction ? this.property.toscaFunction : undefined;
98             this.initToscaFunction();
99             this.loadToscaFunctions();
100             this.emitValidityChange();
101         }
102     }
103
104     private validate(): boolean {
105         return (!this.toscaFunctionForm.value && !this.toscaFunctionTypeForm.value) || this.formGroup.valid;
106     }
107
108     private initToscaFunction(): void {
109         if (this.compositionMap && this.property.subPropertyToscaFunctions) {
110             let keyToFind = [this.compositionMapKey];
111             let subPropertyToscaFunction = this.property.subPropertyToscaFunctions.find(subPropertyToscaFunction => this.areEqual(subPropertyToscaFunction.subPropertyPath, keyToFind));
112
113             if (subPropertyToscaFunction){
114                 this.toscaFunction = subPropertyToscaFunction.toscaFunction;
115                 this.toscaFunctionForm.setValue(this.toscaFunction);
116                 let type = this.toscaFunction.type;
117                 if (type == ToscaFunctionType.CUSTOM) {
118                     let name = (subPropertyToscaFunction.toscaFunction as ToscaCustomFunction).name;
119                     let customToscaFunc = this.customToscaFunctions.find(custToscFunc => _.isEqual(custToscFunc.name, name))
120                     if (customToscaFunc) {
121                         this.toscaFunctionTypeForm.setValue(name);
122                     } else {
123                         this.toscaFunctionTypeForm.setValue("other");
124                     }
125                 } else {
126                     this.toscaFunctionTypeForm.setValue(type);
127                 }
128             }
129             return;
130         }
131             if (this.property instanceof PropertyDeclareAPIModel && this.property.subPropertyToscaFunctions && (<PropertyDeclareAPIModel> this.property).propertiesName){
132                 let propertiesPath = (<PropertyDeclareAPIModel> this.property).propertiesName.split("#");
133             if (propertiesPath.length > 1){
134                 let keyToFind = (<DerivedFEProperty>this.property.input).toscaPath;
135                 let subPropertyToscaFunction = this.property.subPropertyToscaFunctions.find(subPropertyToscaFunction => this.areEqual(subPropertyToscaFunction.subPropertyPath, keyToFind.length > 0 ? keyToFind : propertiesPath.slice(1)));
136
137                 if (subPropertyToscaFunction){
138                         this.toscaFunction = subPropertyToscaFunction.toscaFunction;
139                     this.toscaFunctionForm.setValue(this.toscaFunction);
140                     let type = this.toscaFunction.type;
141                     if (type == ToscaFunctionType.CUSTOM) {
142                         let name = (subPropertyToscaFunction.toscaFunction as ToscaCustomFunction).name;
143                         let customToscaFunc = this.customToscaFunctions.find(custToscFunc => _.isEqual(custToscFunc.name, name))
144                         if (customToscaFunc) {
145                             this.toscaFunctionTypeForm.setValue(name);
146                         } else {
147                             this.toscaFunctionTypeForm.setValue("other");
148                         }
149                     } else {
150                         this.toscaFunctionTypeForm.setValue(type);
151                     }
152                 }
153                 return;
154             }
155         }
156         if (!this.property.isToscaFunction()) {
157             return;
158         }
159
160         this.toscaFunctionForm.setValue(this.inToscaFunction ? this.inToscaFunction : this.property.toscaFunction);
161         let type = this.property.toscaFunction.type;
162         if (type == ToscaFunctionType.CUSTOM) {
163             let name = (this.property.toscaFunction as ToscaCustomFunction).name;
164             let customToscaFunc = this.customToscaFunctions.find(custToscFunc => _.isEqual(custToscFunc.name, name))
165             if (customToscaFunc) {
166                 this.toscaFunctionTypeForm.setValue(name);
167             } else {
168                 this.toscaFunctionTypeForm.setValue("other");
169             }
170         } else {
171             this.toscaFunctionTypeForm.setValue(this.inToscaFunction ? this.inToscaFunction.type : type);
172         }
173     }
174
175     private areEqual(array1: string[], array2: string[]): boolean {
176             return array1.length === array2.length && array1.every(function(value, index) { return value === array2[index]})
177     }
178
179     private loadToscaFunctions(): void {
180         this.toscaFunctions = [];
181         this.toscaFunctions.push(ToscaFunctionType.GET_ATTRIBUTE);
182         this.toscaFunctions.push(ToscaFunctionType.GET_INPUT);
183         this.toscaFunctions.push(ToscaFunctionType.GET_PROPERTY);
184         if ((this.property.type === PROPERTY_TYPES.STRING || this.property.type === PROPERTY_TYPES.ANY) && this.overridingType === undefined) {
185             this.toscaFunctions.push(ToscaFunctionType.CONCAT);
186         }
187         this.loadCustomToscaFunctions();
188     }
189
190     private loadCustomToscaFunctions(): void {
191         if (!this.customToscaFunctions.find(custToscFunc => _.isEqual(custToscFunc.name, "other"))) {
192             let other = new CustomToscaFunction();
193             other.name = "other";
194             other.type = ToscaFunctionType.CUSTOM;
195             this.customToscaFunctions.push(other);
196         }
197         this.toscaCustomFunctions = [];
198         for (let func of this.customToscaFunctions) {
199             this.toscaCustomFunctions.push(func.name);
200         }
201     }
202
203     getCustomToscaFunction(): CustomToscaFunction {
204         let funcName = this.formGroup.get('toscaFunctionType').value;
205         return this.customToscaFunctions.find(custToscFunc => _.isEqual(custToscFunc.name, funcName));
206     }
207
208     getCustomFunctionName():string {
209         let toscaFunctionType: CustomToscaFunction = this.getCustomToscaFunction();
210         let name = toscaFunctionType.name;
211         return name == 'other' ? '' : name;
212     }
213
214     getCustomFunctionType():string {
215         let toscaFunctionType: CustomToscaFunction = this.getCustomToscaFunction();
216         return toscaFunctionType.type;
217     }
218
219     isDefaultCustomFunction(): boolean {
220         let toscaFunctionType: CustomToscaFunction = this.getCustomToscaFunction();
221         if (toscaFunctionType.name === "other") {
222             return false;
223         }
224         return this.customToscaFunctions.filter(e => e.name === toscaFunctionType.name).length > 0;
225     }
226
227     private resetForm(): void {
228         this.formGroup.reset();
229         this.toscaFunction = undefined;
230     }
231
232     private isGetPropertySelected(): boolean {
233         return this.formGroup.get('toscaFunctionType').value === ToscaGetFunctionType.GET_PROPERTY;
234     }
235
236     private isGetAttributeSelected(): boolean {
237         return this.formGroup.get('toscaFunctionType').value === ToscaGetFunctionType.GET_ATTRIBUTE;
238     }
239
240     private isGetInputSelected(): boolean {
241         return this.formGroup.get('toscaFunctionType').value === ToscaGetFunctionType.GET_INPUT;
242     }
243
244     isConcatSelected(): boolean {
245         return this.formGroup.get('toscaFunctionType').value === ToscaFunctionType.CONCAT;
246     }
247
248     isCustomSelected(): boolean {
249         let toscaFunctionType: CustomToscaFunction = this.getCustomToscaFunction();
250         return toscaFunctionType && (toscaFunctionType.type === ToscaFunctionType.CUSTOM || toscaFunctionType.type === ToscaFunctionType.GET_INPUT);
251     }
252
253     isGetFunctionSelected(): boolean {
254         return this.isGetInputSelected() || this.isGetPropertySelected() || this.isGetAttributeSelected();
255     }
256
257     isYamlFunctionSelected(): boolean {
258         return this.formGroup.get('toscaFunctionType').value === ToscaFunctionType.YAML;
259     }
260
261     onClearValues(): void {
262         this.resetForm();
263     }
264
265     showClearButton(): boolean {
266         return this.allowClear && this.toscaFunctionTypeForm.value;
267     }
268
269     onConcatFunctionValidityChange(validationEvent: ToscaConcatFunctionValidationEvent): void {
270         if (validationEvent.isValid) {
271             this.toscaFunctionForm.setValue(validationEvent.toscaConcatFunction);
272         } else {
273             this.toscaFunctionForm.setValue(undefined);
274         }
275     }
276
277     onCustomFunctionValidityChange(validationEvent: ToscaCustomFunctionValidationEvent): void {
278         if (validationEvent.isValid) {
279             this.toscaFunctionForm.setValue(validationEvent.toscaCustomFunction);
280         } else {
281             this.toscaFunctionForm.setValue(undefined);
282         }
283         this.emitValidityChange();
284     }
285
286     onGetFunctionValidityChange(validationEvent: ToscaGetFunctionValidationEvent): void {
287         if (validationEvent.isValid) {
288             this.toscaFunctionForm.setValue(validationEvent.toscaGetFunction);
289         } else {
290             this.toscaFunctionForm.setValue(undefined);
291         }
292         this.emitValidityChange();
293     }
294
295     onYamlFunctionValidityChange(validationEvent: YamlFunctionValidationEvent): void {
296         if (validationEvent.isValid) {
297             this.toscaFunctionForm.setValue(validationEvent.value);
298         } else {
299             this.toscaFunctionForm.setValue(undefined);
300         }
301     }
302
303     onFunctionTypeChange(): void {
304         this.toscaFunction = undefined;
305         this.toscaFunctionForm.reset();
306     }
307
308     private emitValidityChange(): void {
309         const isValid: boolean = this.validate();
310         this.onValidityChange.emit({
311             isValid: isValid,
312             toscaFunction: isValid ? this.buildFunctionFromForm() : undefined
313         });
314     }
315
316     private buildFunctionFromForm(): ToscaFunction {
317         if (!this.toscaFunctionTypeForm.value) {
318             return undefined;
319         }
320         if (this.isConcatSelected()) {
321             return new ToscaConcatFunction(this.toscaFunctionForm.value);
322         }
323         if (this.isCustomSelected()) {
324             return new ToscaCustomFunction(this.toscaFunctionForm.value);
325         }
326         if (this.isGetFunctionSelected()) {
327             return new ToscaGetFunction(this.toscaFunctionForm.value);
328         }
329         if (this.isYamlFunctionSelected()) {
330             return new YamlFunction(this.toscaFunctionForm.value);
331         }
332
333         console.error(`Function ${this.toscaFunctionTypeForm.value} not supported`);
334     }
335 }
336
337 export class ToscaFunctionValidationEvent {
338     isValid: boolean;
339     toscaFunction: ToscaFunction;
340 }