re base code
[sdc.git] / catalog-ui / src / app / directives / graphs-v2 / composition-graph / composition-graph.directive.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
21 import * as _ from "lodash";
22 import {
23     Match,
24     LinkMenu,
25     ComponentInstance,
26     LeftPaletteComponent,
27     Relationship,
28     Component,
29     Service,
30     ConnectRelationModel,
31     CompositionCiNodeBase,
32     CompositionCiNodeVl,
33     ModalModel,
34     ButtonModel,
35     NodesFactory,
36     Point
37 } from "app/models";
38 import {ComponentInstanceFactory, ComponentFactory, GRAPH_EVENTS, GraphColors} from "app/utils";
39 import {EventListenerService, LoaderService} from "app/services";
40 import {CompositionGraphLinkUtils} from "./utils/composition-graph-links-utils";
41 import {CompositionGraphGeneralUtils} from "./utils/composition-graph-general-utils";
42 import {CompositionGraphNodesUtils} from "./utils/composition-graph-nodes-utils";
43 import {CommonGraphUtils} from "../common/common-graph-utils";
44 import {MatchCapabilitiesRequirementsUtils} from "./utils/match-capability-requierment-utils";
45 import {CompositionGraphPaletteUtils} from "./utils/composition-graph-palette-utils";
46 import {ComponentInstanceNodesStyle} from "../common/style/component-instances-nodes-style";
47 import {CytoscapeEdgeEditation} from 'third-party/cytoscape.js-edge-editation/CytoscapeEdgeEditation.js';
48 import {ComponentServiceNg2} from "../../../ng2/services/component-services/component.service";
49 import {ComponentGenericResponse} from "../../../ng2/services/responses/component-generic-response";
50 import {ModalService} from "../../../ng2/services/modal.service";
51 import {ConnectionWizardService} from "../../../ng2/pages/connection-wizard/connection-wizard.service";
52 import {StepModel} from "../../../models/wizard-step";
53 import {FromNodeStepComponent} from "app/ng2/pages/connection-wizard/from-node-step/from-node-step.component";
54 import {PropertiesStepComponent} from "app/ng2/pages/connection-wizard/properties-step/properties-step.component";
55 import {ToNodeStepComponent} from "app/ng2/pages/connection-wizard/to-node-step/to-node-step.component";
56 import {ConnectionWizardHeaderComponent} from "app/ng2/pages/connection-wizard/connection-wizard-header/connection-wizard-header.component";
57 import {ConnectionPropertiesViewComponent} from "../../../ng2/pages/connection-wizard/connection-properties-view/connection-properties-view.component";
58 import {ComponentInstanceServiceNg2} from "../../../ng2/services/component-instance-services/component-instance.service";
59 import {EVENTS} from "../../../utils/constants";
60 import {PropertyBEModel} from "../../../models/properties-inputs/property-be-model";
61 import {ForwardingPath} from "app/models/forwarding-path";
62 import {ServicePathGraphUtils} from "./utils/composition-graph-service-path-utils";
63 import {CompositionCiServicePathLink} from "app/models/graph/graph-links/composition-graph-links/composition-ci-service-path-link";
64 import {
65     ZoneInstance, ZoneInstanceMode, ZoneInstanceType,
66     ZoneInstanceAssignmentType
67 } from "app/models/graph/zones/zone-instance";
68
69 import {Zone} from "app/models/graph/zones/zone";
70 import {CompositionGraphZoneUtils} from "./utils/composition-graph-zone-utils";
71 import {UIZoneInstanceObject} from "../../../models/ui-models/ui-zone-instance-object";
72 import {GroupInstance} from "../../../models/graph/zones/group-instance";
73 import {PolicyInstance} from "../../../models/graph/zones/policy-instance";
74
75
76 export interface ICompositionGraphScope extends ng.IScope {
77
78     component:Component;
79     isLoading:boolean;
80     isViewOnly:boolean;
81     withSidebar:boolean;
82
83     //zones
84     newZoneInstance;
85     zoneTagMode:string;
86     activeZoneInstance:ZoneInstance;
87     zones:Array<Zone>;
88     zoneMinimizeToggle(zoneType:ZoneInstanceType):void;
89     zoneInstanceTagged(taggedInstance:ZoneInstance):void;
90     zoneInstanceModeChanged(newMode:ZoneInstanceMode, instance:ZoneInstance, zoneId:ZoneInstanceType);
91     unsetActiveZoneInstance():void;
92     clickOutsideZoneInstance():void;
93     zoneAssignmentSaveStart():void;
94     zoneAssignmentSaveComplete(success:boolean):void;
95
96     // Link menu - create link menu
97     relationMenuDirectiveObj:ConnectRelationModel;
98     isLinkMenuOpen:boolean;
99     createLinkFromMenu:(chosenMatch:Match, vl:Component)=>void;
100     saveChangedCapabilityProperties:()=>Promise<PropertyBEModel[]>;
101
102     //modify link menu - for now only delete menu
103     relationMenuTimeout:ng.IPromise<any>;
104     linkMenuObject:LinkMenu;
105     isOnDrag:boolean;
106
107     //left palette functions callbacks
108     dropCallback(event:JQueryEventObject, ui:any):void;
109     beforeDropCallback(event:IDragDropEvent):void;
110     verifyDrop(event:JQueryEventObject, ui:any):void;
111
112     //Links menus
113     viewRelation(link:Cy.CollectionEdges):void;
114     deleteRelation(link:Cy.CollectionEdges):void;
115     hideRelationMenu();
116
117     //search,zoom in/out/all
118     componentInstanceNames:Array<string>; //id, name
119     zoom(zoomIn:boolean):void;
120     zoomAllWithoutSidebar():void;
121     getAutoCompleteValues(searchTerm:string):void;
122     highlightSearchMatches(searchTerm:string):void;
123
124     canvasMenuProps:any;
125
126     createOrUpdateServicePath(data:any):void;
127     deletePathsOnCy():void;
128     drawPathOnCy(data:ForwardingPath):void;
129     selectedPathId:string;
130
131     /*//asset popover menu
132      assetPopoverObj:AssetPopoverObj;
133      assetPopoverOpen:boolean;
134      hideAssetPopover():void;
135      deleteNode(nodeId:string):void;*/
136 }
137
138 export class CompositionGraph implements ng.IDirective {
139     private _cy:Cy.Instance;
140     private _currentlyCLickedNodePosition:Cy.Position;
141     // private $document:JQuery = $(document);
142     private dragElement:JQuery;
143     private dragComponent:ComponentInstance;
144
145     constructor(private $q:ng.IQService,
146                 private $log:ng.ILogService,
147                 private $timeout:ng.ITimeoutService,
148                 private NodesFactory:NodesFactory,
149                 private CompositionGraphLinkUtils:CompositionGraphLinkUtils,
150                 private GeneralGraphUtils:CompositionGraphGeneralUtils,
151                 private ComponentInstanceFactory:ComponentInstanceFactory,
152                 private NodesGraphUtils:CompositionGraphNodesUtils,
153                 private eventListenerService:EventListenerService,
154                 private ComponentFactory:ComponentFactory,
155                 private LoaderService:LoaderService,
156                 private commonGraphUtils:CommonGraphUtils,
157                 private matchCapabilitiesRequirementsUtils:MatchCapabilitiesRequirementsUtils,
158                 private CompositionGraphPaletteUtils:CompositionGraphPaletteUtils,
159                 private compositionGraphZoneUtils:CompositionGraphZoneUtils,
160                 private ComponentServiceNg2:ComponentServiceNg2,
161                 private ModalServiceNg2:ModalService,
162                 private ConnectionWizardServiceNg2:ConnectionWizardService,
163                 private ComponentInstanceServiceNg2:ComponentInstanceServiceNg2,
164                 private servicePathGraphUtils:ServicePathGraphUtils) {
165
166     }
167
168     restrict = 'E';
169     template = require('./composition-graph.html');
170     scope = {
171         component: '=',
172         isViewOnly: '=',
173         withSidebar: '='
174     };
175
176     link = (scope:ICompositionGraphScope, el:JQuery) => {
177         this.loadGraph(scope, el);
178
179         if (!scope.component.groupInstances || !scope.component.policies) {
180             this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_COMPOSITION_GRAPH_DATA_LOADED, () => {
181                 this.loadGraphData(scope);
182             });
183         } else {
184             this.loadGraphData(scope);
185         }
186
187         
188         scope.$on('$destroy', () => {
189             this._cy.destroy();
190             _.forEach(GRAPH_EVENTS, (event) => {
191                 this.eventListenerService.unRegisterObserver(event);
192             });
193             this.eventListenerService.unRegisterObserver(EVENTS.SHOW_LOADER_EVENT + 'composition-graph');
194             this.eventListenerService.unRegisterObserver(EVENTS.HIDE_LOADER_EVENT + 'composition-graph');
195         });
196
197     };
198
199     private loadGraphData = (scope:ICompositionGraphScope) => {
200         this.initGraphNodes(scope.component.componentInstances, scope.isViewOnly);
201         this.commonGraphUtils.initGraphLinks(this._cy, scope.component.componentInstancesRelations, scope.component.getRelationRequirementCapability.bind(scope.component));
202         this.commonGraphUtils.initUcpeChildren(this._cy);
203         this.compositionGraphZoneUtils.initZoneInstances(scope.zones, scope.component);
204         setTimeout(() => {//Need settimeout so that angular canvas changes will take effect before resize & center
205             this.GeneralGraphUtils.zoomAllWithMax(this._cy, 1);
206         });
207     }
208
209     private loadGraph = (scope:ICompositionGraphScope, el:JQuery) => {
210         let graphEl = el.find('.sdc-composition-graph-wrapper');
211         this.initGraph(graphEl, scope.isViewOnly);
212         this.initDropZone(scope);
213         this.initZones(scope);
214         this.registerCytoscapeGraphEvents(scope);
215         this.registerCustomEvents(scope, el);
216         this.initViewMode(scope.isViewOnly);
217     };
218
219     private initGraph(graphEl:JQuery, isViewOnly:boolean) {
220
221         this._cy = cytoscape({
222             container: graphEl,
223             style: ComponentInstanceNodesStyle.getCompositionGraphStyle(),
224             zoomingEnabled: true,
225             maxZoom: 1.2,
226             minZoom: .1,
227             userZoomingEnabled: false,
228             userPanningEnabled: true,
229             selectionType: 'single',
230             boxSelectionEnabled: true,
231             autolock: isViewOnly,
232             autoungrabify: isViewOnly
233         });
234     }
235
236     private initViewMode(isViewOnly:boolean) {
237
238         if (isViewOnly) {
239             //remove event listeners
240             this._cy.off('drag');
241             this._cy.off('handlemouseout');
242             this._cy.off('handlemouseover');
243             this._cy.off('canvasredraw');
244             this._cy.off('handletagclick')
245             this._cy.edges().unselectify();
246         }
247     };
248
249     private registerCustomEvents(scope:ICompositionGraphScope, el:JQuery) {
250
251         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_GROUP_INSTANCE_UPDATE, (groupInstance:GroupInstance) => {
252             this.compositionGraphZoneUtils.findAndUpdateZoneInstanceData(scope.zones, groupInstance);
253             this.GeneralGraphUtils.showGroupUpdateSuccess();
254         });
255
256         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_POLICY_INSTANCE_UPDATE, (policyInstance: PolicyInstance) => {
257             this.compositionGraphZoneUtils.findAndUpdateZoneInstanceData(scope.zones, policyInstance);
258             this.GeneralGraphUtils.showPolicyUpdateSuccess();
259         });
260
261         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HOVER_IN, (leftPaletteComponent:LeftPaletteComponent) => {
262             if (scope.isOnDrag) {
263                 return;
264             }
265
266             this.$log.info(`composition-graph::registerEventServiceEvents:: palette hover on component: ${leftPaletteComponent.uniqueId}`);
267
268             let nodesData = this.NodesGraphUtils.getAllNodesData(this._cy.nodes());
269             let nodesLinks = this.GeneralGraphUtils.getAllCompositionCiLinks(this._cy);
270
271             if (this.GeneralGraphUtils.componentRequirementsAndCapabilitiesCaching.containsKey(leftPaletteComponent.uniqueId)) {
272                 let cacheComponent = this.GeneralGraphUtils.componentRequirementsAndCapabilitiesCaching.getValue(leftPaletteComponent.uniqueId);
273                 let filteredNodesData = this.matchCapabilitiesRequirementsUtils.findMatchingNodes(cacheComponent, nodesData, nodesLinks);
274
275                 this.matchCapabilitiesRequirementsUtils.highlightMatchingComponents(filteredNodesData, this._cy);
276                 this.matchCapabilitiesRequirementsUtils.fadeNonMachingComponents(filteredNodesData, nodesData, this._cy);
277
278                 return;
279             }
280
281             //----------------------- ORIT TO FIX------------------------//
282
283             this.ComponentServiceNg2.getCapabilitiesAndRequirements(leftPaletteComponent.componentType, leftPaletteComponent.uniqueId).subscribe((response:ComponentGenericResponse) => {
284
285                 let component = this.ComponentFactory.createEmptyComponent(leftPaletteComponent.componentType);
286                 component.uniqueId = component.uniqueId;
287                 component.capabilities = response.capabilities;
288                 component.requirements = response.requirements;
289                 this.GeneralGraphUtils.componentRequirementsAndCapabilitiesCaching.setValue(leftPaletteComponent.uniqueId, component);
290             });
291         });
292
293         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_ADD_ZONE_INSTANCE_FROM_PALETTE, (component:Component, paletteComponent:LeftPaletteComponent, startPosition:Point) => {
294
295             let zoneType:ZoneInstanceType = this.compositionGraphZoneUtils.getZoneTypeForPaletteComponent(paletteComponent.categoryType);
296             this.compositionGraphZoneUtils.showZone(scope.zones[zoneType]);
297
298             this.LoaderService.showLoader('composition-graph');
299             this.compositionGraphZoneUtils.createZoneInstanceFromLeftPalette(zoneType, component, paletteComponent.type).subscribe((zoneInstance:ZoneInstance) => {
300                 this.LoaderService.hideLoader('composition-graph');
301                 this.compositionGraphZoneUtils.addInstanceToZone(scope.zones[zoneInstance.type], zoneInstance, true);
302                 this.compositionGraphZoneUtils.createPaletteToZoneAnimation(startPosition, zoneType, zoneInstance);
303             }, (error)=> {
304                 this.LoaderService.hideLoader('composition-graph');
305             });
306         });
307
308         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HOVER_OUT, () => {
309
310             this._cy.emit('hidehandles');
311             this.matchCapabilitiesRequirementsUtils.resetFadedNodes(this._cy);
312         });
313
314         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_DRAG_START, (dragElement, dragComponent) => {
315
316             this.dragElement = dragElement;
317             this.dragComponent = this.ComponentInstanceFactory.createComponentInstanceFromComponent(dragComponent);
318         });
319
320         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_DRAG_ACTION, (event:IDragDropEvent) => {
321             this.CompositionGraphPaletteUtils.onComponentDrag(this._cy, event, this.dragElement, this.dragComponent);
322
323         });
324
325         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_COMPONENT_INSTANCE_NAME_CHANGED, (component:ComponentInstance) => {
326
327             let selectedNode = this._cy.getElementById(component.uniqueId);
328             selectedNode.data().componentInstance.name = component.name;
329             selectedNode.data('name', component.name); //used for tooltip
330             selectedNode.data('displayName', selectedNode.data().getDisplayName()); //abbreviated
331
332         });
333
334         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, (componentInstance:ComponentInstance) => {
335             let nodeToDelete = this._cy.getElementById(componentInstance.uniqueId);
336             this.NodesGraphUtils.deleteNode(this._cy, scope.component, nodeToDelete);
337         });
338
339         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_ZONE_INSTANCE, (deletedInstance:UIZoneInstanceObject) => {
340
341             if(deletedInstance.type === ZoneInstanceType.POLICY){
342                 scope.component.policies = scope.component.policies.filter(policy => policy.uniqueId !== deletedInstance.uniqueId);
343             } else if (deletedInstance.type === ZoneInstanceType.GROUP) {
344                 scope.component.groupInstances = scope.component.groupInstances.filter(group => group.uniqueId !== deletedInstance.uniqueId);
345             }
346             //remove it from zones
347             scope.zones[deletedInstance.type].removeInstance(deletedInstance.uniqueId);
348             if (deletedInstance.type === ZoneInstanceType.GROUP && !_.isEmpty(scope.zones[ZoneInstanceType.POLICY])) {
349                 this.compositionGraphZoneUtils.updateTargetsOrMembersOnCanvasDelete(deletedInstance.uniqueId, [scope.zones[ZoneInstanceType.POLICY]], ZoneInstanceAssignmentType.GROUPS);
350             }
351             this.eventListenerService.notifyObservers(EVENTS.UPDATE_PANEL);
352         });
353
354         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE_SUCCESS, (componentInstanceId:string)=> {
355             if (!_.isEmpty(scope.zones)) {
356                 this.compositionGraphZoneUtils.updateTargetsOrMembersOnCanvasDelete(componentInstanceId, scope.zones, ZoneInstanceAssignmentType.COMPONENT_INSTANCES);
357             }
358         });
359         // not in use; commenting out
360         // this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_MULTIPLE_COMPONENTS, () => {
361
362         //     this._cy.$('node:selected').each((i:number, node:Cy.CollectionNodes) => {
363         //         this.NodesGraphUtils.deleteNode(this._cy, scope.component, node);
364         //     });
365
366         // });
367
368         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_EDGE, (releaseLoading:boolean, linksToDelete:Cy.CollectionEdges) => {
369             this.CompositionGraphLinkUtils.deleteLink(this._cy, scope.component, releaseLoading, linksToDelete);
370         });
371
372         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_INSERT_NODE_TO_UCPE, (node:Cy.CollectionNodes, ucpe:Cy.CollectionNodes, updateExistingNode:boolean) => {
373
374             this.commonGraphUtils.initUcpeChildData(node, ucpe);
375             //check if item is a VL, and if so, skip adding the binding to ucpe
376             if (!(node.data() instanceof CompositionCiNodeVl)) {
377                 this.CompositionGraphLinkUtils.createVfToUcpeLink(scope.component, this._cy, ucpe.data(), node.data()); //create link from the node to the ucpe
378             }
379
380             if (updateExistingNode) {
381                 let vlsPendingDeletion:Cy.CollectionNodes = this.NodesGraphUtils.deleteNodeVLsUponMoveToOrFromUCPE(scope.component, node.cy(), node); //delete connected VLs that no longer have 2 links
382                 this.CompositionGraphLinkUtils.deleteLinksWhenNodeMovedFromOrToUCPE(scope.component, node.cy(), node, vlsPendingDeletion); //delete all connected links if needed
383                 this.GeneralGraphUtils.pushUpdateComponentInstanceActionToQueue(scope.component, true, node.data().componentInstance); //update componentInstance position
384             }
385
386         });
387
388         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_REMOVE_NODE_FROM_UCPE, (node:Cy.CollectionNodes, ucpe:Cy.CollectionNodes) => {
389             this.commonGraphUtils.removeUcpeChildData(node);
390             let vlsPendingDeletion:Cy.CollectionNodes = this.NodesGraphUtils.deleteNodeVLsUponMoveToOrFromUCPE(scope.component, node.cy(), node);
391             this.CompositionGraphLinkUtils.deleteLinksWhenNodeMovedFromOrToUCPE(scope.component, node.cy(), node, vlsPendingDeletion); //delete all connected links if needed
392             this.GeneralGraphUtils.pushUpdateComponentInstanceActionToQueue(scope.component, true, node.data().componentInstance); //update componentInstance position
393         });
394
395         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_VERSION_CHANGED, (component:Component) => {
396             scope.component = component;
397             this._cy.elements().remove();
398             this.loadGraphData(scope);
399         });
400
401
402         scope.zoom = (zoomIn:boolean):void => {
403             let currentZoom:number = this._cy.zoom();
404             if (zoomIn) {
405                 this.GeneralGraphUtils.zoomGraphTo(this._cy, currentZoom + .1);
406             } else {
407                 this.GeneralGraphUtils.zoomGraphTo(this._cy, currentZoom - .1);
408             }
409         }
410
411
412         scope.zoomAllWithoutSidebar = () => {
413             scope.withSidebar = false;
414             setTimeout(() => { //wait for sidebar changes to take effect before zooming
415                 this.GeneralGraphUtils.zoomAll(this._cy);
416             });
417         };
418
419         scope.getAutoCompleteValues = (searchTerm:string) => {
420             if (searchTerm.length > 1) { //US requirement: only display search results after 2nd letter typed.
421                 let nodes:Cy.CollectionNodes = this.NodesGraphUtils.getMatchingNodesByName(this._cy, searchTerm);
422                 scope.componentInstanceNames = _.map(nodes, node => node.data('name'));
423             } else {
424                 scope.componentInstanceNames = [];
425             }
426         };
427
428         scope.highlightSearchMatches = (searchTerm:string) => {
429             this.NodesGraphUtils.highlightMatchingNodesByName(this._cy, searchTerm);
430             let matchingNodes:Cy.CollectionNodes = this.NodesGraphUtils.getMatchingNodesByName(this._cy, searchTerm);
431             this.GeneralGraphUtils.zoomAll(this._cy, matchingNodes);
432         };
433
434         scope.saveChangedCapabilityProperties = ():Promise<PropertyBEModel[]> => {
435             return new Promise<PropertyBEModel[]>((resolve) => {
436                 const capabilityPropertiesBE:PropertyBEModel[] = this.ConnectionWizardServiceNg2.changedCapabilityProperties.map((prop) => {
437                     prop.value = prop.getJSONValue();
438                     const propBE = new PropertyBEModel(prop);
439                     propBE.parentUniqueId = this.ConnectionWizardServiceNg2.selectedMatch.relationship.relation.capabilityOwnerId;
440                     return propBE;
441                 });
442                 if (capabilityPropertiesBE.length > 0) {
443                     // if there are capability properties to update, then first update capability properties and then resolve promise
444                     this.ComponentInstanceServiceNg2
445                         .updateInstanceCapabilityProperties(
446                             scope.component,
447                             this.ConnectionWizardServiceNg2.selectedMatch.toNode,
448                             this.ConnectionWizardServiceNg2.selectedMatch.capability,
449                             capabilityPropertiesBE
450                         )
451                         .subscribe((response) => {
452                             console.log("Update resource instance capability properties response: ", response);
453                             this.ConnectionWizardServiceNg2.changedCapabilityProperties = [];
454                             resolve(capabilityPropertiesBE);
455                         });
456                 } else {
457                     // no capability properties to update, immediately resolve promise
458                     resolve(capabilityPropertiesBE);
459                 }
460             });
461         };
462
463         scope.createLinkFromMenu = ():void => {
464             scope.isLinkMenuOpen = false;
465
466             scope.saveChangedCapabilityProperties().then(() => {
467                 //create link:
468                 this.CompositionGraphLinkUtils
469                     .createLinkFromMenu(this._cy, this.ConnectionWizardServiceNg2.selectedMatch, scope.component);
470             });
471         };
472
473         scope.hideRelationMenu = () => {
474             this.commonGraphUtils.safeApply(scope, () => {
475                 delete scope.canvasMenuProps;
476                 this.$timeout.cancel(scope.relationMenuTimeout);
477             });
478         };
479
480         scope.createOrUpdateServicePath = (data:any) => {
481             this.servicePathGraphUtils.createOrUpdateServicePath(scope, data);
482         };
483         scope.deletePathsOnCy = () => {
484             this.servicePathGraphUtils.deletePathsFromGraph(this._cy, <Service> scope.component);
485         };
486         scope.drawPathOnCy = (data:ForwardingPath) => {
487             this.servicePathGraphUtils.drawPath(this._cy, data, <Service> scope.component);
488         };
489
490         scope.viewRelation = (link:Cy.CollectionEdges) => {
491             scope.hideRelationMenu();
492
493             const linkData = link.data();
494             const sourceNode:CompositionCiNodeBase = link.source().data();
495             const targetNode:CompositionCiNodeBase = link.target().data();
496             const relationship:Relationship = linkData.relation.relationships[0];
497
498             scope.component.getRelationRequirementCapability(relationship, sourceNode.componentInstance, targetNode.componentInstance).then((objReqCap) => {
499                 const capability = objReqCap.capability;
500                 const requirement = objReqCap.requirement;
501
502                 this.ConnectionWizardServiceNg2.currentComponent = scope.component;
503                 this.ConnectionWizardServiceNg2.connectRelationModel = new ConnectRelationModel(sourceNode, targetNode, []);
504                 this.ConnectionWizardServiceNg2.selectedMatch = new Match(requirement, capability, true, linkData.source, linkData.target);
505                 this.ConnectionWizardServiceNg2.selectedMatch.relationship = relationship;
506
507                 const title = `Connection Properties`;
508                 const saveButton:ButtonModel = new ButtonModel('Save', 'blue', () => {
509                     scope.saveChangedCapabilityProperties().then(() => {
510                         this.ModalServiceNg2.closeCurrentModal();
511                     })
512                 });
513                 const cancelButton:ButtonModel = new ButtonModel('Cancel', 'white', () => {
514                     this.ModalServiceNg2.closeCurrentModal();
515                 });
516                 const modal = new ModalModel('xl', title, '', [saveButton, cancelButton]);
517                 const modalInstance = this.ModalServiceNg2.createCustomModal(modal);
518                 this.ModalServiceNg2.addDynamicContentToModal(modalInstance, ConnectionPropertiesViewComponent);
519                 modalInstance.instance.open();
520
521                 new Promise((resolve) => {
522                     if (!this.ConnectionWizardServiceNg2.selectedMatch.capability.properties) {
523                         this.ComponentInstanceServiceNg2.getInstanceCapabilityProperties(scope.component, linkData.target, capability)
524                             .subscribe(() => {
525                                 resolve();
526                             }, (error) => {
527                             });
528                     } else {
529                         resolve();
530                     }
531                 }).then(() => {
532                     this.ModalServiceNg2.addDynamicContentToModal(modalInstance, ConnectionPropertiesViewComponent);
533                 })
534
535             }, (error) => {
536             });
537         };
538
539         scope.deleteRelation = (link:Cy.CollectionEdges) => {
540             scope.hideRelationMenu();
541
542             //if multiple edges selected, delete the VL itself so edges get deleted automatically
543             if (this._cy.$('edge:selected').length > 1) {
544                 this.NodesGraphUtils.deleteNode(this._cy, scope.component, this._cy.$('node:selected'));
545             } else {
546                 this.CompositionGraphLinkUtils.deleteLink(this._cy, scope.component, true, link);
547             }
548         };
549
550         /*
551          scope.hideAssetPopover = ():void => {
552
553          this.commonGraphUtils.safeApply(scope, () => {
554          scope.assetPopoverOpen = false;
555          scope.assetPopoverObj = null;
556          });
557          };
558
559          scope.deleteNode = (nodeId:string):void => {
560          if (!scope.isViewOnly) {
561          this.NodesGraphUtils.confirmDeleteNode(nodeId, this._cy, scope.component);
562          //scope.hideAssetPopover();
563          }
564          };*/
565     }
566
567     private registerCytoscapeGraphEvents(scope:ICompositionGraphScope) {
568
569         this._cy.on('addedgemouseup', (event, data) => {
570             scope.relationMenuDirectiveObj = this.CompositionGraphLinkUtils.onLinkDrawn(this._cy, data.source, data.target);
571             if (scope.relationMenuDirectiveObj != null) {
572                 this.ConnectionWizardServiceNg2.setRelationMenuDirectiveObj(scope.relationMenuDirectiveObj);
573                 this.ConnectionWizardServiceNg2.currentComponent = scope.component;
574                 //TODO: init with the selected values
575                 this.ConnectionWizardServiceNg2.selectedMatch = null;
576
577                 let steps:Array<StepModel> = [];
578                 let fromNodeName:string = scope.relationMenuDirectiveObj.fromNode.componentInstance.name;
579                 let toNodeName:string = scope.relationMenuDirectiveObj.toNode.componentInstance.name;
580                 steps.push(new StepModel(fromNodeName, FromNodeStepComponent));
581                 steps.push(new StepModel(toNodeName, ToNodeStepComponent));
582                 steps.push(new StepModel('Properties', PropertiesStepComponent));
583                 let wizardTitle = 'Connect: ' + fromNodeName + ' to ' + toNodeName;
584                 let modalInstance = this.ModalServiceNg2.createMultiStepsWizard(wizardTitle, steps, scope.createLinkFromMenu, ConnectionWizardHeaderComponent);
585                 modalInstance.instance.open();
586
587                 //
588                 // this.ModalServiceNg2.createMultiStepsWizard('Connect', )Connect
589                 // scope.$apply(() => {
590                 //     scope.isLinkMenuOpen = true;
591                 // });
592             }
593         });
594         this._cy.on('tapstart', 'node', (event:Cy.EventObject) => {
595             scope.isOnDrag = true;
596             this._currentlyCLickedNodePosition = angular.copy(event.cyTarget[0].position()); //update node position on drag
597             if (event.cyTarget.data().isUcpe) {
598                 this._cy.nodes('.ucpe-cp').unlock();
599                 event.cyTarget.style('opacity', 0.5);
600             }
601             //scope.hideAssetPopover();
602         });
603
604         this._cy.on('drag', 'node', (event:Cy.EventObject) => {
605
606             if (event.cyTarget.data().isDraggable) {
607                 event.cyTarget.style({'overlay-opacity': 0.24});
608                 if (this.GeneralGraphUtils.isValidDrop(this._cy, event.cyTarget)) {
609                     event.cyTarget.style({'overlay-color': GraphColors.NODE_BACKGROUND_COLOR});
610                 } else {
611                     event.cyTarget.style({'overlay-color': GraphColors.NODE_OVERLAPPING_BACKGROUND_COLOR});
612                 }
613             }
614
615             if (event.cyTarget.data().isUcpe) {
616                 let pos = event.cyTarget.position();
617
618                 this._cy.nodes('[?isInsideGroup]').positions((i, node)=> {
619                     return {
620                         x: pos.x + node.data("ucpeOffset").x,
621                         y: pos.y + node.data("ucpeOffset").y
622                     }
623                 });
624             }
625         });
626
627         /* this._cy.on('mouseover', 'node', (event:Cy.EventObject) => {
628          if (!this._cy.scratch('_edge_editation_highlights')) {
629          this.commonGraphUtils.safeApply(scope, () => {
630          this.showNodePopoverMenu(scope, event.cyTarget[0]);
631          });
632          }
633          });
634
635          this._cy.on('mouseout', 'node', (event:Cy.EventObject) => {
636          scope.hideAssetPopover();
637          });*/
638
639
640         this._cy.on('handlemouseover', (event, payload) => {
641
642             if (payload.node.grabbed() || this._cy.scratch('_edge_editation_highlights') === true) { //no need to add opacity while we are dragging and hovering othe nodes- or if opacity was already calculated for these nodes
643                 return;
644             }
645
646             if (scope.zoneTagMode) {
647                 scope.zoneTagMode = scope.zones[scope.activeZoneInstance.type].getHoverTagModeId(); 
648                 return;
649             }
650
651             let nodesData = this.NodesGraphUtils.getAllNodesData(this._cy.nodes());
652             let nodesLinks = this.GeneralGraphUtils.getAllCompositionCiLinks(this._cy);
653
654             let linkableNodes = this.commonGraphUtils.getLinkableNodes(this._cy, payload.node);
655             let filteredNodesData = this.matchCapabilitiesRequirementsUtils.findMatchingNodes(payload.node.data().componentInstance, linkableNodes, nodesLinks);
656             this.matchCapabilitiesRequirementsUtils.highlightMatchingComponents(filteredNodesData, this._cy);
657             this.matchCapabilitiesRequirementsUtils.fadeNonMachingComponents(filteredNodesData, nodesData, this._cy, payload.node.data());
658
659             this._cy.scratch()._edge_editation_highlights = true;
660             /*scope.hideAssetPopover();*/
661         });
662
663         this._cy.on('handlemouseout', () => {
664             if (scope.zoneTagMode) {
665                 scope.zoneTagMode = scope.zones[scope.activeZoneInstance.type].getTagModeId();
666                 return;
667             }
668             if (this._cy.scratch('_edge_editation_highlights') === true) {
669                 this._cy.removeScratch('_edge_editation_highlights');
670                 this._cy.emit('hidehandles');
671                 this.matchCapabilitiesRequirementsUtils.resetFadedNodes(this._cy);
672             }
673         });
674
675
676         this._cy.on('tapend', (event:Cy.EventObject) => {
677             scope.isOnDrag = false;
678             if (scope.zoneTagMode) {
679                 return;
680             }
681             if (event.cyTarget === this._cy) { //On Background clicked
682                 if (this._cy.$('node:selected').length === 0) { //if the background click but not dragged
683                     if (scope.activeZoneInstance) {
684                         scope.unsetActiveZoneInstance();
685                     }
686                     this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED);
687                 }
688                 scope.hideRelationMenu();
689             }
690
691             else if (event.cyTarget.isEdge()) { //On Edge clicked
692                 this.CompositionGraphLinkUtils.handleLinkClick(this._cy, event);
693                 if (event.cyTarget.data().type === CompositionCiServicePathLink.LINK_TYPE) {
694                     return;
695                 }
696                 this.openModifyLinkMenu(scope, this.CompositionGraphLinkUtils.getModifyLinkMenu(event.cyTarget[0], event), 6000);
697             }
698
699             else { //On Node clicked
700
701                 this._cy.nodes(':grabbed').style({'overlay-opacity': 0});
702
703                 let isUcpe:boolean = event.cyTarget.data().isUcpe;
704                 let newPosition = event.cyTarget[0].position();
705                 //node position changed (drop after drag event) - we need to update position
706                 if (this._currentlyCLickedNodePosition.x !== newPosition.x || this._currentlyCLickedNodePosition.y !== newPosition.y) {
707                     let nodesMoved:Cy.CollectionNodes = this._cy.$(':grabbed');
708                     if (isUcpe) {
709                         nodesMoved = nodesMoved.add(this._cy.nodes('[?isInsideGroup]:free')); //'child' nodes will not be recognized as "grabbed" elements within cytoscape. manually add them to collection of nodes moved.
710                     }
711                     this.NodesGraphUtils.onNodesPositionChanged(this._cy, scope.component, nodesMoved);
712                 } else {
713                     this.$log.debug('composition-graph::onNodeSelectedEvent:: fired');
714                     if (scope.activeZoneInstance) {
715                         scope.unsetActiveZoneInstance();
716                     }
717                     scope.$apply(() => {
718                         this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_NODE_SELECTED, event.cyTarget.data().componentInstance);
719                         //open node popover menu
720                         //this.showNodePopoverMenu(scope, event.cyTarget[0]);
721                     });
722                 }
723
724                 if (isUcpe) {
725                     this._cy.nodes('.ucpe-cp').lock();
726                     event.cyTarget.style('opacity', 1);
727                 }
728
729             }
730         });
731
732         this._cy.on('boxselect', 'node', (event:Cy.EventObject) => {
733             scope.unsetActiveZoneInstance();
734             this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_NODE_SELECTED, event.cyTarget.data().componentInstance);
735         });
736
737         this._cy.on('canvasredraw', (event:Cy.EventObject) => {
738             if (scope.zoneTagMode) {
739                 this.compositionGraphZoneUtils.showZoneTagIndications(this._cy, scope.activeZoneInstance);
740             }
741         });
742
743         this._cy.on('handletagclick', (event:Cy.EventObject, eventData:any) => {
744             this.compositionGraphZoneUtils.handleTagClick(this._cy, scope.activeZoneInstance, eventData.nodeId);
745
746
747         });
748     }
749
750     private openModifyLinkMenu = (scope:ICompositionGraphScope, linkMenuObject:LinkMenu, timeOutInMilliseconds?:number) => {
751         scope.hideRelationMenu();
752         this.$timeout(() => {
753             scope.canvasMenuProps = {
754                 open: true,
755                 styleClass: 'w-sdc-canvas-menu-list',
756                 items: [],
757                 position: {
758                     x: `${linkMenuObject.position.x}px`,
759                     y: `${linkMenuObject.position.y}px`
760                 }
761             };
762
763             if (this._cy.$('edge:selected').length === 1) {
764                 scope.canvasMenuProps.items.push({
765                     contents: 'View',
766                     styleClass: 'w-sdc-canvas-menu-item-view',
767                     action: () => {
768                         scope.viewRelation(<Cy.CollectionEdges>linkMenuObject.link);
769                     }
770                 });
771             }
772             if(!scope.isViewOnly){
773                 scope.canvasMenuProps.items.push({
774                     contents: 'Delete',
775                     styleClass: 'w-sdc-canvas-menu-item-delete',
776                     action: () => {
777                         scope.deleteRelation(<Cy.CollectionEdges>linkMenuObject.link);
778                     }
779                 });
780             }
781             scope.relationMenuTimeout = this.$timeout(() => {
782                 scope.hideRelationMenu();
783             }, timeOutInMilliseconds ? timeOutInMilliseconds : 6000);
784         });
785     };
786
787     private initGraphNodes(componentInstances:ComponentInstance[], isViewOnly:boolean) {
788
789
790         setTimeout(()=> {
791             let handles = new CytoscapeEdgeEditation;
792             handles.init(this._cy);
793             if (!isViewOnly) { //Init nodes handle extension - enable dynamic links
794                 handles.initNodeEvents();
795                 handles.registerHandle(ComponentInstanceNodesStyle.getAddEdgeHandle());
796             }
797             handles.registerHandle(ComponentInstanceNodesStyle.getTagHandle());
798             handles.registerHandle(ComponentInstanceNodesStyle.getTaggedPolicyHandle());
799             handles.registerHandle(ComponentInstanceNodesStyle.getTaggedGroupHandle());
800         }, 0);
801
802
803         _.each(componentInstances, (instance) => {
804             let compositionGraphNode:CompositionCiNodeBase = this.NodesFactory.createNode(instance);
805             this.commonGraphUtils.addComponentInstanceNodeToGraph(this._cy, compositionGraphNode);
806         });
807     }
808
809
810     private initDropZone(scope:ICompositionGraphScope) {
811
812         if (scope.isViewOnly) {
813             return;
814         }
815         scope.dropCallback = (event:IDragDropEvent) => {
816             this.$log.debug(`composition-graph::dropCallback:: fired`);
817             this.CompositionGraphPaletteUtils.addNodeFromPalette(this._cy, event, scope.component);
818         };
819
820         scope.verifyDrop = (event:JQueryEventObject) => {
821
822             if (!this.dragElement || this.dragElement.hasClass('red')) {
823                 return false;
824             }
825             return true;
826         };
827
828         scope.beforeDropCallback = (event:IDragDropEvent):ng.IPromise<void> => {
829             let deferred:ng.IDeferred<void> = this.$q.defer<void>();
830             if (this.dragElement.hasClass('red')) {
831                 deferred.reject();
832             } else {
833                 deferred.resolve();
834             }
835
836             return deferred.promise;
837         }
838     }
839
840
841     private initZones = (scope:ICompositionGraphScope):void => {
842         scope.zones = this.compositionGraphZoneUtils.createCompositionZones();
843
844
845         scope.zoneMinimizeToggle = (zoneType:ZoneInstanceType):void => {
846             scope.zones[zoneType].minimized = !scope.zones[zoneType].minimized;
847         };
848
849         scope.zoneInstanceModeChanged = (newMode:ZoneInstanceMode, instance:ZoneInstance, zoneId:ZoneInstanceType):void => {
850             if (scope.zoneTagMode) { //we're in tag mode.
851                 if (instance == scope.activeZoneInstance && newMode == ZoneInstanceMode.NONE) { //we want to turn tag mode off.
852                     scope.zoneTagMode = null;
853                     scope.activeZoneInstance.mode = ZoneInstanceMode.SELECTED;
854                     this.compositionGraphZoneUtils.endCyTagMode(this._cy);
855                     this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_CANVAS_TAG_END, instance);
856                     
857                 }
858             } else {
859                 if (instance != scope.activeZoneInstance || (instance == scope.activeZoneInstance && newMode > ZoneInstanceMode.HOVER)) { //when active zone instance gets hover/none,dont actually change mode, just show/hide indications
860                     instance.mode = newMode;
861                 }
862
863                 if (newMode == ZoneInstanceMode.NONE) {
864                     this.compositionGraphZoneUtils.hideZoneTagIndications(this._cy);
865                     if (scope.zones[ZoneInstanceType.GROUP]) {
866                         this.compositionGraphZoneUtils.hideGroupZoneIndications(scope.zones[ZoneInstanceType.GROUP].instances);
867                     }
868                 }
869                 if (newMode >= ZoneInstanceMode.HOVER) {
870                     this.compositionGraphZoneUtils.showZoneTagIndications(this._cy, instance);
871                     if (instance.type == ZoneInstanceType.POLICY && scope.zones[ZoneInstanceType.GROUP]) {
872                         this.compositionGraphZoneUtils.showGroupZoneIndications(scope.zones[ZoneInstanceType.GROUP].instances, instance);
873                     }
874                 }
875                 if (newMode >= ZoneInstanceMode.SELECTED) {
876                     this._cy.$('node:selected').unselect();
877                     if (scope.activeZoneInstance && scope.activeZoneInstance != instance && newMode >= ZoneInstanceMode.SELECTED) {
878                         scope.activeZoneInstance.mode = ZoneInstanceMode.NONE;
879                     }
880                     scope.activeZoneInstance = instance;
881                     this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_ZONE_INSTANCE_SELECTED, instance);
882                 }
883                 if (newMode == ZoneInstanceMode.TAG) {
884                     this.compositionGraphZoneUtils.startCyTagMode(this._cy);
885                     scope.zoneTagMode = scope.zones[zoneId].getTagModeId();
886                     this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_CANVAS_TAG_START, zoneId);
887                 }
888             }
889         };
890
891         scope.zoneInstanceTagged = (taggedInstance:ZoneInstance) => {
892             scope.activeZoneInstance.addOrRemoveAssignment(taggedInstance.instanceData.uniqueId, ZoneInstanceAssignmentType.GROUPS);
893             let newHandle:string = this.compositionGraphZoneUtils.getCorrectHandleForNode(taggedInstance.instanceData.uniqueId, scope.activeZoneInstance);
894             taggedInstance.showHandle(newHandle);
895         }
896
897         scope.zoneBackgroundClicked = ():void => {
898             if (!scope.zoneTagMode && scope.activeZoneInstance) {
899                 scope.unsetActiveZoneInstance();
900             }
901         };
902
903         scope.zoneAssignmentSaveStart = () => {
904             this.LoaderService.showLoader('composition-graph');
905         }
906
907         scope.zoneAssignmentSaveComplete = (success:boolean) => {
908             this.LoaderService.hideLoader('composition-graph');
909             if (!success) {
910                 this.GeneralGraphUtils.showUpdateFailure();
911             }
912         };
913
914         scope.unsetActiveZoneInstance = ():void => {
915             if(scope.activeZoneInstance){
916                 scope.activeZoneInstance.mode = ZoneInstanceMode.NONE;
917                 scope.activeZoneInstance = null;
918                 scope.zoneTagMode = null;
919                 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED);
920             }
921         };
922     };
923
924
925     public static factory = ($q,
926                              $log,
927                              $timeout,
928                              NodesFactory,
929                              LinksGraphUtils,
930                              GeneralGraphUtils,
931                              ComponentInstanceFactory,
932                              NodesGraphUtils,
933                              EventListenerService,
934                              ComponentFactory,
935                              LoaderService,
936                              CommonGraphUtils,
937                              MatchCapabilitiesRequirementsUtils,
938                              CompositionGraphPaletteUtils,
939                              CompositionGraphZoneUtils,
940                              ComponentServiceNg2,
941                              ModalService,
942                              ConnectionWizardService,
943                              ComponentInstanceServiceNg2,
944                              ServicePathGraphUtils) => {
945         return new CompositionGraph(
946             $q,
947             $log,
948             $timeout,
949             NodesFactory,
950             LinksGraphUtils,
951             GeneralGraphUtils,
952             ComponentInstanceFactory,
953             NodesGraphUtils,
954             EventListenerService,
955             ComponentFactory,
956             LoaderService,
957             CommonGraphUtils,
958             MatchCapabilitiesRequirementsUtils,
959             CompositionGraphPaletteUtils,
960             CompositionGraphZoneUtils,
961             ComponentServiceNg2,
962             ModalService,
963             ConnectionWizardService,
964             ComponentInstanceServiceNg2,
965             ServicePathGraphUtils);
966     }
967 }
968
969 CompositionGraph.factory.$inject = [
970     '$q',
971     '$log',
972     '$timeout',
973     'NodesFactory',
974     'CompositionGraphLinkUtils',
975     'CompositionGraphGeneralUtils',
976     'ComponentInstanceFactory',
977     'CompositionGraphNodesUtils',
978     'EventListenerService',
979     'ComponentFactory',
980     'LoaderService',
981     'CommonGraphUtils',
982     'MatchCapabilitiesRequirementsUtils',
983     'CompositionGraphPaletteUtils',
984     'CompositionGraphZoneUtils',
985     'ComponentServiceNg2',
986     'ModalServiceNg2',
987     'ConnectionWizardServiceNg2',
988     'ComponentInstanceServiceNg2',
989     'ServicePathGraphUtils'
990 ];