CSIT Fix for SDC-2585
[sdc.git] / catalog-ui / src / app / view-models / workspace / tabs / composition / composition-view-model.ts
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. 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  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20 'use strict';
21 import * as _ from "lodash";
22 import { Component, ComponentInstance, IAppMenu, Requirement, Capability, ButtonModel } from "app/models";
23 import { SharingService, CacheService, EventListenerService, LeftPaletteLoaderService } from "app/services";
24 import { ModalsHandler, GRAPH_EVENTS, ComponentFactory, ChangeLifecycleStateHandler, MenuHandler, EVENTS, ComponentInstanceFactory } from "app/utils";
25 import { IWorkspaceViewModelScope } from "../../workspace-view-model";
26 import { ComponentGenericResponse } from "app/ng2/services/responses/component-generic-response";
27 import { Resource } from "app/models/components/resource";
28 import { ResourceType, ComponentType } from "app/utils/constants";
29 import { ComponentServiceFactoryNg2 } from "app/ng2/services/component-services/component.service.factory";
30 import { ServiceGenericResponse } from "app/ng2/services/responses/service-generic-response";
31 import { Service } from "app/models/components/service";
32 import { ZoneInstance } from "app/models/graph/zones/zone-instance";
33 import { ComponentServiceNg2 } from "app/ng2/services/component-services/component.service";
34 import { ModalService as ModalServiceSdcUI} from "sdc-ui/lib/angular/modals/modal.service"
35 import { IModalConfig, IModalButtonComponent } from "sdc-ui/lib/angular/modals/models/modal-config";
36 import { ValueEditComponent } from "app/ng2/components/ui/forms/value-edit/value-edit.component";
37 import { UnsavedChangesComponent } from "../../../../ng2/components/ui/forms/unsaved-changes/unsaved-changes.component";
38 import { ModalButtonComponent } from "sdc-ui/lib/angular/components";
39
40
41
42 export interface ICompositionViewModelScope extends IWorkspaceViewModelScope {
43
44     currentComponent:Component;
45
46     //Added for now, in the future need to remove and use only id and type to pass to tabs.
47     selectedComponent: Component;
48     selectedZoneInstance: ZoneInstance;
49
50     componentInstanceNames: Array<string>;
51     isLoading:boolean;
52     graphApi:any;
53     sharingService:SharingService;
54     sdcMenu:IAppMenu;
55     version:string;
56     isViewOnly:boolean;
57     isCanvasTagging:boolean;
58     isLoadingRightPanel:boolean;
59     disabledTabs:boolean;
60     openVersionChangeModal(pathsToDelete:string[]):ng.IPromise<any>;
61     onComponentInstanceVersionChange(component:Component);
62     isComponentInstanceSelected():boolean;
63     updateSelectedComponent():void;
64     openUpdateModal();
65     deleteSelectedComponentInstance():void;
66     onBackgroundClick():void;
67     setSelectedInstance(componentInstance:ComponentInstance):void;
68     setSelectedZoneInstance(zoneInstance: ZoneInstance):void;
69     changeZoneInstanceName(newName:string):void;
70     printScreen():void;
71     isPNF():boolean;
72     isConfiguration():boolean;
73     preventMoveTab(state: boolean):void;
74     registerCreateInstanceEvent(callback: Function):void;
75     unregisterCreateInstanceEvent():void;
76     registerChangeComponentInstanceNameEvent(callback: Function):void;
77     unregisterChangeComponentInstanceNameEvent():void;
78
79     ComponentServiceNg2:ComponentServiceNg2,
80     cacheComponentsInstancesFullData:Component;
81 }
82
83 export class CompositionViewModel {
84
85     static '$inject' = [
86         '$scope',
87         '$log',
88         'sdcMenu',
89         'MenuHandler',
90         '$uibModal',
91         '$state',
92         'Sdc.Services.SharingService',
93         '$filter',
94         'Sdc.Services.CacheService',
95         'ComponentFactory',
96         'ChangeLifecycleStateHandler',
97         'LeftPaletteLoaderService',
98         'ModalsHandler',
99         'ModalServiceSdcUI',
100         'EventListenerService',
101         'ComponentServiceFactoryNg2',
102         'ComponentServiceNg2',
103         'Notification'
104     ];
105
106     constructor(private $scope:ICompositionViewModelScope,
107                 private $log:ng.ILogService,
108                 private sdcMenu:IAppMenu,
109                 private MenuHandler:MenuHandler,
110                 private $uibModal:ng.ui.bootstrap.IModalService,
111                 private $state:ng.ui.IStateService,
112                 private sharingService:SharingService,
113                 private $filter:ng.IFilterService,
114                 private cacheService:CacheService,
115                 private ComponentFactory:ComponentFactory,
116                 private ChangeLifecycleStateHandler:ChangeLifecycleStateHandler,
117                 private LeftPaletteLoaderService:LeftPaletteLoaderService,
118                 private ModalsHandler:ModalsHandler,
119                 private ModalServiceSdcUI: ModalServiceSdcUI,
120                 private eventListenerService:EventListenerService,
121                 private ComponentServiceFactoryNg2: ComponentServiceFactoryNg2,
122                 private ComponentServiceNg2:ComponentServiceNg2,
123                 private Notification:any
124     ) {
125
126         this.$scope.setValidState(true);
127         this.initScope();
128         this.initGraphData();
129         this.registerGraphEvents(this.$scope);
130     }
131
132
133     private initGraphData = ():void => {
134         if(!this.hasCompositionGraphData(this.$scope.component)) {
135             this.$scope.isLoading = true;
136             let service = this.ComponentServiceFactoryNg2.getComponentService(this.$scope.component);
137             service.getComponentCompositionData(this.$scope.component).subscribe((response:ComponentGenericResponse) => {
138                 if (this.$scope.component.isService()) {
139                     (<Service> this.$scope.component).forwardingPaths = (<ServiceGenericResponse>response).forwardingPaths;
140                 }
141                 this.$scope.component.componentInstances = response.componentInstances || [];
142                 this.$scope.component.componentInstancesRelations = response.componentInstancesRelations || [];
143                 this.$scope.component.policies = response.policies || [];
144                 this.$scope.component.groupInstances = response.groupInstances || [];
145                 this.$scope.isLoading = false;
146                 this.initComponent();
147                 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_COMPOSITION_GRAPH_DATA_LOADED);
148             });
149         } else {
150             this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_COMPOSITION_GRAPH_DATA_LOADED);
151         }
152         this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_COMPOSITION_GRAPH_DATA_LOADED);
153     };
154
155     private hasCompositionGraphData = (component:Component):boolean => {
156         return !!(component.componentInstances && component.componentInstancesRelations && component.policies && component.groupInstances);
157     };
158
159     private cacheComponentsInstancesFullData:Array<Component>;
160
161     private initComponent = ():void => {
162         this.$scope.currentComponent = this.$scope.component;
163         this.$scope.selectedComponent = this.$scope.currentComponent;
164         this.$scope.selectedZoneInstance = null;
165         this.updateUuidMap();
166         this.$scope.isViewOnly = this.$scope.isViewMode();
167     };
168
169     private registerGraphEvents = (scope:ICompositionViewModelScope):void => {
170         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_NODE_SELECTED, scope.setSelectedInstance);
171         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_ZONE_INSTANCE_SELECTED, scope.setSelectedZoneInstance);
172         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED, scope.onBackgroundClick);
173         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_CANVAS_TAG_START, () => {
174             scope.isCanvasTagging = true;
175             this.eventListenerService.notifyObservers(EVENTS.ON_WORKSPACE_UNSAVED_CHANGES, true, this.showUnsavedChangesAlert);
176         });
177         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_CANVAS_TAG_END, () => {
178             scope.isCanvasTagging = false;
179             this.resetUnsavedChanges();
180         });
181         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_ZONE_INSTANCE_NAME_CHANGED, scope.changeZoneInstanceName);
182         this.eventListenerService.registerObserverCallback(EVENTS.UPDATE_PANEL, this.removeSelectedZoneInstance);
183     };
184
185     private showUnsavedChangesAlert = (afterSave?:Function):Promise<any> => {
186         let deferred = new Promise<any>((resolve, reject)=> {
187             const modal = this.ModalServiceSdcUI.openCustomModal(
188                 {
189                     title: "Unsaved Changes",
190                     size: 'sm',
191                     type: 'custom',
192
193                     buttons: [
194                         {id: 'cancelButton', text: 'Cancel', type: 'secondary', size: 'xsm', closeModal: true, callback: () => reject()},
195                         {id: 'discardButton', text: 'Discard', type: 'secondary', size: 'xsm', closeModal: true, callback: () => { this.resetUnsavedChanges(); resolve()}},
196                         {id: 'saveButton', text: 'Save', type: 'primary', size: 'xsm', closeModal: true, callback: () => {  reject(); this.saveUnsavedChanges(afterSave);  }}
197                     ] as IModalButtonComponent[]
198                 }, UnsavedChangesComponent, { isValidChangedData: true});
199         });
200
201         return deferred;
202     }
203
204     private unRegisterGraphEvents = (scope: ICompositionViewModelScope):void => {
205         this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_NODE_SELECTED, scope.setSelectedInstance);
206         this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_ZONE_INSTANCE_SELECTED, scope.setSelectedZoneInstance);
207         this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED, scope.onBackgroundClick);
208         this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_CANVAS_TAG_START);
209         this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_CANVAS_TAG_END);
210         this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_ZONE_INSTANCE_NAME_CHANGED, scope.changeZoneInstanceName);
211         this.eventListenerService.unRegisterObserver(EVENTS.UPDATE_PANEL, this.removeSelectedZoneInstance);
212
213     };
214
215     private resetUnsavedChanges = () => {
216         this.eventListenerService.notifyObservers(EVENTS.ON_WORKSPACE_UNSAVED_CHANGES, false);
217     }
218
219     private saveUnsavedChanges = (afterSaveFunction?:Function):void => {
220         this.$scope.selectedZoneInstance.forceSave.next(afterSaveFunction);
221         this.eventListenerService.notifyObservers(EVENTS.ON_WORKSPACE_UNSAVED_CHANGES, false);
222     }
223
224     private openUpdateComponentInstanceNameModal = ():void => {
225
226         let modalConfig:IModalConfig = {
227             title: "Edit Name",
228             size: "sm",
229             type: "custom",
230             testId: "renameInstanceModal",
231             buttons: [
232                 {id: 'saveButton', text: 'OK', size: 'xsm', callback: this.saveInstanceName, closeModal: false},
233                 {id: 'cancelButton', text: 'Cancel', size: 'sm', closeModal: true}
234             ]
235         };
236
237         this.ModalServiceSdcUI.openCustomModal(modalConfig, ValueEditComponent, {name: this.$scope.currentComponent.selectedInstance.name, validityChangedCallback: this.enableOrDisableSaveButton});
238
239     };
240
241
242     private enableOrDisableSaveButton = (shouldEnable: boolean): void => {
243         let saveButton: ModalButtonComponent = this.ModalServiceSdcUI.getCurrentInstance().getButtonById('saveButton');
244         saveButton.disabled = !shouldEnable;
245     }
246
247     private saveInstanceName = () => {
248         let currentModal = this.ModalServiceSdcUI.getCurrentInstance();
249         let nameFromModal:string = currentModal.innerModalContent.instance.name;
250
251         if(nameFromModal != this.$scope.currentComponent.selectedInstance.name){
252             currentModal.buttons[0].disabled = true;
253             let componentInstanceModel:ComponentInstance = ComponentInstanceFactory.createComponentInstance(this.$scope.currentComponent.selectedInstance);
254             componentInstanceModel.name = nameFromModal;
255
256             let onFailed = (error) => {
257                 currentModal.buttons[0].disabled = false;
258             };
259             let onSuccess = (componentInstance:ComponentInstance) => {
260
261                 this.$scope.currentComponent.selectedInstance.name = componentInstance.name;
262                 //update requirements and capabilities owner name
263                 _.forEach(this.$scope.currentComponent.selectedInstance.requirements, (requirementsArray:Array<Requirement>) => {
264                     _.forEach(requirementsArray, (requirement:Requirement):void => {
265                         requirement.ownerName = componentInstance.name;
266                     });
267                 });
268
269                 _.forEach(this.$scope.currentComponent.selectedInstance.capabilities, (capabilitiesArray:Array<Capability>) => {
270                     _.forEach(capabilitiesArray, (capability:Capability):void => {
271                         capability.ownerName = componentInstance.name;
272                     });
273                 });
274                 this.ModalServiceSdcUI.closeModal();
275                 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_COMPONENT_INSTANCE_NAME_CHANGED, this.$scope.currentComponent.selectedInstance);
276             };
277
278             this.$scope.currentComponent.updateComponentInstance(componentInstanceModel).then(onSuccess, onFailed);
279         }  else {
280             this.ModalServiceSdcUI.closeModal();
281         }
282
283     };
284
285     private removeSelectedComponentInstance = ():void => {
286         this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, this.$scope.currentComponent.selectedInstance)
287         this.$scope.currentComponent.selectedInstance = null;
288         this.$scope.selectedComponent = this.$scope.currentComponent;
289     };
290
291     private removeSelectedZoneInstance = ():void => {
292         this.$scope.currentComponent.selectedInstance = null;
293         this.$scope.selectedZoneInstance = null;
294         this.$scope.selectedComponent = this.$scope.currentComponent;
295     }
296
297     private updateUuidMap = ():void => {
298         /**
299          * In case user press F5, the page is refreshed and this.sharingService.currentEntity will be undefined,
300          * but after loadService or loadResource this.sharingService.currentEntity will be defined.
301          * Need to update the uuidMap with the new resource or service.
302          */
303         this.sharingService.addUuidValue(this.$scope.currentComponent.uniqueId, this.$scope.currentComponent.uuid);
304     };
305
306     private initScope = ():void => {
307         this.$scope.sharingService = this.sharingService;
308         this.$scope.sdcMenu = this.sdcMenu;
309         this.$scope.isLoading = false;
310         this.$scope.isLoadingRightPanel = false;
311         this.$scope.isCanvasTagging = false;
312         this.$scope.graphApi = {};
313         this.$scope.version = this.cacheService.get('version');
314         this.initComponent();
315
316         this.cacheComponentsInstancesFullData = new Array<Component>();
317
318         this.$scope.isComponentInstanceSelected = ():boolean => {
319             return this.$scope.currentComponent && this.$scope.currentComponent.selectedInstance != undefined && this.$scope.currentComponent.selectedInstance != null;
320         };
321
322         this.$scope.$on('$destroy', () => {
323             this.unRegisterGraphEvents(this.$scope);
324         })
325
326         this.$scope.restoreComponent = ():void => {
327             this.ComponentServiceNg2.restoreComponent(this.$scope.selectedComponent.componentType, this.$scope.selectedComponent.uniqueId).subscribe(() => {
328                     this.Notification.success({
329                         message: '&lt;' + this.$scope.component.name + '&gt; ' + this.$filter('translate')("ARCHIVE_SUCCESS_MESSAGE_TEXT"),
330                         title: this.$filter('translate')("ARCHIVE_SUCCESS_MESSAGE_TITLE")
331                     });
332                     this.$scope.selectedComponent.archived = false;
333                 }
334             )
335         };
336
337         this.$scope.updateSelectedComponent = ():void => {
338             if (this.$scope.currentComponent.selectedInstance) {
339                 let parentComponentUid = this.$scope.currentComponent.selectedInstance.componentUid
340                 if(this.$scope.currentComponent.selectedInstance.originType === ComponentType.SERVICE_PROXY){
341                     parentComponentUid = this.$scope.currentComponent.selectedInstance.sourceModelUid;
342                 }
343                 let componentParent = _.find(this.cacheComponentsInstancesFullData, (component) => {
344                     return component.uniqueId === parentComponentUid;
345                 });
346                 if (componentParent) {
347                     this.$scope.selectedComponent = componentParent;
348                 }
349                 else {
350                     try {
351                         let onSuccess = (component:Component) => {
352                             this.$scope.isLoadingRightPanel = false;
353                             this.$scope.selectedComponent = component;
354                             this.cacheComponentsInstancesFullData.push(component);
355                         };
356                         let onError = (component:Component) => {
357                             console.log("Error updating selected component");
358                             this.$scope.isLoadingRightPanel = false;
359                         };
360                         this.ComponentFactory.getComponentFromServer(this.$scope.currentComponent.selectedInstance.originType, parentComponentUid).then(onSuccess, onError);
361                     } catch (e) {
362                         console.log("Error updating selected component", e);
363                         this.$scope.isLoadingRightPanel = false;
364                     }
365                 }
366             }
367             else {
368
369                 this.$scope.selectedComponent = this.$scope.currentComponent;
370             }
371         };
372
373         this.$scope.setSelectedInstance = (selectedComponent:ComponentInstance):void => {
374
375             this.$log.debug('composition-view-model::onNodeSelected:: with id: ' + selectedComponent.uniqueId);
376             this.$scope.currentComponent.setSelectedInstance(selectedComponent);
377             this.$scope.selectedZoneInstance = null;
378             this.$scope.updateSelectedComponent();
379
380             if (this.$state.current.name === 'workspace.composition.api') {
381                 this.$state.go('workspace.composition.details');
382             }
383             if(!selectedComponent.isServiceProxy() && (this.$state.current.name === 'workspace.composition.consumption' || this.$state.current.name === 'workspace.composition.dependencies')) {
384                 this.$state.go('workspace.composition.details');
385             }
386         };
387
388         this.$scope.setSelectedZoneInstance = (zoneInstance: ZoneInstance): void => {
389             this.$scope.currentComponent.selectedInstance = null;
390             this.$scope.selectedZoneInstance = zoneInstance;
391         };
392
393         this.$scope.onBackgroundClick = ():void => {
394             this.$scope.currentComponent.selectedInstance = null;
395             this.$scope.selectedZoneInstance = null;
396             this.$scope.selectedComponent = this.$scope.currentComponent;
397
398             if (this.$state.current.name === 'workspace.composition.api' || this.$state.current.name === 'workspace.composition.consumption' || this.$state.current.name === 'workspace.composition.dependencies') {
399                 this.$state.go('workspace.composition.details');
400             }
401
402             if(this.$scope.selectedComponent.isService() && this.$state.current.name === 'workspace.composition.relations'){
403                 this.$state.go('workspace.composition.api');
404             }
405         };
406
407         this.$scope.openUpdateModal = ():void => {
408             this.openUpdateComponentInstanceNameModal();
409         };
410
411         this.$scope.changeZoneInstanceName = (newName:string):void => {
412             this.$scope.selectedZoneInstance.instanceData.name = newName;
413         };
414
415         this.$scope.deleteSelectedComponentInstance = ():void => {
416             const {currentComponent} = this.$scope;
417             const {title, message} = this.$scope.sdcMenu.alertMessages['deleteInstance'];
418             let modalText = message.format([currentComponent.selectedInstance.name]);
419
420             if (currentComponent.isService()) {
421                 const {forwardingPaths} = (<Service>currentComponent);
422                 const instanceId = currentComponent.selectedInstance.uniqueId;
423
424                 const relatedPaths = _.filter(forwardingPaths, forwardingPath => {
425                     const pathElements = forwardingPath.pathElements.listToscaDataDefinition;
426                     return pathElements.find(path => path.fromNode === instanceId || path.toNode === instanceId);
427                 });
428
429                 if (relatedPaths.length) {
430                     const pathNames = _.map(relatedPaths, path => path.name).join(', ');
431                     modalText += `<p>The following service paths will be erased: ${pathNames}</p>`;
432                 }
433             }
434             this.ModalServiceSdcUI.openAlertModal(title, modalText, "OK", this.removeSelectedComponentInstance, "deleteInstanceModal");
435         };
436
437         this.$scope.openVersionChangeModal = (pathsToDelete:string[]):ng.IPromise<any> => {
438             const {currentComponent} = this.$scope;
439             const {forwardingPaths} = <Service>currentComponent;
440
441             const relatedPaths = _.filter(forwardingPaths, path =>
442                 _.find(pathsToDelete, id =>
443                     path.uniqueId === id
444                 )
445             ).map(path => path.name);
446             const pathNames = _.join(relatedPaths, ', ') || 'none';
447
448             const {title, message} = this.$scope.sdcMenu.alertMessages['upgradeInstance'];
449             return this.ModalsHandler.openConfirmationModal(title, message.format([pathNames]), false);
450         };
451
452         this.$scope.onComponentInstanceVersionChange = (component:Component):void => {
453             let onChange = () => {
454                 this.$scope.currentComponent = component;
455                 this.$scope.setComponent(this.$scope.currentComponent);
456                 this.$scope.updateSelectedComponent();
457                 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_VERSION_CHANGED, this.$scope.currentComponent);
458             };
459
460             if (component.isService()) {
461                 const service = this.ComponentServiceFactoryNg2.getComponentService(component);
462                 service.getComponentCompositionData(component).subscribe((response:ServiceGenericResponse) => {
463                     (<Service>component).forwardingPaths = response.forwardingPaths;
464                     onChange();
465                 });
466             } else {
467                 onChange();
468             }
469         };
470
471         this.$scope.isPNF = (): boolean => {
472             return this.$scope.selectedComponent.isResource() && (<Resource>this.$scope.selectedComponent).resourceType === ResourceType.PNF;
473         };
474
475         this.$scope.isConfiguration = (): boolean => {
476             return this.$scope.selectedComponent.isResource() && (<Resource>this.$scope.selectedComponent).resourceType === ResourceType.CONFIGURATION;
477         };
478
479         this.$scope.preventMoveTab = (state: boolean): void => {
480             this.$scope.disabledTabs = state;
481         };
482
483         this.eventListenerService.registerObserverCallback(EVENTS.ON_LIFECYCLE_CHANGE, this.$scope.reload);
484
485         this.$scope.registerCreateInstanceEvent = (callback: Function): void => {
486             this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_CREATE_COMPONENT_INSTANCE, callback);
487         };
488
489         this.$scope.unregisterCreateInstanceEvent = (): void => {
490             this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_CREATE_COMPONENT_INSTANCE);
491         };
492
493         this.$scope.registerChangeComponentInstanceNameEvent = (callback: Function): void => {
494             this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_COMPONENT_INSTANCE_NAME_CHANGED, callback);
495         };
496
497         this.$scope.unregisterChangeComponentInstanceNameEvent = (): void => {
498             this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_COMPONENT_INSTANCE_NAME_CHANGED);
499         };
500     }
501 }