Validation problems when trying to set an operation input of complex type
[sdc.git] / catalog-ui / src / app / ng2 / pages / composition / interface-operatons / operation-creator / interface-operation-handler.component.ts
1 /*
2 * ============LICENSE_START=======================================================
3 * SDC
4 * ================================================================================
5 *  Copyright (C) 2021 Nordix Foundation. All rights reserved.
6 *  ================================================================================
7 *  Licensed under the Apache License, Version 2.0 (the "License");
8 *  you may not use this file except in compliance with the License.
9 *  You may obtain a copy of the License at
10 *
11 *        http://www.apache.org/licenses/LICENSE-2.0
12 *  Unless required by applicable law or agreed to in writing, software
13 *  distributed under the License is distributed on an "AS IS" BASIS,
14 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 *  See the License for the specific language governing permissions and
16 *  limitations under the License.
17 *
18 *  SPDX-License-Identifier: Apache-2.0
19 *  ============LICENSE_END=========================================================
20 */
21 import {Component, EventEmitter, Output, ViewChild} from '@angular/core';
22 import {UIInterfaceModel} from "../interface-operations.component";
23 import {InputOperationParameter, InterfaceOperationModel, IOperationParamsList} from "../../../../../models/interfaceOperation";
24 import {TranslateService} from "../../../../shared/translator/translate.service";
25 import {DropdownValue} from "../../../../components/ui/form-components/dropdown/ui-element-dropdown.component";
26 import {ArtifactModel} from "../../../../../models/artifacts";
27 import {PropertyBEModel} from "../../../../../models/properties-inputs/property-be-model";
28 import {PropertyParamRowComponent} from "./property-param-row/property-param-row.component";
29 import {PropertyFEModel} from "../../../../../models/properties-inputs/property-fe-model";
30 import {IDropDownOption} from 'onap-ui-angular';
31 import {ComponentServiceNg2} from "../../../../services/component-services/component.service";
32 import {DropDownComponent} from "onap-ui-angular/dist/form-elements/dropdown/dropdown.component";
33 import {DataTypeService} from "../../../../services/data-type.service";
34 import {Observable} from "rxjs/Observable";
35 import {DataTypeModel} from "../../../../../models/data-types";
36 import {InstanceFeDetails} from "../../../../../models/instance-fe-details";
37 import {TopologyTemplateService} from "app/ng2/services/component-services/topology-template.service";
38 import {CustomToscaFunction} from "../../../../../models/default-custom-functions";
39 import {ToscaFunctionType} from "../../../../../models/tosca-function-type.enum";
40
41 @Component({
42     selector: 'operation-handler',
43     templateUrl: './interface-operation-handler.component.html',
44     styleUrls: ['./interface-operation-handler.component.less'],
45     providers: [TranslateService]
46 })
47 export class InterfaceOperationHandlerComponent {
48
49     @Output('propertyChanged') emitter: EventEmitter<PropertyFEModel> = new EventEmitter<PropertyFEModel>();
50     @ViewChild('interfaceOperationDropDown') interfaceOperationDropDown: DropDownComponent;
51
52     input: {
53         componentInstanceMap: Map<string, InstanceFeDetails>;
54         toscaArtifactTypes: Array<DropdownValue>;
55         selectedInterface: UIInterfaceModel;
56         selectedInterfaceOperation: InterfaceOperationModel;
57         validityChangedCallback: Function;
58         isViewOnly: boolean;
59         isEdit: boolean;
60         validImplementationProps:boolean;
61         modelName: string;
62     };
63
64     dataTypeMap$: Observable<Map<string, DataTypeModel>>;
65     dataTypeMap: Map<string, DataTypeModel>;
66     interfaceType: string;
67     artifactVersion: string;
68     artifactName: string;
69     interfaceOperationName: string;
70     operationToUpdate: InterfaceOperationModel;
71     inputs: Array<InputOperationParameter> = [];
72     properties: Array<PropertyParamRowComponent> = [];
73     isLoading: boolean = false;
74     isViewOnly: boolean;
75     isEdit: boolean;
76     validImplementationProps:boolean;
77     interfaceTypes: Array<DropdownValue> = [];
78     interfaceTypeOptions: Array<DropDownOption> = [];
79     selectedInterfaceType: DropDownOption = undefined;
80     interfaceOperationMap: Map<string, Array<string>> = new Map<string, Array<string>>();
81     interfaceOperationOptions: Array<DropDownOption> = [];
82     selectedInterfaceOperation: DropDownOption = undefined;
83     modelName: string;
84     toscaArtifactTypeSelected: string;
85     toscaArtifactTypeProperties: Array<PropertyBEModel> = [];
86     artifactTypeProperties: Array<InputOperationParameter> = [];
87     toscaArtifactTypes: Array<DropdownValue> = [];
88     componentInstanceMap: Map<string, InstanceFeDetails>;
89     customToscaFunctions: Array<CustomToscaFunction>;
90     enableAddArtifactImplementation: boolean;
91     propertyValueValid: boolean = true;
92     inputTypeOptions: any[];
93
94     constructor(private dataTypeService: DataTypeService,
95                 private componentServiceNg2: ComponentServiceNg2,
96                 private topologyTemplateService: TopologyTemplateService) {
97     }
98
99     ngOnInit() {
100         this.isViewOnly = this.input.isViewOnly;
101         this.isEdit = this.input.isEdit;
102         this.validImplementationProps = this.input.validImplementationProps;
103         this.componentInstanceMap =  this.input.componentInstanceMap ? this.input.componentInstanceMap : null;
104         this.interfaceType = this.input.selectedInterface.type;
105         this.operationToUpdate = new InterfaceOperationModel(this.input.selectedInterfaceOperation);
106         this.operationToUpdate.interfaceId = this.input.selectedInterface.uniqueId;
107         this.operationToUpdate.interfaceType = this.input.selectedInterface.type;
108         this.modelName = this.input.modelName;
109         this.initCustomToscaFunctions();
110         this.initInputs();
111         this.removeImplementationQuote();
112         this.loadInterfaceOperationImplementation();
113
114         this.dataTypeMap$ = new Observable<Map<string, DataTypeModel>>(subscriber => {
115             this.dataTypeService.findAllDataTypesByModel(this.modelName)
116             .then((dataTypesMap: Map<string, DataTypeModel>) => {
117                 subscriber.next(dataTypesMap);
118             });
119         });
120         this.dataTypeMap$.subscribe(value => {
121             this.dataTypeMap = value;
122         });
123     }
124
125     private initInputs() {
126         if (!this.operationToUpdate.inputs) {
127             this.operationToUpdate.inputs = new class implements IOperationParamsList {
128                 listToscaDataDefinition: Array<InputOperationParameter> = [];
129             }
130         }
131
132         this.inputs = Array.from(this.operationToUpdate.inputs.listToscaDataDefinition);
133         this.removeImplementationQuote();
134         this.loadInterfaceOperationImplementation();
135         this.loadInterfaceType();
136     }
137
138     private initCustomToscaFunctions() {
139         this.customToscaFunctions = [];
140         this.topologyTemplateService.getDefaultCustomFunction().toPromise().then((data) => {
141             if (data) {
142                 for (let customFunction of data) {
143                     this.customToscaFunctions.push(new CustomToscaFunction(customFunction));
144                 }
145             }
146         });
147     }
148
149     private loadInterfaceType() {
150         this.componentServiceNg2.getInterfaceTypesByModel(this.modelName)
151         .subscribe(response => {
152             if (response) {
153                 this.interfaceOperationMap = new Map<string, Array<string>>();
154                 for (const interfaceType of Object.keys(response).sort()) {
155                     const operationList = response[interfaceType];
156                     operationList.sort();
157                     this.interfaceOperationMap.set(interfaceType, operationList);
158                     const operationDropDownOption: DropDownOption = new DropDownOption(interfaceType);
159                     this.interfaceTypeOptions.push(operationDropDownOption);
160                     if (this.interfaceType == interfaceType) {
161                         this.selectedInterfaceType = operationDropDownOption;
162                     }
163                 }
164                 this.loadInterfaceTypeOperations();
165             }
166         });
167     }
168
169     loadInterfaceTypeOperations() {
170         this.interfaceOperationOptions = new Array<DropDownOption>();
171         const interfaceOperationList = this.interfaceOperationMap.get(this.interfaceType);
172
173         if (interfaceOperationList) {
174             interfaceOperationList.forEach(operationName => {
175                 const operationOption = new DropDownOption(operationName, operationName);
176                 this.interfaceOperationOptions.push(operationOption);
177                 if (this.operationToUpdate.name == operationName) {
178                     this.selectedInterfaceOperation = operationOption
179                 }
180             });
181         }
182
183         this.interfaceOperationDropDown.allOptions = this.interfaceOperationOptions;
184     }
185
186     private loadInterfaceOperationImplementation() {
187         this.toscaArtifactTypes = this.input.toscaArtifactTypes;
188         if (this.operationToUpdate.implementation) {
189             this.artifactVersion = this.operationToUpdate.implementation.artifactVersion;
190             this.artifactName = this.operationToUpdate.implementation.artifactName;
191             this.toscaArtifactTypeProperties = this.operationToUpdate.implementation.properties;
192         }
193         this.artifactTypeProperties = this.convertArtifactsPropertiesToInput();
194         this.getArtifactTypesSelected();
195     }
196
197     onDescriptionChange = (value: any): void => {
198         this.operationToUpdate.description = value;
199     }
200
201     onURIChange(value: string | undefined) {
202         if(!this.operationToUpdate.implementation){
203             let artifact = new ArtifactModel();
204             this.operationToUpdate.implementation = artifact;
205         }
206         this.operationToUpdate.implementation.artifactName = value ? value : '';
207     }
208
209     onPropertyValueChange = (propertyValue) => {
210         this.emitter.emit(propertyValue);
211     }
212
213     onMarkToAddArtifactToImplementation(event: boolean) {
214         if (!event) {
215             this.toscaArtifactTypeSelected = undefined;
216             this.artifactVersion = undefined;
217             if (this.operationToUpdate.implementation.artifactType) {
218                 this.operationToUpdate.implementation.artifactVersion = '';
219                 this.operationToUpdate.implementation.artifactType = '';
220             }
221             this.toscaArtifactTypeProperties = undefined;
222             this.artifactTypeProperties = undefined;
223         } else {
224             this.getArtifactTypesSelected();
225         }
226         this.enableAddArtifactImplementation = event;
227     }
228
229     onSelectToscaArtifactType(type: IDropDownOption) {
230         if (type) {
231             let toscaArtifactType = type.value;
232             let artifact = new ArtifactModel();
233             artifact.artifactName = this.operationToUpdate.implementation.artifactName;
234             artifact.artifactVersion = this.operationToUpdate.implementation.artifactVersion;
235             artifact.artifactType = toscaArtifactType.type;
236             artifact.properties = toscaArtifactType.properties;
237             this.toscaArtifactTypeProperties = artifact.properties;
238             this.artifactTypeProperties = this.convertArtifactsPropertiesToInput();
239             this.toscaArtifactTypeSelected = artifact.artifactType;
240             this.operationToUpdate.implementation = artifact;
241             this.getArtifactTypesSelected();
242         }
243     }
244
245     onArtifactVersionChange(value: string | undefined) {
246             this.operationToUpdate.implementation.artifactVersion = value ? value : '';
247     }
248
249     onAddInput(inputOperationParameter: InputOperationParameter) {
250         this.addInput(inputOperationParameter);
251     }
252
253     propertyValueValidation = (propertyValue): void => {
254         this.onPropertyValueChange(propertyValue);
255         this.propertyValueValid = propertyValue.isValid;
256     }
257
258     onRemoveInput = (inputParam: InputOperationParameter): void => {
259         let index = this.inputs.indexOf(inputParam);
260         this.inputs.splice(index, 1);
261     }
262
263     private removeImplementationQuote(): void {
264         if (this.operationToUpdate.implementation) {
265             if (!this.operationToUpdate.implementation
266                 || !this.operationToUpdate.implementation.artifactName) {
267                 return;
268             }
269
270             let implementation = this.operationToUpdate.implementation.artifactName.trim();
271
272             if (implementation.startsWith("'") && implementation.endsWith("'")) {
273                 this.operationToUpdate.implementation.artifactName = implementation.slice(1, -1);
274             }
275         }
276     }
277
278     private getArtifactTypesSelected() {
279         if (this.operationToUpdate.implementation && this.operationToUpdate.implementation.artifactType) {
280             this.artifactName =
281                 this.artifactName ? this.artifactName : this.operationToUpdate.implementation.artifactName;
282             this.toscaArtifactTypeSelected = this.operationToUpdate.implementation.artifactType;
283             this.artifactVersion =
284                 this.artifactVersion ? this.artifactVersion : this.operationToUpdate.implementation.artifactVersion;
285             this.toscaArtifactTypeProperties = this.operationToUpdate.implementation.properties;
286             this.artifactTypeProperties = this.convertArtifactsPropertiesToInput();
287             this.enableAddArtifactImplementation = true;
288         }
289     }
290
291     toDropDownOption(val: string) {
292         return { value : val, label: val };
293     }
294
295     /**
296      * Handles the input value change event.
297      * @param changedInput the changed input
298      */
299     onInputValueChange(changedInput: InputOperationParameter) {
300         if (changedInput.value instanceof Object) {
301             changedInput.value = JSON.stringify(changedInput.value);
302         }
303         const inputOperationParameter = this.inputs.find(value => value.name == changedInput.name);
304         inputOperationParameter.toscaFunction = null;
305         inputOperationParameter.value = changedInput.value;
306         if (inputOperationParameter.subPropertyToscaFunctions) {
307             inputOperationParameter.subPropertyToscaFunctions = undefined;
308         }
309         if (changedInput.isToscaFunction()) {
310             inputOperationParameter.toscaFunction = changedInput.toscaFunction;
311             inputOperationParameter.value = changedInput.toscaFunction.buildValueString();
312         }
313     }
314
315     onArtifactPropertyValueChange(changedProperty: InputOperationParameter) {
316         const property = this.toscaArtifactTypeProperties.find(artifactProperty => artifactProperty.name == changedProperty.name);
317         if (changedProperty.value instanceof Object) {
318             changedProperty.value = JSON.stringify(changedProperty.value);
319         }
320         property.toscaFunction = null;
321         property.value = changedProperty.value;
322         if (changedProperty.isToscaFunction()) {
323             property.toscaFunction = changedProperty.toscaFunction;
324             property.value = changedProperty.toscaFunction.buildValueString();
325         }
326     }
327
328     implementationPropsValidityChange(validImplementationProps: boolean) {
329         this.validImplementationProps = validImplementationProps;
330     }
331
332     /**
333      * Handles the add input event.
334      * @param input the input to add
335      * @private
336      */
337     private addInput(input: InputOperationParameter) {
338         this.operationToUpdate.inputs.listToscaDataDefinition.push(input);
339         this.inputs = Array.from(this.operationToUpdate.inputs.listToscaDataDefinition);
340     }
341
342     /**
343      * Return a list with current input names.
344      */
345     collectInputNames() {
346         return this.inputs.map((input) => input.name);
347     }
348
349     /**
350      * Handles the delete input event.
351      * @param inputName the name of the input to be deleted
352      */
353     onInputDelete(inputName: string) {
354         const currentInputs = this.operationToUpdate.inputs.listToscaDataDefinition;
355         const input1 = currentInputs.find(value => value.name === inputName);
356         const indexOfInput = currentInputs.indexOf(input1);
357         if (indexOfInput === -1) {
358             console.error(`Could not delete input '${inputName}'. Input not found.`);
359             return;
360         }
361         currentInputs.splice(currentInputs.indexOf(input1), 1);
362         this.inputs = Array.from(currentInputs);
363     }
364
365     private convertArtifactsPropertiesToInput(): Array<InputOperationParameter> {
366         if (!this.toscaArtifactTypeProperties) {
367             return [];
368         }
369         const inputList: Array<InputOperationParameter> = [];
370         this.toscaArtifactTypeProperties.forEach(property => {
371             const input = new InputOperationParameter();
372             input.name = property.name;
373             input.type = property.type;
374             input.schema = property.schema;
375             input.toscaDefaultValue = property.defaultValue;
376             input.value = property.value;
377             input.toscaFunction = property.toscaFunction;
378             inputList.push(input);
379         });
380         return inputList;
381     }
382
383     onSelectInterface(dropDownOption: DropDownOption) {
384         if (dropDownOption) {
385             this.setInterfaceType(dropDownOption);
386         } else {
387             this.setInterfaceType(undefined);
388         }
389         this.setInterfaceOperation(undefined);
390         this.interfaceOperationDropDown.selectOption({} as IDropDownOption);
391         this.loadInterfaceTypeOperations();
392     }
393
394     onSelectOperation(dropDownOption: DropDownOption) {
395         if (this.selectedInterfaceType && dropDownOption) {
396             this.setInterfaceOperation(dropDownOption);
397         }
398     }
399
400     private setInterfaceType(dropDownOption: DropDownOption) {
401         this.selectedInterfaceType = dropDownOption ? dropDownOption : undefined;
402         this.interfaceType = dropDownOption ? dropDownOption.value : undefined;
403         this.operationToUpdate.interfaceType = dropDownOption ? dropDownOption.value : undefined;
404         this.operationToUpdate.interfaceId = dropDownOption ? dropDownOption.value : undefined;
405     }
406
407     private setInterfaceOperation(dropDownOption: DropDownOption) {
408         this.operationToUpdate.name = dropDownOption ? dropDownOption.value : undefined;
409         this.operationToUpdate.operationType = dropDownOption ? dropDownOption.value : undefined;
410         this.selectedInterfaceOperation = dropDownOption ? dropDownOption : undefined;
411     }
412 }
413
414 class DropDownOption implements IDropDownOption {
415     value: string;
416     label: string;
417
418     constructor(value: string, label?: string) {
419         this.value = value;
420         this.label = label || value;
421     }
422 }