Issues found when trying to create activities in interface operations
[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 { FormControl } from '@angular/forms';
23 import {UIInterfaceModel} from "../interface-operations.component";
24 import {
25     ActivityParameter,
26     IActivityParameterList,
27     IFilterParameterList,
28     InputOperationParameter,
29     InterfaceOperationModel,
30     IOperationParamsList,
31     FilterParameter,
32     Milestone,
33     MilestoneEnum
34 } from "../../../../../models/interfaceOperation";
35 import {TranslateService} from "../../../../shared/translator/translate.service";
36 import {DropdownValue} from "../../../../components/ui/form-components/dropdown/ui-element-dropdown.component";
37 import {ArtifactModel} from "../../../../../models/artifacts";
38 import {PropertyBEModel} from "../../../../../models/properties-inputs/property-be-model";
39 import {PropertyParamRowComponent} from "./property-param-row/property-param-row.component";
40 import {PropertyFEModel} from "../../../../../models/properties-inputs/property-fe-model";
41 import {IDropDownOption} from 'onap-ui-angular';
42 import {ComponentServiceNg2} from "../../../../services/component-services/component.service";
43 import {DropDownComponent} from "onap-ui-angular/dist/form-elements/dropdown/dropdown.component";
44 import {DataTypeService} from "../../../../services/data-type.service";
45 import {Observable} from "rxjs/Observable";
46 import {DataTypeModel} from "../../../../../models/data-types";
47 import {InstanceFeDetails} from "../../../../../models/instance-fe-details";
48 import {TopologyTemplateService} from "app/ng2/services/component-services/topology-template.service";
49 import {CustomToscaFunction} from "../../../../../models/default-custom-functions";
50
51 @Component({
52     selector: 'operation-handler',
53     templateUrl: './interface-operation-handler.component.html',
54     styleUrls: ['./interface-operation-handler.component.less'],
55     providers: [TranslateService]
56 })
57 export class InterfaceOperationHandlerComponent {
58
59     @Output('propertyChanged') emitter: EventEmitter<PropertyFEModel> = new EventEmitter<PropertyFEModel>();
60     @ViewChild('interfaceOperationDropDown') interfaceOperationDropDown: DropDownComponent;
61
62     input: {
63         componentInstanceMap: Map<string, InstanceFeDetails>;
64         toscaArtifactTypes: Array<DropdownValue>;
65         selectedInterface: UIInterfaceModel;
66         selectedInterfaceOperation: InterfaceOperationModel;
67         validityChangedCallback: Function;
68         isViewOnly: boolean;
69         isEdit: boolean;
70         validImplementationProps: boolean;
71         validMilestoneActivities: boolean;
72         validMilestoneFilters: boolean;
73         modelName: string;
74     };
75
76     dataTypeMap$: Observable<Map<string, DataTypeModel>>;
77     dataTypeMap: Map<string, DataTypeModel>;
78     interfaceType: string;
79     artifactVersion: string;
80     artifactName: string;
81     interfaceOperationName: string;
82     operationToUpdate: InterfaceOperationModel;
83     inputs: Array<InputOperationParameter> = [];
84     properties: Array<PropertyParamRowComponent> = [];
85     isLoading: boolean = false;
86     isViewOnly: boolean;
87     isEdit: boolean;
88     validImplementationProps: boolean;
89     validMilestoneActivities: boolean;
90     validMilestoneFilters: boolean;
91     interfaceTypes: Array<DropdownValue> = [];
92     interfaceTypeOptions: Array<DropDownOption> = [];
93     selectedInterfaceType: DropDownOption = undefined;
94     interfaceOperationMap: Map<string, Array<string>> = new Map<string, Array<string>>();
95     interfaceOperationOptions: Array<DropDownOption> = [];
96     selectedInterfaceOperation: DropDownOption = undefined;
97     modelName: string;
98     toscaArtifactTypeSelected: string;
99     toscaArtifactTypeProperties: Array<PropertyBEModel> = [];
100     artifactTypeProperties: Array<InputOperationParameter> = [];
101     toscaArtifactTypes: Array<DropdownValue> = [];
102     componentInstanceMap: Map<string, InstanceFeDetails>;
103     customToscaFunctions: Array<CustomToscaFunction>;
104     enableAddArtifactImplementation: boolean;
105     propertyValueValid: boolean = true;
106     showActivities: boolean = false;
107     inputTypeOptions: any[];
108     timeoutValue = new FormControl('');
109     timeoutType = new FormControl('');
110     invalidMilestones: string[] = [];
111     activeTab: string;
112     milestones = Object.keys(MilestoneEnum)
113             .map(key => MilestoneEnum[key]);
114
115     constructor(private dataTypeService: DataTypeService,
116                 private componentServiceNg2: ComponentServiceNg2,
117                 private topologyTemplateService: TopologyTemplateService) {
118     }
119
120     ngOnInit() {
121         this.isViewOnly = this.input.isViewOnly;
122         this.isEdit = this.input.isEdit;
123         this.validImplementationProps = this.input.validImplementationProps;
124         this.validMilestoneActivities = this.input.validMilestoneActivities;
125         this.validMilestoneFilters = this.input.validMilestoneFilters;
126         this.componentInstanceMap =  this.input.componentInstanceMap ? this.input.componentInstanceMap : null;
127         this.interfaceType = this.input.selectedInterface.type;
128         this.operationToUpdate = new InterfaceOperationModel(this.input.selectedInterfaceOperation);
129         this.operationToUpdate.interfaceId = this.input.selectedInterface.uniqueId;
130         this.operationToUpdate.interfaceType = this.input.selectedInterface.type;
131         this.modelName = this.input.modelName;
132         this.timeoutType.setValue('sec');
133         if (this.operationToUpdate.implementation && this.operationToUpdate.implementation.timeout != null) {
134             this.timeoutValue.setValue(this.operationToUpdate.implementation.timeout);
135             let timeout = this.timeoutValue.value / 3600;
136             if (Number.isInteger(timeout)) {
137                 if (timeout > 23 && Number.isInteger(timeout / 24)) {
138                     this.timeoutValue.setValue(timeout / 24);
139                     this.timeoutType.setValue("day");
140                 } else {
141                     this.timeoutValue.setValue(timeout);
142                     this.timeoutType.setValue("hour");
143                 }
144             } else if (Number.isInteger(timeout / 24)) {
145                 this.timeoutValue.setValue(timeout / 24);
146                 this.timeoutType.setValue("day");
147             }
148         }
149         if (!this.operationToUpdate.milestones) {
150             this.operationToUpdate.milestones = {};
151         }
152         this.initCustomToscaFunctions();
153         this.initInputs();
154         this.removeImplementationQuote();
155         this.loadInterfaceOperationImplementation();
156
157         this.dataTypeMap$ = new Observable<Map<string, DataTypeModel>>(subscriber => {
158             this.dataTypeService.findAllDataTypesByModel(this.modelName)
159             .then((dataTypesMap: Map<string, DataTypeModel>) => {
160                 subscriber.next(dataTypesMap);
161                 this.showActivities = dataTypesMap.has("tosca.dataTypes.tmf.milestoneJeopardyData");
162             });
163         });
164         this.dataTypeMap$.subscribe(value => {
165             this.dataTypeMap = value;
166         });
167     }
168
169     private initInputs() {
170         if (!this.operationToUpdate.inputs) {
171             this.operationToUpdate.inputs = new class implements IOperationParamsList {
172                 listToscaDataDefinition: Array<InputOperationParameter> = [];
173             }
174         }
175
176         this.inputs = Array.from(this.operationToUpdate.inputs.listToscaDataDefinition);
177         this.removeImplementationQuote();
178         this.loadInterfaceOperationImplementation();
179         this.loadInterfaceType();
180     }
181
182     private initCustomToscaFunctions() {
183         this.customToscaFunctions = [];
184         this.topologyTemplateService.getDefaultCustomFunction().toPromise().then((data) => {
185             if (data) {
186                 for (let customFunction of data) {
187                     this.customToscaFunctions.push(new CustomToscaFunction(customFunction));
188                 }
189             }
190         });
191     }
192
193     private loadInterfaceType() {
194         this.componentServiceNg2.getInterfaceTypesByModel(this.modelName)
195         .subscribe(response => {
196             if (response) {
197                 this.interfaceOperationMap = new Map<string, Array<string>>();
198                 for (const interfaceType of Object.keys(response).sort()) {
199                     const operationList = response[interfaceType];
200                     operationList.sort();
201                     this.interfaceOperationMap.set(interfaceType, operationList);
202                     const operationDropDownOption: DropDownOption = new DropDownOption(interfaceType);
203                     this.interfaceTypeOptions.push(operationDropDownOption);
204                     if (this.interfaceType == interfaceType) {
205                         this.selectedInterfaceType = operationDropDownOption;
206                     }
207                 }
208                 this.loadInterfaceTypeOperations();
209             }
210         });
211     }
212
213     loadInterfaceTypeOperations() {
214         this.interfaceOperationOptions = new Array<DropDownOption>();
215         const interfaceOperationList = this.interfaceOperationMap.get(this.interfaceType);
216
217         if (interfaceOperationList) {
218             interfaceOperationList.forEach(operationName => {
219                 const operationOption = new DropDownOption(operationName, operationName);
220                 this.interfaceOperationOptions.push(operationOption);
221                 if (this.operationToUpdate.name == operationName) {
222                     this.selectedInterfaceOperation = operationOption
223                 }
224             });
225         }
226
227         this.interfaceOperationDropDown.allOptions = this.interfaceOperationOptions;
228     }
229
230     private loadInterfaceOperationImplementation() {
231         this.toscaArtifactTypes = this.input.toscaArtifactTypes;
232         if (this.operationToUpdate.implementation) {
233             this.artifactVersion = this.operationToUpdate.implementation.artifactVersion;
234             this.artifactName = this.operationToUpdate.implementation.artifactName;
235             this.toscaArtifactTypeProperties = this.operationToUpdate.implementation.properties;
236         }
237         this.artifactTypeProperties = this.convertArtifactsPropertiesToInput();
238         this.getArtifactTypesSelected();
239     }
240
241     onDescriptionChange = (value: any): void => {
242         this.operationToUpdate.description = value;
243     }
244
245     onURIChange(value: string | undefined) {
246         if(!this.operationToUpdate.implementation){
247             let artifact = new ArtifactModel();
248             this.operationToUpdate.implementation = artifact;
249         }
250         this.operationToUpdate.implementation.artifactName = value ? value : '';
251     }
252
253     onPropertyValueChange = (propertyValue) => {
254         this.emitter.emit(propertyValue);
255     }
256
257     onMarkToAddArtifactToImplementation(event: boolean) {
258         if (!event) {
259             this.toscaArtifactTypeSelected = undefined;
260             this.artifactVersion = undefined;
261             if (this.operationToUpdate.implementation.artifactType) {
262                 this.operationToUpdate.implementation.artifactVersion = '';
263                 this.operationToUpdate.implementation.artifactType = '';
264             }
265             this.toscaArtifactTypeProperties = undefined;
266             this.artifactTypeProperties = undefined;
267         } else {
268             this.getArtifactTypesSelected();
269         }
270         this.enableAddArtifactImplementation = event;
271     }
272
273     onSelectToscaArtifactType(type: IDropDownOption) {
274         if (type) {
275             let toscaArtifactType = type.value;
276             let artifact = new ArtifactModel();
277             artifact.artifactName = this.operationToUpdate.implementation.artifactName;
278             artifact.artifactVersion = this.operationToUpdate.implementation.artifactVersion;
279             artifact.artifactType = toscaArtifactType.type;
280             artifact.properties = toscaArtifactType.properties;
281             this.toscaArtifactTypeProperties = artifact.properties;
282             this.artifactTypeProperties = this.convertArtifactsPropertiesToInput();
283             this.toscaArtifactTypeSelected = artifact.artifactType;
284             this.operationToUpdate.implementation = artifact;
285             this.getArtifactTypesSelected();
286         }
287     }
288
289     onArtifactVersionChange(value: string | undefined) {
290             this.operationToUpdate.implementation.artifactVersion = value ? value : '';
291     }
292
293     onAddInput(inputOperationParameter: InputOperationParameter) {
294         this.addInput(inputOperationParameter);
295     }
296
297     propertyValueValidation = (propertyValue): void => {
298         this.onPropertyValueChange(propertyValue);
299         this.propertyValueValid = propertyValue.isValid;
300     }
301
302     onRemoveInput = (inputParam: InputOperationParameter): void => {
303         let index = this.inputs.indexOf(inputParam);
304         this.inputs.splice(index, 1);
305     }
306
307     timeoutConversion = (): void => {
308         let timeout = this.timeoutValue.value;
309         if (timeout != null) {
310             if (this.timeoutType.value == null || this.timeoutType.value == 'sec') {
311                 this.operationToUpdate.implementation.timeout = timeout;
312                 return;
313             }
314             if (this.timeoutType.value == 'hour') {
315                 this.operationToUpdate.implementation.timeout = timeout * 3600;
316             } else if (this.timeoutType.value == 'day') {
317                 this.operationToUpdate.implementation.timeout = (timeout * 24) * 3600;
318             }
319         }
320     }
321
322     private removeImplementationQuote(): void {
323         if (this.operationToUpdate.implementation) {
324             if (!this.operationToUpdate.implementation
325                 || !this.operationToUpdate.implementation.artifactName) {
326                 return;
327             }
328
329             let implementation = this.operationToUpdate.implementation.artifactName.trim();
330
331             if (implementation.startsWith("'") && implementation.endsWith("'")) {
332                 this.operationToUpdate.implementation.artifactName = implementation.slice(1, -1);
333             }
334         }
335     }
336
337     private getArtifactTypesSelected() {
338         if (this.operationToUpdate.implementation && this.operationToUpdate.implementation.artifactType) {
339             this.artifactName =
340                 this.artifactName ? this.artifactName : this.operationToUpdate.implementation.artifactName;
341             this.toscaArtifactTypeSelected = this.operationToUpdate.implementation.artifactType;
342             this.artifactVersion =
343                 this.artifactVersion ? this.artifactVersion : this.operationToUpdate.implementation.artifactVersion;
344             this.toscaArtifactTypeProperties = this.operationToUpdate.implementation.properties;
345             this.artifactTypeProperties = this.convertArtifactsPropertiesToInput();
346             this.enableAddArtifactImplementation = true;
347         }
348     }
349
350     toDropDownOption(val: string) {
351         return { value : val, label: val };
352     }
353
354     /**
355      * Handles the input value change event.
356      * @param changedInput the changed input
357      */
358     onInputValueChange(changedInput: InputOperationParameter) {
359         if (changedInput.value instanceof Object) {
360             changedInput.value = JSON.stringify(changedInput.value);
361         }
362         const inputOperationParameter = this.inputs.find(value => value.name == changedInput.name);
363         inputOperationParameter.toscaFunction = null;
364         inputOperationParameter.value = changedInput.value;
365         inputOperationParameter.subPropertyToscaFunctions = changedInput.subPropertyToscaFunctions;
366         if (changedInput.isToscaFunction()) {
367             inputOperationParameter.toscaFunction = changedInput.toscaFunction;
368             inputOperationParameter.value = changedInput.toscaFunction.buildValueString();
369         }
370     }
371
372     onArtifactPropertyValueChange(changedProperty: InputOperationParameter) {
373         const property = this.toscaArtifactTypeProperties.find(artifactProperty => artifactProperty.name == changedProperty.name);
374         if (changedProperty.value instanceof Object) {
375             changedProperty.value = JSON.stringify(changedProperty.value);
376         }
377         property.toscaFunction = null;
378         property.value = changedProperty.value;
379         if (changedProperty.isToscaFunction()) {
380             property.toscaFunction = changedProperty.toscaFunction;
381             property.value = changedProperty.toscaFunction.buildValueString();
382         }
383     }
384
385     implementationPropsValidityChange(validImplementationProps: boolean) {
386         this.validImplementationProps = validImplementationProps;
387     }
388
389     /**
390      * Handles the add input event.
391      * @param input the input to add
392      * @private
393      */
394     private addInput(input: InputOperationParameter) {
395         this.operationToUpdate.inputs.listToscaDataDefinition.push(input);
396         this.inputs = Array.from(this.operationToUpdate.inputs.listToscaDataDefinition);
397     }
398
399     /**
400      * Return a list with current input names.
401      */
402     collectInputNames() {
403         return this.inputs.map((input) => input.name);
404     }
405
406     /**
407      * Handles the delete input event.
408      * @param inputName the name of the input to be deleted
409      */
410     onInputDelete(inputName: string) {
411         const currentInputs = this.operationToUpdate.inputs.listToscaDataDefinition;
412         const input1 = currentInputs.find(value => value.name === inputName);
413         const indexOfInput = currentInputs.indexOf(input1);
414         if (indexOfInput === -1) {
415             console.error(`Could not delete input '${inputName}'. Input not found.`);
416             return;
417         }
418         currentInputs.splice(currentInputs.indexOf(input1), 1);
419         this.inputs = Array.from(currentInputs);
420     }
421
422     private convertArtifactsPropertiesToInput(): Array<InputOperationParameter> {
423         if (!this.toscaArtifactTypeProperties) {
424             return [];
425         }
426         const inputList: Array<InputOperationParameter> = [];
427         this.toscaArtifactTypeProperties.forEach(property => {
428             const input = new InputOperationParameter();
429             input.name = property.name;
430             input.type = property.type;
431             input.schema = property.schema;
432             input.toscaDefaultValue = property.defaultValue;
433             input.value = property.value;
434             input.toscaFunction = property.toscaFunction;
435             inputList.push(input);
436         });
437         return inputList;
438     }
439
440     onSelectInterface(dropDownOption: DropDownOption) {
441         if (dropDownOption) {
442             this.setInterfaceType(dropDownOption);
443         } else {
444             this.setInterfaceType(undefined);
445         }
446         this.setInterfaceOperation(undefined);
447         this.interfaceOperationDropDown.selectOption({} as IDropDownOption);
448         this.loadInterfaceTypeOperations();
449     }
450
451     onSelectOperation(dropDownOption: DropDownOption) {
452         if (this.selectedInterfaceType && dropDownOption) {
453             this.setInterfaceOperation(dropDownOption);
454         }
455     }
456
457     private setInterfaceType(dropDownOption: DropDownOption) {
458         this.selectedInterfaceType = dropDownOption ? dropDownOption : undefined;
459         this.interfaceType = dropDownOption ? dropDownOption.value : undefined;
460         this.operationToUpdate.interfaceType = dropDownOption ? dropDownOption.value : undefined;
461         this.operationToUpdate.interfaceId = dropDownOption ? dropDownOption.value : undefined;
462     }
463
464     private setInterfaceOperation(dropDownOption: DropDownOption) {
465         this.operationToUpdate.name = dropDownOption ? dropDownOption.value : undefined;
466         this.operationToUpdate.operationType = dropDownOption ? dropDownOption.value : undefined;
467         this.selectedInterfaceOperation = dropDownOption ? dropDownOption : undefined;
468     }
469
470     getExistingFilters(key: string) {
471         if (this.operationToUpdate.milestones[key] && this.operationToUpdate.milestones[key].filters) {
472             return this.operationToUpdate.milestones[key].filters
473         }
474         return undefined;
475     }
476
477     filtersChangeEvent($event: any, milestone: string) {
478         if ($event.valid) {
479             if (this.invalidMilestones.indexOf(milestone) > -1) {
480                 this.invalidMilestones.splice(this.invalidMilestones.indexOf(milestone), 1);
481                 this.validMilestoneFilters = this.invalidMilestones.length < 1;
482                 this.validMilestoneActivities = this.invalidMilestones.length < 1;
483             }
484             let operationMilestone = this.operationToUpdate.milestones[milestone];
485             if (!operationMilestone) {
486                 operationMilestone = new Milestone();
487             }
488             operationMilestone.filters = new class implements IFilterParameterList {
489                 listToscaDataDefinition: Array<FilterParameter> = [];
490             }
491             let milestoneFilters = $event.filters;
492             for (let filter of milestoneFilters) {
493                 let filterParameter = new FilterParameter();
494                 filterParameter.constraint = filter.constraint;
495                 filterParameter.name = filter.name;
496                 filterParameter.filterValue = filter.filterValue;
497                 filterParameter.toscaFunction = filter.toscaFunction;
498                 operationMilestone.filters.listToscaDataDefinition.push(filterParameter);
499             }
500             this.operationToUpdate.milestones[milestone] = operationMilestone;
501         } else {
502             if (this.invalidMilestones.indexOf(milestone) == -1) {
503                 this.invalidMilestones.push(milestone);
504             }
505             this.validMilestoneFilters = false;
506             this.validMilestoneActivities = false;
507         }
508     }
509
510     getExistingActivities(key: string) {
511         if (
512             this.operationToUpdate.milestones[key]
513             && this.operationToUpdate.milestones[key].activities
514             && this.operationToUpdate.milestones[key].activities.listToscaDataDefinition
515             && this.operationToUpdate.milestones[key].activities.listToscaDataDefinition.length > 0
516         ) {
517             return this.operationToUpdate.milestones[key].activities
518         }
519         return undefined;
520     }
521
522     activitiesChangeEvent($event: any, milestone: string) {
523         if ($event.valid) {
524             if (this.invalidMilestones.indexOf(milestone) > -1) {
525                 this.invalidMilestones.splice(this.invalidMilestones.indexOf(milestone), 1);
526                 this.validMilestoneActivities = this.invalidMilestones.length < 1;
527                 this.validMilestoneFilters  = this.invalidMilestones.length < 1;
528             }
529             let operationMilestone = this.operationToUpdate.milestones[milestone];
530             if (!operationMilestone) {
531                 operationMilestone = new Milestone();
532             }
533             operationMilestone.activities = new class implements IActivityParameterList {
534                 listToscaDataDefinition: Array<ActivityParameter> = [];
535             }
536             let milestoneActivities = $event.activities;
537             for (let activity of milestoneActivities) {
538                 let activityParameter = new ActivityParameter();
539                 activityParameter.type = activity.type;
540                 activityParameter.workflow = activity.workflow;
541                 activityParameter.inputs = activity.inputs;
542                 operationMilestone.activities.listToscaDataDefinition.push(activityParameter);
543             }
544             this.operationToUpdate.milestones[milestone] = operationMilestone;
545         } else {
546             if (this.invalidMilestones.indexOf(milestone) == -1) {
547                 this.invalidMilestones.push(milestone);
548             }
549             this.validMilestoneActivities = false;
550             this.validMilestoneFilters = false;
551         }
552     }
553
554     isActiveTab(title: string): boolean {
555         if (this.activeTab) {
556             return this.activeTab == title;
557         }
558         return this.milestones[0] == title;
559     }
560
561     tabChanged = (event) => {
562         this.activeTab = event.title;
563       }
564
565     isInvalidActivity(title: string) {
566         if (this.invalidMilestones.indexOf(title) > -1) {
567             return "#cf2a2a";
568         }
569       }
570 }
571
572 class DropDownOption implements IDropDownOption {
573     value: string;
574     label: string;
575
576     constructor(value: string, label?: string) {
577         this.value = value;
578         this.label = label || value;
579     }
580 }