90d6a6cfdee43e48d234064855dfebcdd09136d3
[sdc.git] / catalog-ui / src / app / ng2 / pages / interface-definition / interface-definition.page.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 {Component, ComponentRef, Inject, Input} from '@angular/core';
22 import {Component as IComponent} from 'app/models/components/component';
23 import {WorkflowServiceNg2} from 'app/ng2/services/workflow.service';
24
25 import {ISdcConfig, SdcConfigToken} from "app/ng2/config/sdc-config.config";
26 import {TranslateService} from "app/ng2/shared/translator/translate.service";
27 import {IModalButtonComponent, SdcUiServices} from 'onap-ui-angular';
28 import {ModalComponent} from 'app/ng2/components/ui/modal/modal.component';
29
30 import {ModalService} from 'app/ng2/services/modal.service';
31 import {
32     ArtifactModel,
33     ButtonModel,
34     CapabilitiesGroup,
35     InputBEModel,
36     InterfaceModel,
37     ModalModel,
38     OperationModel,
39     WORKFLOW_ASSOCIATION_OPTIONS
40 } from 'app/models';
41
42 import {ComponentServiceNg2} from 'app/ng2/services/component-services/component.service';
43 import {TopologyTemplateService} from "../../services/component-services/topology-template.service";
44 import {InterfaceOperationModel} from "../../../models/interfaceOperation";
45 import {InterfaceOperationHandlerComponent} from "../composition/interface-operatons/operation-creator/interface-operation-handler.component";
46 import {DropdownValue} from "../../components/ui/form-components/dropdown/ui-element-dropdown.component";
47 import {ToscaArtifactModel} from "../../../models/toscaArtifact";
48 import {ToscaArtifactService} from "../../services/tosca-artifact.service";
49 import {InterfaceOperationComponent} from "../interface-operation/interface-operation.page.component";
50 import {Observable} from "rxjs/Observable";
51 import {PluginsService} from 'app/ng2/services/plugins.service';
52
53 export class UIOperationModel extends OperationModel {
54     isCollapsed: boolean = true;
55     isEllipsis: boolean;
56     MAX_LENGTH = 75;
57
58     constructor(operation: OperationModel) {
59         super(operation);
60         if (!operation.description) {
61             this.description = '';
62         }
63
64         if (this.description.length > this.MAX_LENGTH) {
65             this.isEllipsis = true;
66         } else {
67             this.isEllipsis = false;
68         }
69     }
70
71     getDescriptionEllipsis(): string {
72         if (this.isCollapsed && this.description.length > this.MAX_LENGTH) {
73             return this.description.substr(0, this.MAX_LENGTH - 3) + '...';
74         }
75         return this.description;
76     }
77
78     toggleCollapsed(e) {
79         e.stopPropagation();
80         this.isCollapsed = !this.isCollapsed;
81     }
82 }
83
84 class ModalTranslation {
85     CREATE_TITLE: string;
86     EDIT_TITLE: string;
87     DELETE_TITLE: string;
88     CANCEL_BUTTON: string;
89     SAVE_BUTTON: string;
90     CREATE_BUTTON: string;
91     DELETE_BUTTON: string;
92     deleteText: Function;
93
94     constructor(private TranslateService: TranslateService) {
95         this.TranslateService.languageChangedObservable.subscribe(lang => {
96             this.CREATE_TITLE = this.TranslateService.translate("INTERFACE_CREATE_TITLE");
97             this.EDIT_TITLE = this.TranslateService.translate('INTERFACE_EDIT_TITLE');
98             this.DELETE_TITLE = this.TranslateService.translate("INTERFACE_DELETE_TITLE");
99             this.CANCEL_BUTTON = this.TranslateService.translate("INTERFACE_CANCEL_BUTTON");
100             this.SAVE_BUTTON = this.TranslateService.translate("INTERFACE_SAVE_BUTTON");
101             this.CREATE_BUTTON = this.TranslateService.translate("INTERFACE_CREATE_BUTTON");
102             this.DELETE_BUTTON = this.TranslateService.translate("INTERFACE_DELETE_BUTTON");
103             this.deleteText = (operationName) => this.TranslateService.translate("INTERFACE_DELETE_TEXT", {operationName});
104         });
105     }
106 }
107
108 export class UIInterfaceModel extends InterfaceModel {
109     isCollapsed: boolean = false;
110
111     constructor(interf?: any) {
112         super(interf);
113         if (this.operations) {
114             this.operations = this.operations.map((operation) => new UIOperationModel(operation));
115         }
116     }
117
118     toggleCollapse() {
119         this.isCollapsed = !this.isCollapsed;
120     }
121 }
122
123 @Component({
124     selector: 'interface-definition',
125     templateUrl: './interface-definition.page.component.html',
126     styleUrls: ['interface-definition.page.component.less'],
127     providers: [ModalService, TranslateService, InterfaceOperationComponent]
128 })
129 export class InterfaceDefinitionComponent {
130
131     modalInstance: ComponentRef<ModalComponent>;
132     interfaces: UIInterfaceModel[];
133     inputs: InputBEModel[];
134
135     deploymentArtifactsFilePath: Array<DropdownValue> = [];
136
137     toscaArtifactTypes: Array<DropdownValue> = [];
138     interfaceTypesTest: Array<DropdownValue> = [];
139     interfaceTypesMap: Map<string, string[]>;
140
141     isLoading: boolean;
142     interfaceTypes: { [interfaceType: string]: string[] };
143     modalTranslation: ModalTranslation;
144     workflows: any[];
145     capabilities: CapabilitiesGroup;
146
147     openOperation: OperationModel;
148     enableWorkflowAssociation: boolean;
149     workflowIsOnline: boolean;
150     validImplementationProps:boolean = true;
151
152     @Input() component: IComponent;
153     @Input() readonly: boolean;
154     @Input() enableMenuItems: Function;
155     @Input() disableMenuItems: Function;
156
157     constructor(
158         @Inject(SdcConfigToken) private sdcConfig: ISdcConfig,
159         @Inject("$state") private $state: ng.ui.IStateService,
160         @Inject("Notification") private notification: any,
161         private translateService: TranslateService,
162         private componentServiceNg2: ComponentServiceNg2,
163         private modalServiceNg2: ModalService,
164         private modalServiceSdcUI: SdcUiServices.ModalService,
165         private topologyTemplateService: TopologyTemplateService,
166         private toscaArtifactService: ToscaArtifactService,
167         private ComponentServiceNg2: ComponentServiceNg2,
168         private WorkflowServiceNg2: WorkflowServiceNg2,
169         private ModalServiceSdcUI: SdcUiServices.ModalService,
170         private PluginsService: PluginsService
171     ) {
172         this.modalTranslation = new ModalTranslation(translateService);
173         this.interfaceTypesMap = new Map<string, string[]>();
174     }
175
176     ngOnInit(): void {
177         this.isLoading = true;
178         this.interfaces = [];
179         this.workflowIsOnline = !_.isUndefined(this.PluginsService.getPluginByStateUrl('workflowDesigner'));
180         Observable.forkJoin(
181             this.ComponentServiceNg2.getInterfaceOperations(this.component),
182             this.ComponentServiceNg2.getComponentInputs(this.component),
183             this.ComponentServiceNg2.getInterfaceTypes(this.component),
184             this.ComponentServiceNg2.getCapabilitiesAndRequirements(this.component.componentType, this.component.uniqueId)
185         ).subscribe((response: any[]) => {
186             const callback = (workflows) => {
187                 this.isLoading = false;
188                 this.initInterfaces(response[0].interfaces);
189                 this.sortInterfaces();
190                 this.inputs = response[1].inputs;
191                 this.interfaceTypes = response[2];
192                 this.workflows = (workflows.items) ? workflows.items : workflows;
193                 this.capabilities = response[3].capabilities;
194             };
195             if (this.enableWorkflowAssociation && this.workflowIsOnline) {
196                 this.WorkflowServiceNg2.getWorkflows().subscribe(
197                     callback,
198                     (err) => {
199                         this.workflowIsOnline = false;
200                         callback([]);
201                     }
202                 );
203             } else {
204                 callback([]);
205             }
206         });
207         this.loadToscaArtifacts();
208     }
209
210     initInterfaces(interfaces: InterfaceModel[]): void {
211         if (interfaces) {
212             this.interfaces = interfaces.map((interf) => new UIInterfaceModel(interf));
213         }
214     }
215
216     private cancelAndCloseModal = () => {
217         return this.modalServiceNg2.closeCurrentModal();
218     }
219
220     private disableSaveButton = (): boolean => {
221         let disable:boolean = true;
222         if(this.readonly) {
223             return disable;
224         }
225     
226         let selectedInterfaceOperation = this.modalInstance.instance.dynamicContent.instance.selectedInterfaceOperation;
227         let isInterfaceOperation:boolean = !(typeof selectedInterfaceOperation == 'undefined' || _.isEmpty(selectedInterfaceOperation));
228         let selectedInterfaceType = this.modalInstance.instance.dynamicContent.instance.selectedInterfaceType;
229         let isInterfaceType:boolean = !(typeof selectedInterfaceType == 'undefined' || _.isEmpty(selectedInterfaceType));
230         let bothSet: boolean = isInterfaceOperation && isInterfaceType;
231     
232         let enableAddArtifactImplementation = this.modalInstance.instance.dynamicContent.instance.enableAddArtifactImplementation;
233         if(enableAddArtifactImplementation) {
234             let validImplementationProps = this.modalInstance.instance.dynamicContent.instance.validImplementationProps;
235             let toscaArtifactTypeSelected = this.modalInstance.instance.dynamicContent.instance.toscaArtifactTypeSelected;
236             let isToscaArtifactType:boolean = !(typeof toscaArtifactTypeSelected == 'undefined' || _.isEmpty(toscaArtifactTypeSelected));
237             disable = !bothSet || !isToscaArtifactType || !validImplementationProps;
238             return disable;
239         }
240         disable = !bothSet;
241         return disable;
242     }
243
244     onSelectInterfaceOperation(interfaceModel: UIInterfaceModel, operation: InterfaceOperationModel) {
245         const isEdit = operation !== undefined;
246         const modalButtons = [];
247         if (!this.readonly) {
248             const saveButton: ButtonModel = new ButtonModel(this.modalTranslation.SAVE_BUTTON, 'blue',
249                 () => isEdit ? this.updateOperation() : this.createOperationCallback(),
250                 this.disableSaveButton
251             );
252             modalButtons.push(saveButton);
253         }
254         modalButtons.push(new ButtonModel(this.modalTranslation.CANCEL_BUTTON, 'outline white', this.cancelAndCloseModal));
255         const interfaceDataModal: ModalModel =
256             new ModalModel('l', this.modalTranslation.EDIT_TITLE, '', modalButtons, 'custom');
257         this.modalInstance = this.modalServiceNg2.createCustomModal(interfaceDataModal);
258
259         this.modalServiceNg2.addDynamicContentToModal(
260             this.modalInstance,
261             InterfaceOperationHandlerComponent,
262             {
263                 deploymentArtifactsFilePath: this.deploymentArtifactsFilePath,
264                 toscaArtifactTypes: this.toscaArtifactTypes,
265                 selectedInterface: interfaceModel ? interfaceModel : new UIInterfaceModel(),
266                 selectedInterfaceOperation: operation ? operation : new InterfaceOperationModel(),
267                 validityChangedCallback: this.disableSaveButton,
268                 isViewOnly: this.readonly,
269                 validImplementationProps: this.validImplementationProps,
270                 'isEdit': isEdit,
271                 interfaceTypesMap: this.interfaceTypesMap,
272                 modelName: this.component.model
273             }
274         );
275         this.modalInstance.instance.open();
276     }
277
278     private updateOperation = (): void => {
279         this.modalServiceNg2.currentModal.instance.dynamicContent.instance.isLoading = true;
280         const interfaceOperationHandlerComponentInstance: InterfaceOperationHandlerComponent = this.modalInstance.instance.dynamicContent.instance;
281         const operationToUpdate = this.modalInstance.instance.dynamicContent.instance.operationToUpdate;
282         let timeout = null;
283         if (operationToUpdate.implementation && operationToUpdate.implementation.timeout != null) {
284             timeout = operationToUpdate.implementation.timeout;
285         }
286         const isArtifactChecked = interfaceOperationHandlerComponentInstance.enableAddArtifactImplementation;
287         if (!isArtifactChecked) {
288             const artifactName = interfaceOperationHandlerComponentInstance.artifactName ?
289                 interfaceOperationHandlerComponentInstance.artifactName : '';
290             operationToUpdate.implementation = new ArtifactModel({'artifactName': artifactName, 'artifactVersion': ''} as ArtifactModel);
291         }
292         if (timeout != null) {
293             operationToUpdate.implementation.timeout = timeout;
294         }
295         this.componentServiceNg2.updateComponentInterfaceOperation(this.component.uniqueId, operationToUpdate)
296         .subscribe((newOperation: InterfaceOperationModel) => {
297             let oldOpIndex;
298             let oldInterf;
299             this.interfaces.forEach(interf => {
300                 interf.operations.forEach(op => {
301                     if (op.uniqueId === newOperation.uniqueId) {
302                         oldInterf = interf;
303                         oldOpIndex = interf.operations.findIndex((el) => el.uniqueId === op.uniqueId);
304                     }
305                 });
306             });
307             oldInterf.operations.splice(oldOpIndex, 1);
308             oldInterf.operations.push(new InterfaceOperationModel(newOperation));
309         }, error => {
310             this.modalServiceNg2.currentModal.instance.dynamicContent.instance.isLoading = false;
311         }, () => {
312             this.sortInterfaces();
313             this.modalServiceNg2.currentModal.instance.dynamicContent.instance.isLoading = false;
314             this.modalServiceNg2.closeCurrentModal();
315         });
316     }
317
318     private createOperationCallback(): void {
319         this.modalServiceNg2.currentModal.instance.dynamicContent.instance.isLoading = true;
320         const operationToUpdate = this.modalInstance.instance.dynamicContent.instance.operationToUpdate;
321         console.log('createOperationCallback', operationToUpdate);
322         console.log('this.component', this.component);
323         this.componentServiceNg2.createComponentInterfaceOperation(this.component.uniqueId, this.component.getTypeUrl(), operationToUpdate)
324         .subscribe((newOperation: InterfaceOperationModel) => {
325             const foundInterface = this.interfaces.find(value => value.type === newOperation.interfaceType);
326             if (foundInterface) {
327                 foundInterface.operations.push(new UIOperationModel(new OperationModel(newOperation)));
328             } else {
329                 const uiInterfaceModel = new UIInterfaceModel();
330                 uiInterfaceModel.type = newOperation.interfaceType;
331                 uiInterfaceModel.uniqueId = newOperation.interfaceType;
332                 uiInterfaceModel.operations = [];
333                 uiInterfaceModel.operations.push(new UIOperationModel(new OperationModel(newOperation)));
334                 this.interfaces.push(uiInterfaceModel);
335             }
336         }, error => {
337             this.modalServiceNg2.currentModal.instance.dynamicContent.instance.isLoading = false;
338         }, () => {
339             this.modalServiceNg2.currentModal.instance.dynamicContent.instance.isLoading = false;
340             this.modalServiceNg2.closeCurrentModal();
341         });
342     }
343
344     private handleEnableAddArtifactImplementation = (newOperation: InterfaceOperationModel): InterfaceOperationModel => {
345         if (!this.isEnableAddArtifactImplementation()) {
346             newOperation.implementation.artifactType = null;
347             newOperation.implementation.artifactVersion = null;
348         }
349         return newOperation;
350     }
351
352     private isEnableAddArtifactImplementation = (): boolean => {
353         return this.modalInstance.instance.dynamicContent.enableAddArtifactImplementation;
354     }
355
356     private initInterfaceDefinition() {
357         this.isLoading = true;
358         this.interfaces = [];
359         this.topologyTemplateService.getComponentInterfaceOperations(this.component.componentType, this.component.uniqueId)
360         .subscribe((response) => {
361             if (response.interfaces) {
362                 this.interfaces = response.interfaces.map((interfaceModel) => new UIInterfaceModel(interfaceModel));
363             }
364             this.isLoading = false;
365         });
366     }
367
368     private loadToscaArtifacts() {
369         this.toscaArtifactService.getToscaArtifacts(this.component.model).subscribe(response => {
370             if (response) {
371                 let toscaArtifactsFound = <ToscaArtifactModel[]>_.values(response);
372                 toscaArtifactsFound.forEach(value => this.toscaArtifactTypes.push(new DropdownValue(value, value.type)));
373             }
374         }, error => {
375             this.notification.error({
376                 message: 'Failed to Load Tosca Artifacts:' + error,
377                 title: 'Failure'
378             });
379         });
380     }
381
382     private loadInterfaceTypes() {
383         this.componentServiceNg2.getInterfaceTypes(this.component).subscribe(response => {
384             if (response) {
385                 console.info("loadInterfaceTypes ", response);
386                 for (const interfaceType in response) {
387                     this.interfaceTypesMap.set(interfaceType, response[interfaceType]);
388                     this.interfaceTypesTest.push(new DropdownValue(interfaceType, interfaceType));
389                 }
390             }
391         }, error => {
392             this.notification.error({
393                 message: 'Failed to Load Interface Types:' + error,
394                 title: 'Failure'
395             });
396         });
397     }
398
399     collapseAll(value: boolean = true): void {
400         this.interfaces.forEach(interfaceData => {
401             interfaceData.isCollapsed = value;
402         });
403     }
404
405     isAllCollapsed(): boolean {
406         return this.interfaces.every((interfaceData) => interfaceData.isCollapsed);
407     }
408
409     isAllExpanded(): boolean {
410         return this.interfaces.every((interfaceData) => !interfaceData.isCollapsed);
411     }
412
413     isInterfaceListEmpty(): boolean {
414         return this.interfaces.length === 0;
415     }
416
417     isOperationListEmpty(): boolean {
418         return this.interfaces.filter((interfaceData) => interfaceData.operations && interfaceData.operations.length > 0).length > 0;
419     }
420
421     onRemoveOperation(operation: OperationModel): void {
422         if (this.readonly) {
423             return;
424         }
425
426         const deleteButton: IModalButtonComponent = {
427             id: 'deleteButton',
428             text: this.modalTranslation.DELETE_BUTTON,
429             type: 'primary',
430             size: 'small',
431             closeModal: true,
432             callback: () => {
433                 this.ComponentServiceNg2
434                 .deleteInterfaceOperation(this.component, operation)
435                 .subscribe(() => {
436                     const curInterf = this.interfaces.find((interf) => interf.type === operation.interfaceType);
437                     const index = curInterf.operations.findIndex((el) => el.uniqueId === operation.uniqueId);
438                     curInterf.operations.splice(index, 1);
439                     if (!curInterf.operations.length) {
440                         const interfIndex = this.interfaces.findIndex((interf) => interf.type === operation.interfaceType);
441                         this.interfaces.splice(interfIndex, 1);
442                     }
443                 });
444             }
445         };
446
447         const cancelButton: IModalButtonComponent = {
448             id: 'cancelButton',
449             text: this.modalTranslation.CANCEL_BUTTON,
450             type: 'secondary',
451             size: 'small',
452             closeModal: true,
453             callback: () => {
454                 this.openOperation = null;
455             },
456         };
457
458         this.ModalServiceSdcUI.openWarningModal(
459             this.modalTranslation.DELETE_TITLE,
460             this.modalTranslation.deleteText(operation.name),
461             'deleteOperationModal',
462             [deleteButton, cancelButton],
463         );
464     }
465
466     private createOperation = (operation: OperationModel): void => {
467         this.ComponentServiceNg2.createInterfaceOperation(this.component, operation).subscribe((response: OperationModel) => {
468             this.openOperation = null;
469
470             let curInterf = this.interfaces.find((interf) => interf.type === operation.interfaceType);
471
472             if (!curInterf) {
473                 curInterf = new UIInterfaceModel({
474                     type: response.interfaceType,
475                     uniqueId: response.uniqueId,
476                     operations: []
477                 });
478                 this.interfaces.push(curInterf);
479             }
480
481             const newOpModel = new UIOperationModel(response);
482             curInterf.operations.push(newOpModel);
483             this.sortInterfaces();
484
485             if (operation.workflowAssociationType === WORKFLOW_ASSOCIATION_OPTIONS.EXTERNAL && operation.artifactData) {
486                 this.ComponentServiceNg2.uploadInterfaceOperationArtifact(this.component, newOpModel, operation).subscribe();
487             } else if (response.workflowId && operation.workflowAssociationType === WORKFLOW_ASSOCIATION_OPTIONS.EXISTING) {
488                 this.WorkflowServiceNg2.associateWorkflowArtifact(this.component, response).subscribe();
489             } else if (operation.workflowAssociationType === WORKFLOW_ASSOCIATION_OPTIONS.NEW) {
490                 this.$state.go('workspace.plugins', {path: 'workflowDesigner'});
491             }
492         });
493     }
494
495     private enableOrDisableSaveButton = (shouldEnable: boolean): void => {
496         const saveButton = this.modalInstance.instance.dynamicContent.getButtonById('saveButton');
497         saveButton.disabled = !shouldEnable;
498     }
499
500     private sortInterfaces(): void {
501         this.interfaces = this.interfaces.filter((interf) => interf.operations && interf.operations.length > 0); // remove empty interfaces
502         this.interfaces.sort((a, b) => a.type.localeCompare(b.type)); // sort interfaces alphabetically
503         this.interfaces.forEach((interf) => {
504             interf.operations.sort((a, b) => a.name.localeCompare(b.name)); // sort operations alphabetically
505         });
506     }
507
508 }