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