Implement adding Interface to VFC
[sdc.git] / catalog-ui / src / app / ng2 / pages / interface-definition / operation-creator / operation-creator-interface-definition.component.ts
1 /*
2 * ============LICENSE_START=======================================================
3 * SDC
4 * ================================================================================
5 *  Copyright (C) 2022 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 * as _ from "lodash";
22 import {Component, ViewChild} from '@angular/core';
23
24 import {TranslateService} from "app/ng2/shared/translator/translate.service";
25 import {WorkflowServiceNg2} from 'app/ng2/services/workflow.service';
26 import {
27     Capability,
28     InputBEModel,
29     InterfaceModel,
30     OperationModel,
31     OperationParameter,
32     WORKFLOW_ASSOCIATION_OPTIONS
33 } from 'app/models';
34
35 import {Tabs} from "app/ng2/components/ui/tabs/tabs.component";
36 import {
37     DropdownValue
38 } from "app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component";
39 import {IDropDownOption} from 'onap-ui-angular';
40 import {DropDownComponent} from "onap-ui-angular/dist/components";
41 import {DROPDOWN_OPTION_TYPE} from "app/utils";
42 import {Subscription} from "rxjs";
43
44 export class DropDownOption implements IDropDownOption {
45     value: string;
46     label: string;
47
48     constructor(value: string, label?: string) {
49         this.value = value;
50         this.label = label || value;
51     }
52 }
53
54 class TypedDropDownOption extends DropDownOption {
55     type: string;
56
57     constructor(value: string, label?: string, type?: string) {
58         super(value, label);
59         this.type = type;
60     }
61 }
62
63 export interface OperationCreatorInput {
64     allWorkflows: Array<any>,
65     inputOperation: OperationModel,
66     interfaces: Array<InterfaceModel>,
67     inputProperties: Array<InputBEModel>,
68     enableWorkflowAssociation: boolean,
69     readonly: boolean,
70     interfaceTypes: { [interfaceType: string]: Array<string> },
71     validityChangedCallback: Function,
72     workflowIsOnline: boolean,
73     capabilities: Array<Capability>
74 }
75
76 @Component({
77     selector: 'operation-creator-interface-definition',
78     templateUrl: './operation-creator-interface-definition.component.html',
79     styleUrls: ['./operation-creator-interface-definition.component.less'],
80     providers: [TranslateService]
81 })
82
83 export class OperationCreatorInterfaceDefinitionComponent implements OperationCreatorInput {
84
85     input: OperationCreatorInput;
86     inputOperation: OperationModel;
87     interfaces: Array<InterfaceModel>;
88     operation: OperationModel;
89     interfaceNames: Array<TypedDropDownOption> = [];
90     interfaceTypes: { [interfaceType: string]: Array<string> };
91     operationNames: Array<TypedDropDownOption> = [];
92     validityChangedCallback: Function;
93     capabilities: Array<Capability>;
94
95     allWorkflows: Array<any>;
96     workflows: Array<DropdownValue> = [];
97     workflowVersions: Array<DropdownValue> = [];
98     inputProperties: Array<InputBEModel> = [];
99     archivedWorkflowId: string = '&';
100
101     inputParameters: Array<OperationParameter> = [];
102     noAssignInputParameters: Array<OperationParameter> = [];
103     assignInputParameters: { [key: string]: { [key: string]: Array<OperationParameter>; }; } = {};
104
105     outputParameters: Array<OperationParameter> = [];
106     noAssignOutputParameters: Array<OperationParameter> = [];
107     assignOutputParameters: { [key: string]: { [key: string]: Array<OperationParameter>; }; } = {};
108     componentCapabilities: Array<Capability> = [];
109
110     tableParameters: Array<OperationParameter> = [];
111     operationOutputs: Array<OperationModel> = [];
112
113     associationOptions: Array<DropdownValue> = [];
114     workflowAssociationType: string;
115
116     enableWorkflowAssociation: boolean;
117     workflowIsOnline: boolean;
118     isEditMode: boolean = false;
119     isLoading: boolean = false;
120     readonly: boolean;
121
122     propertyTooltipText: String;
123
124     TYPE_INPUT = 'Inputs';
125     TYPE_OUTPUT = 'Outputs';
126
127     INTERFACE_OTHER_HEADER = 'Local Interfaces';
128     INTERFACE_OTHER = 'Local';
129
130     @ViewChild('propertyInputTabs') propertyInputTabs: Tabs;
131     @ViewChild('operationNamesDropdown') operationNamesDropdown: DropDownComponent;
132     @ViewChild('workflowAssignmentDropdown') workflowAssignmentDropdown: DropDownComponent;
133     currentTab: String;
134
135     constructor(private workflowServiceNg2: WorkflowServiceNg2, private translateService: TranslateService) {
136         this.translateService.languageChangedObservable.subscribe(lang => {
137             this.propertyTooltipText = this.translateService.translate("OPERATION_PROPERTY_TOOLTIP_TEXT");
138
139             this.associationOptions = [
140                 new DropDownOption(WORKFLOW_ASSOCIATION_OPTIONS.EXTERNAL, this.translateService.translate("EXTERNAL_WORKFLOW_ASSOCIATION")),
141                 new DropDownOption(WORKFLOW_ASSOCIATION_OPTIONS.EXISTING, this.translateService.translate("EXISTING_WORKFLOW_ASSOCIATION")),
142             ];
143
144             this.workflowAssociationType = this.operation.workflowAssociationType;
145         });
146
147         this.currentTab = this.TYPE_INPUT;
148     }
149
150     createInterfaceDropdown(type: string) {
151         let label = type;
152         const lastDot = label.lastIndexOf('.');
153         if (lastDot > -1) {
154             label = label.substr(lastDot + 1);
155         }
156         return new TypedDropDownOption(type, label);
157     }
158
159     ngOnInit() {
160         this.interfaceNames = _.map(
161             _.keys(this.interfaceTypes),
162             type => this.createInterfaceDropdown(type)
163         );
164         this.interfaceNames.unshift(new TypedDropDownOption('Existing Interfaces', 'Existing Interfaces', DROPDOWN_OPTION_TYPE.HEADER));
165         this.interfaceNames = this.interfaceNames.concat([
166             new TypedDropDownOption(' ', ' ', DROPDOWN_OPTION_TYPE.HORIZONTAL_LINE),
167             new TypedDropDownOption(this.INTERFACE_OTHER_HEADER, this.INTERFACE_OTHER_HEADER, DROPDOWN_OPTION_TYPE.HEADER),
168             new TypedDropDownOption(this.INTERFACE_OTHER)
169         ]);
170         const inputOperation = this.inputOperation;
171         this.operation = new OperationModel(inputOperation || {});
172
173         this.operationOutputs = _.reduce(
174             this.interfaces,
175             (acc: Array<OperationModel>, interf) => [
176                 ...acc,
177                 ..._.filter(
178                     interf.operations,
179                     op => op.uniqueId !== this.operation.uniqueId
180                 ),
181             ],
182             []);
183
184         if (this.enableWorkflowAssociation) {
185             if (this.workflowIsOnline) {
186                 this.workflows = _.map(
187                     _.filter(
188                         this.allWorkflows,
189                         (workflow: any) => {
190                             if (workflow.archiving === this.workflowServiceNg2.WF_STATE_ACTIVE) {
191                                 return true;
192                             }
193                             if (workflow.archiving === this.workflowServiceNg2.WF_STATE_ARCHIVED &&
194                                 workflow.id === this.operation.workflowId) {
195                                 this.archivedWorkflowId = workflow.id;
196                                 return true;
197                             }
198                             return false;
199                         }
200                     ),
201                     (workflow: any) => new DropdownValue(workflow.id, workflow.name)
202                 );
203             } else {
204                 this.workflows = [];
205             }
206         }
207         this.reconstructOperation();
208         this.filterCapabilities();
209         this.validityChanged();
210         this.updateTable();
211     }
212
213     ngAfterViewInit() {
214         if (this.workflowAssignmentDropdown) {
215             this.workflowAssignmentDropdown.allOptions = this.associationOptions && this.associationOptions.length ?
216                 this.associationOptions :
217                 [
218                     new DropDownOption(WORKFLOW_ASSOCIATION_OPTIONS.EXTERNAL, this.translateService.translate("EXTERNAL_WORKFLOW_ASSOCIATION")),
219                     new DropDownOption(WORKFLOW_ASSOCIATION_OPTIONS.EXISTING, this.translateService.translate("EXISTING_WORKFLOW_ASSOCIATION")),
220                 ];
221         }
222     }
223
224     reconstructOperation = () => {
225
226         const buildAndUpdate = () => {
227             this.buildParams();
228             this.updateTable();
229         };
230
231         const inputOperation = this.inputOperation;
232         if (inputOperation) {
233             this.onSelectInterface(new DropDownOption(this.operation.interfaceType));
234
235             if (this.enableWorkflowAssociation && inputOperation.workflowVersionId && this.isUsingExistingWF(inputOperation)) {
236                 this.assignInputParameters[this.operation.workflowId] = {[inputOperation.workflowVersionId]: []};
237                 this.assignOutputParameters[this.operation.workflowId] = {[inputOperation.workflowVersionId]: []};
238                 this.inputParameters = this.assignInputParameters[this.operation.workflowId][this.operation.workflowVersionId];
239                 this.outputParameters = this.assignOutputParameters[this.operation.workflowId][this.operation.workflowVersionId];
240
241                 const sub = this.onSelectWorkflow(new DropDownOption(inputOperation.workflowId), inputOperation.workflowVersionId);
242                 if (sub) {
243                     sub.add(() => {
244                         buildAndUpdate();
245                         this.operation.workflowVersionId = '-1';
246                         setTimeout(() => this.operation.workflowVersionId = this.inputOperation.workflowVersionId);
247                     });
248                 } else {
249                     buildAndUpdate();
250                 }
251             } else {
252                 this.inputParameters = this.noAssignInputParameters;
253                 this.outputParameters = this.noAssignOutputParameters;
254                 buildAndUpdate();
255             }
256
257             if (inputOperation.uniqueId) {
258                 this.isEditMode = true;
259             }
260         }
261
262     }
263
264     filterCapabilities() {
265         this.componentCapabilities = _.filter(this.capabilities, (cap: Capability) => cap.properties);
266     }
267
268     buildParams = () => {
269
270         if (this.inputOperation.outputs) {
271             this.currentTab = this.TYPE_OUTPUT;
272             this.updateTable();
273             _.forEach(
274                 [...this.inputOperation.outputs.listToscaDataDefinition].sort((a, b) => a.name.localeCompare(b.name)),
275                 (output: OperationParameter) => {
276                     this.addParam({...output, required: Boolean(output.required)});
277                 }
278             );
279         }
280
281         this.currentTab = this.TYPE_INPUT;
282         this.updateTable();
283         if (this.inputOperation.inputs) {
284             _.forEach(
285                 [...this.inputOperation.inputs.listToscaDataDefinition].sort((a, b) => a.name.localeCompare(b.name)),
286                 (input: OperationParameter) => {
287                     this.addParam({...input, required: Boolean(input.required)});
288                 }
289             );
290         }
291
292     }
293
294     isInterfaceOther(): boolean {
295         return this.operation.interfaceType === this.INTERFACE_OTHER;
296     }
297
298     onSelectInterface(interf: IDropDownOption) {
299         if (interf && this.operation.interfaceType !== interf.value) {
300             this.operation.name = null;
301         }
302         this.operation.interfaceType = interf && interf.value;
303         this.operationNames = !this.operation.interfaceType ? [] : (
304             _.map(
305                 this.interfaceTypes[this.operation.interfaceType],
306                 name => {
307                     const curInterf = _.find(
308                         this.interfaces,
309                         interf => interf.type === this.operation.interfaceType
310                     );
311                     const existingOp = _.find(
312                         curInterf && curInterf.operations || [],
313                         op => op.name === name
314                     );
315                     const ddType = (existingOp && existingOp.uniqueId !== this.operation.uniqueId) ? DROPDOWN_OPTION_TYPE.HORIZONTAL_LINE : DROPDOWN_OPTION_TYPE.SIMPLE;
316                     return new TypedDropDownOption(name, name, ddType);
317                 }
318             )
319         );
320         if (this.operationNamesDropdown) {
321             this.operationNamesDropdown.allOptions = <IDropDownOption[]>this.operationNames;
322         }
323         this.validityChanged();
324     }
325
326     onSelectOperationName(name: IDropDownOption) {
327         if (name) {
328             this.operation.name = name.value;
329         }
330         this.validityChanged();
331     }
332
333     onChangeName() {
334         this.validityChanged();
335     }
336
337     get descriptionValue() {
338         return this.operation.description;
339     }
340
341     set descriptionValue(v) {
342         this.operation.description = v || null;
343         this.validityChanged();
344     }
345
346     onSelectWorkflow(workflowId: DropDownOption, selectedVersionId?: string): Subscription {
347
348         if (_.isUndefined(workflowId) || !this.workflowIsOnline) {
349             return;
350         }
351
352         if (this.operation.workflowId === workflowId.value && !selectedVersionId) {
353             return;
354         }
355
356         this.operation.workflowId = workflowId.value;
357         if (!this.assignInputParameters[this.operation.workflowId]) {
358             this.assignInputParameters[this.operation.workflowId] = {};
359             this.assignOutputParameters[this.operation.workflowId] = {};
360         }
361         this.operation.workflowName = workflowId.label;
362         if (!this.assignInputParameters[this.operation.workflowName]) {
363             this.assignInputParameters[this.operation.workflowName] = {};
364             this.assignOutputParameters[this.operation.workflowName] = {};
365         }
366
367         this.isLoading = true;
368         this.validityChanged();
369         return this.workflowServiceNg2.getWorkflowVersions(this.operation.workflowId).subscribe((versions: Array<any>) => {
370             this.isLoading = false;
371
372             this.workflowVersions = _.map(
373                 _.filter(
374                     versions, version => version.state === this.workflowServiceNg2.VERSION_STATE_CERTIFIED
375                 ).sort((a, b) => a.name.localeCompare(b.name)),
376                 (version: any) => {
377                     if (!this.assignInputParameters[this.operation.workflowId][version.id] && version.id !== selectedVersionId) {
378                         this.assignInputParameters[this.operation.workflowId][version.id] = _.map(version.inputs, (input: any) => {
379                             return new OperationParameter({
380                                 ...input,
381                                 type: input.type.toLowerCase(),
382                                 required: Boolean(input.mandatory)
383                             });
384                         })
385                         .sort((a, b) => a.name.localeCompare(b.name));
386
387                         this.assignOutputParameters[this.operation.workflowId][version.id] = _.map(version.outputs, (output: any) => {
388                             return new OperationParameter({
389                                 ...output,
390                                 type: output.type.toLowerCase(),
391                                 required: Boolean(output.mandatory)
392                             });
393                         })
394                         .sort((a, b) => a.name.localeCompare(b.name));
395                     }
396                     return new DropdownValue(version.id, `V ${version.name}`);
397                 }
398             );
399             if (!selectedVersionId && this.workflowVersions.length) {
400                 this.operation.workflowVersionId = _.last(this.workflowVersions).value;
401                 this.operation.workflowVersion = _.last(this.workflowVersions).label;
402             }
403
404             this.changeWorkflowVersion(new DropDownOption(this.operation.workflowVersionId));
405             this.validityChanged();
406         });
407
408     }
409
410     changeWorkflowVersion(versionId: DropDownOption) {
411
412         if (_.isUndefined(versionId) || !this.workflowIsOnline) {
413             return;
414         }
415
416         this.operation.workflowVersionId = versionId.value;
417         this.inputParameters = this.assignInputParameters[this.operation.workflowId][this.operation.workflowVersionId];
418         this.outputParameters = this.assignOutputParameters[this.operation.workflowId][this.operation.workflowVersionId];
419         this.updateTable();
420         this.validityChanged();
421
422     }
423
424     toggleAssociateWorkflow(type: DropDownOption) {
425
426         if (_.isUndefined(type)) {
427             return;
428         }
429
430         this.operation.workflowAssociationType = type.value;
431         this.workflowAssociationType = this.operation.workflowAssociationType;
432
433         if (!this.isUsingExistingWF()) {
434             this.inputParameters = this.noAssignInputParameters;
435             this.outputParameters = this.noAssignOutputParameters;
436         } else {
437             if (!this.operation.workflowId || !this.operation.workflowVersionId) {
438                 this.inputParameters = [];
439                 this.outputParameters = [];
440             } else {
441                 this.inputParameters = this.assignInputParameters[this.operation.workflowId][this.operation.workflowVersionId];
442                 this.outputParameters = this.assignOutputParameters[this.operation.workflowId][this.operation.workflowVersionId];
443             }
444         }
445
446         this.updateTable();
447         this.validityChanged();
448
449     }
450
451     onChangeArtifactFile(e: any) {
452         const file = e.target.files && e.target.files[0];
453         this.operation.artifactFileName = file && file.name;
454
455         if (!this.operation.artifactFileName) {
456             this.operation.artifactData = null;
457             this.validityChanged();
458             return;
459         }
460
461         const reader = new FileReader();
462         reader.onloadend = () => {
463             this.isLoading = false;
464             const result = <String>reader.result;
465             this.operation.artifactData = result.substring(result.indexOf(',') + 1);
466             this.validityChanged();
467         }
468
469         this.isLoading = true;
470         reader.readAsDataURL(file);
471     }
472
473     tabChanged = (event) => {
474
475         this.currentTab = event.title;
476         this.updateTable();
477
478     }
479
480     updateTable() {
481
482         switch (this.currentTab) {
483             case this.TYPE_INPUT:
484                 this.tableParameters = this.inputParameters;
485                 break;
486             case this.TYPE_OUTPUT:
487                 this.tableParameters = this.outputParameters;
488                 break;
489         }
490
491     }
492
493     addParam(param?: OperationParameter): void {
494         this.tableParameters.push(new OperationParameter(param || {required: false}));
495         this.validityChanged();
496     }
497
498     canAdd = (): boolean => {
499
500         let valid = true;
501         if (this.currentTab === this.TYPE_INPUT) {
502             _.forEach(this.inputParameters, param => {
503                 if (!param.name || !param.inputId) {
504                     valid = false;
505                 }
506             });
507         } else {
508             _.forEach(this.outputParameters, param => {
509                 if (!param.name || !param.type) {
510                     valid = false;
511                 }
512             });
513         }
514
515         return valid;
516
517     }
518
519     isParamsValid = (): boolean => {
520
521         let valid = true;
522         _.forEach(this.inputParameters, param => {
523             if (!param.name || !param.inputId) {
524                 valid = false;
525             }
526         });
527         _.forEach(this.outputParameters, param => {
528             if (!param.name || !param.type) {
529                 valid = false;
530             }
531         });
532
533         return valid;
534
535     }
536
537     onRemoveParam = (param: OperationParameter): void => {
538         let index = _.indexOf(this.tableParameters, param);
539         this.tableParameters.splice(index, 1);
540         this.validityChanged();
541     }
542
543     createParamLists = () => {
544         this.operation.createInputsList(this.inputParameters);
545         this.operation.createOutputsList(this.outputParameters);
546     }
547
548     isUsingExistingWF = (operation?: OperationModel): boolean => {
549         operation = operation || this.operation;
550         return operation.workflowAssociationType === WORKFLOW_ASSOCIATION_OPTIONS.EXISTING;
551     }
552
553     isUsingExternalWF = (operation?: OperationModel): boolean => {
554         operation = operation || this.operation;
555         return operation.workflowAssociationType === WORKFLOW_ASSOCIATION_OPTIONS.EXTERNAL;
556     }
557
558     shouldCreateWF = (operation?: OperationModel): boolean => {
559         operation = operation || this.operation;
560         return operation.workflowAssociationType === WORKFLOW_ASSOCIATION_OPTIONS.NEW;
561     }
562
563     checkFormValidForSubmit = (): boolean => {
564         return this.operation.name &&
565             (!this.isUsingExistingWF() || this.operation.workflowVersionId) &&
566             this.isParamsValid();
567     }
568
569     validityChanged = () => {
570         let validState = this.checkFormValidForSubmit();
571         this.validityChangedCallback(validState);
572     }
573
574     getSelectedDropdown(options: DropdownValue[], selectedValue: string): DropdownValue {
575         const selectedDropdown = _.find(options, (option) => option.value === selectedValue);
576         return selectedDropdown || this.toDropDownOption(null);
577     }
578
579     toDropDownOption(val: string) {
580         return {value: val, label: val};
581     }
582 }