UI Support for operation milestones
[sdc.git] / catalog-ui / src / app / ng2 / pages / composition / interface-operatons / interface-operations.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
22 import {Component, ComponentRef, Inject, Input} from '@angular/core';
23 import {
24     TopologyTemplateService
25 } from '../../../services/component-services/topology-template.service';
26 import {TranslateService} from "../../../shared/translator/translate.service";
27 import {ModalService} from 'app/ng2/services/modal.service';
28 import {CompositionService} from "app/ng2/pages/composition/composition.service";
29 import {ModalComponent} from 'app/ng2/components/ui/modal/modal.component';
30 import {Component as TopologyTemplate} from "../../../../models/components/component";
31 import {PluginsService} from "app/ng2/services/plugins.service";
32 import {SelectedComponentType} from "../common/store/graph.actions";
33 import {InstanceFeDetails} from "../../../../models/instance-fe-details";
34 import {WorkspaceService} from "../../workspace/workspace.service";
35 import {
36     ComponentInterfaceDefinitionModel,
37     InterfaceOperationModel
38 } from "../../../../models/interfaceOperation";
39 import {
40     InterfaceOperationHandlerComponent
41 } from "./operation-creator/interface-operation-handler.component";
42
43 import {
44     ArtifactModel,
45     ButtonModel,
46     ComponentInstance,
47     ComponentMetadata,
48     InputBEModel,
49     InterfaceModel,
50     ModalModel
51 } from 'app/models';
52 import {ArtifactGroupType} from "../../../../utils/constants";
53 import {
54     DropdownValue
55 } from "../../../components/ui/form-components/dropdown/ui-element-dropdown.component";
56 import {ToscaArtifactService} from "../../../services/tosca-artifact.service";
57 import {ToscaArtifactModel} from "../../../../models/toscaArtifact";
58
59 export class UIInterfaceOperationModel extends InterfaceOperationModel {
60     isCollapsed: boolean = true;
61     isEllipsis: boolean;
62     MAX_LENGTH = 75;
63
64     constructor(operation: InterfaceOperationModel) {
65         super(operation);
66
67         if (!operation.description) {
68             this.description = '';
69         }
70
71         if (this.description.length > this.MAX_LENGTH) {
72             this.isEllipsis = true;
73         } else {
74             this.isEllipsis = false;
75         }
76     }
77
78     getDescriptionEllipsis(): string {
79         if (this.isCollapsed && this.description.length > this.MAX_LENGTH) {
80             return this.description.substr(0, this.MAX_LENGTH - 3) + '...';
81         }
82         return this.description;
83     }
84
85     toggleCollapsed(e) {
86         e.stopPropagation();
87         this.isCollapsed = !this.isCollapsed;
88     }
89 }
90
91 class ModalTranslation {
92     EDIT_TITLE: string;
93     CANCEL_BUTTON: string;
94     CLOSE_BUTTON: string;
95     SAVE_BUTTON: string;
96
97     constructor(private translateService: TranslateService) {
98         this.translateService.languageChangedObservable.subscribe(lang => {
99             this.EDIT_TITLE = this.translateService.translate('INTERFACE_EDIT_TITLE');
100             this.CANCEL_BUTTON = this.translateService.translate("INTERFACE_CANCEL_BUTTON");
101             this.CLOSE_BUTTON = this.translateService.translate("INTERFACE_CLOSE_BUTTON");
102             this.SAVE_BUTTON = this.translateService.translate("INTERFACE_SAVE_BUTTON");
103         });
104     }
105 }
106
107 export class UIInterfaceModel extends ComponentInterfaceDefinitionModel {
108     isCollapsed: boolean = false;
109
110     constructor(interf?: any) {
111         super(interf);
112         this.operations = _.map(
113             this.operations,
114             (operation) => new UIInterfaceOperationModel(operation)
115         );
116     }
117
118     toggleCollapse() {
119         this.isCollapsed = !this.isCollapsed;
120     }
121 }
122
123 @Component({
124     selector: 'app-interface-operations',
125     templateUrl: './interface-operations.component.html',
126     styleUrls: ['./interface-operations.component.less'],
127     providers: [ModalService, TranslateService]
128 })
129 export class InterfaceOperationsComponent {
130     interfaces: UIInterfaceModel[];
131     inputs: Array<InputBEModel>;
132     isLoading: boolean;
133     interfaceTypes: { [interfaceType: string]: string[] };
134     topologyTemplate: TopologyTemplate;
135     componentMetaData: ComponentMetadata;
136     componentInstanceSelected: ComponentInstance;
137     modalInstance: ComponentRef<ModalComponent>;
138     modalTranslation: ModalTranslation;
139     componentInstancesInterfaces: Map<string, InterfaceModel[]>;
140
141     deploymentArtifactsFilePath: Array<DropdownValue> = [];
142     toscaArtifactTypes: Array<DropdownValue> = [];
143     componentInstanceMap: Map<string, InstanceFeDetails> = new Map<string, InstanceFeDetails>();
144     validImplementationProps: boolean = true;
145     validMilestoneActivities: boolean = true;
146     validMilestoneFilters: boolean = true;
147
148     @Input() component: ComponentInstance;
149     @Input() isViewOnly: boolean;
150     @Input() enableMenuItems: Function;
151     @Input() disableMenuItems: Function;
152     @Input() componentType: SelectedComponentType;
153
154
155     constructor(
156         private translateService: TranslateService,
157         private pluginsService: PluginsService,
158         private topologyTemplateService: TopologyTemplateService,
159         private toscaArtifactService: ToscaArtifactService,
160         private modalServiceNg2: ModalService,
161         private compositionService: CompositionService,
162         private workspaceService: WorkspaceService,
163         @Inject("Notification") private Notification: any,
164     ) {
165         this.modalTranslation = new ModalTranslation(translateService);
166     }
167
168     ngOnInit(): void {
169         this.componentMetaData = this.workspaceService.metadata;
170         this.loadComponentInstances();
171         this.loadDeployedArtifacts();
172         this.loadToscaArtifacts()
173     }
174
175     private loadComponentInstances() {
176         this.isLoading = true;
177         this.topologyTemplateService.getComponentInstances(this.componentMetaData.componentType, this.componentMetaData.uniqueId)
178         .subscribe((response) => {
179             this.componentInstanceSelected = response.componentInstances.find(ci => ci.uniqueId === this.component.uniqueId);
180             this.initComponentInstanceInterfaceOperations();
181             this.isLoading = false;
182         });
183     }
184
185     private initComponentInstanceInterfaceOperations() {
186         this.initInterfaces(this.componentInstanceSelected.interfaces);
187         this.sortInterfaces();
188     }
189
190     private initInterfaces(interfaces: ComponentInterfaceDefinitionModel[]): void {
191         this.interfaces = _.map(interfaces, (interfaceModel) => new UIInterfaceModel(interfaceModel));
192     }
193
194     private sortInterfaces(): void {
195         this.interfaces = _.filter(this.interfaces, (interf) => interf.operations && interf.operations.length > 0); // remove empty interfaces
196         this.interfaces.sort((a, b) => a.type.localeCompare(b.type)); // sort interfaces alphabetically
197         _.forEach(this.interfaces, (interf) => {
198             interf.operations.sort((a, b) => a.name.localeCompare(b.name)); // sort operations alphabetically
199         });
200     }
201
202     collapseAll(value: boolean = true): void {
203         _.forEach(this.interfaces, (interf) => {
204             interf.isCollapsed = value;
205         });
206     }
207
208     isAllCollapsed(): boolean {
209         return _.every(this.interfaces, (interf) => interf.isCollapsed);
210     }
211
212     isAllExpanded(): boolean {
213         return _.every(this.interfaces, (interf) => !interf.isCollapsed);
214     }
215
216     isListEmpty(): boolean {
217         return _.filter(
218             this.interfaces,
219             (interf) => interf.operations && interf.operations.length > 0
220         ).length === 0;
221     }
222
223     private disableSaveButton = (): boolean => {
224         let disable:boolean = true;
225         if(this.isViewOnly) {
226             return disable;
227         }
228
229         const validMilestoneFilters = this.modalInstance.instance.dynamicContent.instance.validMilestoneFilters;
230         const validMilestoneActivities = this.modalInstance.instance.dynamicContent.instance.validMilestoneActivities;
231         if (!validMilestoneActivities || !validMilestoneFilters) {
232             return disable;
233         }
234         let enableAddArtifactImplementation = this.modalInstance.instance.dynamicContent.instance.enableAddArtifactImplementation;
235         if(enableAddArtifactImplementation) {
236             const validImplementationProps = this.modalInstance.instance.dynamicContent.instance.validImplementationProps;
237             const toscaArtifactTypeSelected = this.modalInstance.instance.dynamicContent.instance.toscaArtifactTypeSelected;
238             const isToscaArtifactType:boolean = !(typeof toscaArtifactTypeSelected == 'undefined' || _.isEmpty(toscaArtifactTypeSelected));
239             disable = !isToscaArtifactType || !validImplementationProps;
240             return disable;
241         }
242         disable = false;
243         return disable;
244     }
245
246     onSelectInterfaceOperation(interfaceModel: UIInterfaceModel, operation: InterfaceOperationModel) {
247
248         const buttonList = [];
249         if (this.isViewOnly) {
250             const closeButton: ButtonModel = new ButtonModel(this.modalTranslation.CLOSE_BUTTON, 'outline white', this.cancelAndCloseModal);
251             buttonList.push(closeButton);
252         } else {
253             const saveButton: ButtonModel = new ButtonModel(this.modalTranslation.SAVE_BUTTON, 'blue', () =>
254                 this.updateInterfaceOperation(), this.disableSaveButton);
255             const cancelButton: ButtonModel = new ButtonModel(this.modalTranslation.CANCEL_BUTTON, 'outline white', this.cancelAndCloseModal);
256             buttonList.push(saveButton);
257             buttonList.push(cancelButton);
258         }
259         const modalModel: ModalModel = new ModalModel('l', this.modalTranslation.EDIT_TITLE, '', buttonList, 'custom');
260         this.modalInstance = this.modalServiceNg2.createCustomModal(modalModel);
261
262         const componentInstances = this.compositionService.getComponentInstances()
263         if (componentInstances) {
264             componentInstances.forEach(value => {
265                 this.componentInstanceMap.set(value.uniqueId, <InstanceFeDetails>{
266                     name: value.name
267                 });
268             });
269         }
270
271         this.modalServiceNg2.addDynamicContentToModal(
272             this.modalInstance,
273             InterfaceOperationHandlerComponent,
274             {
275                 deploymentArtifactsFilePath: this.deploymentArtifactsFilePath,
276                 componentInstanceMap: this.componentInstanceMap,
277                 toscaArtifactTypes: this.toscaArtifactTypes,
278                 selectedInterface: interfaceModel ? interfaceModel : new UIInterfaceModel(),
279                 selectedInterfaceOperation: operation ? operation : new InterfaceOperationModel(),
280                 validityChangedCallback: this.disableSaveButton,
281                 isViewOnly: this.isViewOnly,
282                 validImplementationProps: this.validImplementationProps,
283                 validMilestoneActivities: this.validMilestoneActivities,
284                 validMilestoneFilters: this.validMilestoneFilters,
285                 isEdit: true,
286                 modelName: this.componentMetaData.model
287             }
288         );
289         this.modalInstance.instance.open();
290     }
291
292     private cancelAndCloseModal = () => {
293         this.loadComponentInstances();
294         return this.modalServiceNg2.closeCurrentModal();
295     }
296
297     private updateInterfaceOperation() {
298         this.modalServiceNg2.currentModal.instance.dynamicContent.instance.isLoading = true;
299         const interfaceOperationHandlerComponentInstance: InterfaceOperationHandlerComponent = this.modalInstance.instance.dynamicContent.instance;
300         const operationUpdated: InterfaceOperationModel = interfaceOperationHandlerComponentInstance.operationToUpdate;
301         let timeout = null;
302         if (operationUpdated.implementation && operationUpdated.implementation.timeout != null) {
303             timeout = operationUpdated.implementation.timeout;
304         }
305         const isArtifactChecked = interfaceOperationHandlerComponentInstance.enableAddArtifactImplementation;
306         if (!isArtifactChecked) {
307             let artifactName = interfaceOperationHandlerComponentInstance.artifactName;
308             artifactName = artifactName === undefined ? '' : artifactName;
309             operationUpdated.implementation = new ArtifactModel({'artifactName': artifactName, 'artifactVersion': ''} as ArtifactModel);
310         }
311         if (timeout != null) {
312             operationUpdated.implementation.timeout = timeout;
313         }
314         this.topologyTemplateService.updateComponentInstanceInterfaceOperation(
315             this.componentMetaData.uniqueId,
316             this.componentMetaData.componentType,
317             this.componentInstanceSelected.uniqueId,
318             operationUpdated)
319         .subscribe((updatedComponentInstance: ComponentInstance) => {
320             this.componentInstanceSelected = new ComponentInstance(updatedComponentInstance);
321             this.initComponentInstanceInterfaceOperations();
322             this.modalServiceNg2.currentModal.instance.dynamicContent.instance.isLoading = false;
323             this.modalServiceNg2.closeCurrentModal();
324         }, () => {
325             this.modalServiceNg2.currentModal.instance.dynamicContent.instance.isLoading = false;
326             this.modalServiceNg2.closeCurrentModal();
327         });
328     }
329
330     loadDeployedArtifacts() {
331         this.topologyTemplateService.getArtifactsByType(this.componentMetaData.componentType, this.componentMetaData.uniqueId, ArtifactGroupType.DEPLOYMENT)
332         .subscribe(response => {
333             let artifactsDeployment = response.deploymentArtifacts;
334             if (artifactsDeployment) {
335                 let deploymentArtifactsFound = <ArtifactModel[]>_.values(artifactsDeployment)
336                 deploymentArtifactsFound.forEach(value => {
337                     this.deploymentArtifactsFilePath.push(new DropdownValue(value, value.artifactType.concat('->').concat(value.artifactName)));
338                 });
339             }
340         }, error => {
341             this.Notification.error({
342                 message: 'Failed to Load the Deployed Artifacts:' + error,
343                 title: 'Failure'
344             });
345         });
346     }
347
348     loadToscaArtifacts() {
349         this.toscaArtifactService.getToscaArtifacts(this.componentMetaData.model).subscribe(response => {
350             if (response) {
351                 let toscaArtifactsFound = <ToscaArtifactModel[]>_.values(response);
352                 toscaArtifactsFound.forEach(value => this.toscaArtifactTypes.push(new DropdownValue(value, value.type)));
353             }
354         }, error => {
355             this.Notification.error({
356                 message: 'Failed to Load Tosca Artifacts:' + error,
357                 title: 'Failure'
358             });
359         });
360     }
361
362 }