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