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