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