c48231f2c6cadbb7295b6602a4f0c3272f2d36c1
[sdc.git] / catalog-ui / src / app / ng2 / pages / composition / graph / composition-graph.component.ts
1 /**
2  * Created by ob0695 on 4/24/2018.
3  */
4 import { AfterViewInit, Component, ElementRef, HostBinding, Input } from '@angular/core';
5 import { Select, Store } from '@ngxs/store';
6 import {
7     ButtonModel,
8     Component as TopologyTemplate,
9     ComponentInstance,
10     CompositionCiNodeBase,
11     ConnectRelationModel,
12     GroupInstance,
13     LeftPaletteComponent,
14     LinkMenu,
15     Match,
16     ModalModel,
17     NodesFactory,
18     Point,
19     PolicyInstance,
20     PropertyBEModel,
21     Relationship,
22     StepModel,
23     Zone,
24     ZoneInstance,
25     ZoneInstanceAssignmentType,
26     ZoneInstanceMode,
27     ZoneInstanceType
28 } from 'app/models';
29 import { ForwardingPath } from 'app/models/forwarding-path';
30 import { CompositionCiServicePathLink } from 'app/models/graph/graph-links/composition-graph-links/composition-ci-service-path-link';
31 import { UIZoneInstanceObject } from 'app/models/ui-models/ui-zone-instance-object';
32 import { CompositionService } from 'app/ng2/pages/composition/composition.service';
33 import { CommonGraphUtils } from 'app/ng2/pages/composition/graph/common/common-graph-utils';
34 import { ComponentInstanceNodesStyle } from 'app/ng2/pages/composition/graph/common/style/component-instances-nodes-style';
35 import { ConnectionPropertiesViewComponent } from 'app/ng2/pages/composition/graph/connection-wizard/connection-properties-view/connection-properties-view.component';
36 import { ConnectionWizardHeaderComponent } from 'app/ng2/pages/composition/graph/connection-wizard/connection-wizard-header/connection-wizard-header.component';
37 import { ConnectionWizardService } from 'app/ng2/pages/composition/graph/connection-wizard/connection-wizard.service';
38 import { FromNodeStepComponent } from 'app/ng2/pages/composition/graph/connection-wizard/from-node-step/from-node-step.component';
39 import { PropertiesStepComponent } from 'app/ng2/pages/composition/graph/connection-wizard/properties-step/properties-step.component';
40 import { ToNodeStepComponent } from 'app/ng2/pages/composition/graph/connection-wizard/to-node-step/to-node-step.component';
41 import { WorkspaceService } from 'app/ng2/pages/workspace/workspace.service';
42 import { ComponentInstanceServiceNg2 } from 'app/ng2/services/component-instance-services/component-instance.service';
43 import { TopologyTemplateService } from 'app/ng2/services/component-services/topology-template.service';
44 import { ModalService } from 'app/ng2/services/modal.service';
45 import { ComponentGenericResponse } from 'app/ng2/services/responses/component-generic-response';
46 import { ServiceGenericResponse } from 'app/ng2/services/responses/service-generic-response';
47 import { WorkspaceState } from 'app/ng2/store/states/workspace.state';
48 import { EventListenerService } from 'app/services';
49 import { ComponentInstanceFactory, EVENTS, SdcElementType } from 'app/utils';
50 import { ComponentType, GRAPH_EVENTS, GraphColors, DEPENDENCY_EVENTS , SUBSTITUTION_FILTER_EVENTS} from 'app/utils/constants';
51 import * as _ from 'lodash';
52 import { DndDropEvent } from 'ngx-drag-drop/ngx-drag-drop';
53 import { SdcUiServices } from 'onap-ui-angular';
54 import { NotificationSettings } from 'onap-ui-angular/dist/notifications/utilities/notification.config';
55 import { menuItem } from 'onap-ui-angular/dist/simple-popup-menu/menu-data.interface';
56 import { CytoscapeEdgeEditation } from '../../../../../third-party/cytoscape.js-edge-editation/CytoscapeEdgeEditation.js';
57 import { SelectedComponentType, SetSelectedComponentAction } from '../common/store/graph.actions';
58 import { GraphState } from '../common/store/graph.state';
59 import {
60     CompositionGraphGeneralUtils,
61     CompositionGraphNodesUtils,
62     CompositionGraphZoneUtils,
63     MatchCapabilitiesRequirementsUtils
64 } from './utils';
65 import { CompositionGraphLinkUtils } from './utils/composition-graph-links-utils';
66 import { CompositionGraphPaletteUtils } from './utils/composition-graph-palette-utils';
67 import { ServicePathGraphUtils } from './utils/composition-graph-service-path-utils';
68 import { RelationshipOperationsStepComponent } from "app/ng2/pages/composition/graph/connection-wizard/relationship-operations-step/relationship-operations-step.component";
69
70 declare const window: any;
71
72 @Component({
73     selector: 'composition-graph',
74     templateUrl: './composition-graph.component.html',
75     styleUrls: ['./composition-graph.component.less']
76 })
77
78 export class CompositionGraphComponent implements AfterViewInit {
79
80     @Select(WorkspaceState.isViewOnly) isViewOnly$: boolean;
81     @Select(GraphState.withSidebar) withSidebar$: boolean;
82     @Input() topologyTemplate: TopologyTemplate;
83     @HostBinding('attr.data-tests-id') dataTestId: string;
84     @Input() testId: string;
85
86     // tslint:disable:variable-name
87     private _cy: Cy.Instance;
88     private zoneTagMode: string;
89     private activeZoneInstance: ZoneInstance;
90     private zones: Zone[];
91     private currentlyClickedNodePosition: Cy.Position;
92     private dragElement: JQuery;
93     private dragComponent: ComponentInstance;
94     private componentInstanceNames: string[];
95     private topologyTemplateId: string;
96     private topologyTemplateType: string;
97
98     constructor(private elRef: ElementRef,
99                 private nodesFactory: NodesFactory,
100                 private eventListenerService: EventListenerService,
101                 private compositionGraphZoneUtils: CompositionGraphZoneUtils,
102                 private generalGraphUtils: CompositionGraphGeneralUtils,
103                 private compositionGraphLinkUtils: CompositionGraphLinkUtils,
104                 private nodesGraphUtils: CompositionGraphNodesUtils,
105                 private connectionWizardService: ConnectionWizardService,
106                 private commonGraphUtils: CommonGraphUtils,
107                 private modalService: ModalService,
108                 private compositionGraphPaletteUtils: CompositionGraphPaletteUtils,
109                 private topologyTemplateService: TopologyTemplateService,
110                 private componentInstanceService: ComponentInstanceServiceNg2,
111                 private matchCapabilitiesRequirementsUtils: MatchCapabilitiesRequirementsUtils,
112                 private store: Store,
113                 private compositionService: CompositionService,
114                 private loaderService: SdcUiServices.LoaderService,
115                 private workspaceService: WorkspaceService,
116                 private notificationService: SdcUiServices.NotificationsService,
117                 private simplePopupMenuService: SdcUiServices.simplePopupMenuService,
118                 private servicePathGraphUtils: ServicePathGraphUtils) {
119     }
120
121     ngOnInit() {
122         this.dataTestId = this.testId;
123         this.topologyTemplateId = this.workspaceService.metadata.uniqueId;
124         this.topologyTemplateType = this.workspaceService.metadata.componentType;
125
126         this.store.dispatch(new SetSelectedComponentAction({
127             component: this.topologyTemplate,
128             type: SelectedComponentType.TOPOLOGY_TEMPLATE
129         }));
130         this.eventListenerService.registerObserverCallback(EVENTS.ON_CHECKOUT, () => {
131             this.loadGraphData();
132         });
133         this.loadCompositionData();
134     }
135
136     ngAfterViewInit() {
137         this.loadGraph();
138     }
139
140     ngOnDestroy() {
141         this._cy.destroy();
142         _.forEach(GRAPH_EVENTS, (event) => {
143             this.eventListenerService.unRegisterObserver(event);
144         });
145         this.eventListenerService.unRegisterObserver(EVENTS.ON_CHECKOUT);
146         this.eventListenerService.unRegisterObserver(DEPENDENCY_EVENTS.ON_DEPENDENCY_CHANGE);
147         this.eventListenerService.unRegisterObserver(SUBSTITUTION_FILTER_EVENTS.ON_SUBSTITUTION_FILTER_CHANGE);
148     }
149
150     public isViewOnly = (): boolean => {
151         return this.store.selectSnapshot((state) => state.workspace.isViewOnly);
152     }
153
154     public zoom = (zoomIn: boolean): void => {
155         const currentZoom: number = this._cy.zoom();
156         if (zoomIn) {
157             this.generalGraphUtils.zoomGraphTo(this._cy, currentZoom + .1);
158         } else {
159             this.generalGraphUtils.zoomGraphTo(this._cy, currentZoom - .1);
160         }
161     }
162
163     public zoomAllWithoutSidebar = () => {
164         setTimeout(() => { // wait for sidebar changes to take effect before zooming
165             this.generalGraphUtils.zoomAll(this._cy);
166         });
167     }
168
169     public getAutoCompleteValues = (searchTerm: string) => {
170         if (searchTerm.length > 1) { // US requirement: only display search results after 2nd letter typed.
171             const nodes: Cy.CollectionNodes = this.nodesGraphUtils.getMatchingNodesByName(this._cy, searchTerm);
172             this.componentInstanceNames = _.map(nodes, (node) => node.data('name'));
173         } else {
174             this.componentInstanceNames = [];
175         }
176     }
177
178     public highlightSearchMatches = (searchTerm: string) => {
179         this.nodesGraphUtils.highlightMatchingNodesByName(this._cy, searchTerm);
180         const matchingNodes: Cy.CollectionNodes = this.nodesGraphUtils.getMatchingNodesByName(this._cy, searchTerm);
181         this.generalGraphUtils.zoomAll(this._cy, matchingNodes);
182     }
183
184     public onDrop = (dndEvent: DndDropEvent) => {
185         this.compositionGraphPaletteUtils.addNodeFromPalette(this._cy, dndEvent);
186     }
187
188     public openServicePathMenu = ($event): void => {
189
190         const menuConfig: menuItem[] = [];
191         if (!this.isViewOnly()) {
192             menuConfig.push({
193                 text: 'Create Service Flow',
194                 action: () => this.servicePathGraphUtils.onCreateServicePath()
195             });
196         }
197         menuConfig.push({
198             text: 'Service Flows List',
199             type: '',
200             action: () => this.servicePathGraphUtils.onListServicePath()
201         });
202         const popup = this.simplePopupMenuService.openBaseMenu(menuConfig, {
203             x: $event.x,
204             y: $event.y
205         });
206
207     }
208
209     public deletePathsOnCy = () => {
210         this.servicePathGraphUtils.deletePathsFromGraph(this._cy);
211     }
212
213     public drawPathOnCy = (data: ForwardingPath) => {
214         this.servicePathGraphUtils.drawPath(this._cy, data);
215     }
216
217     public onTapEnd = (event: Cy.EventObject) => {
218         if (this.zoneTagMode) {
219             return;
220         }
221         if (event.cyTarget === this._cy) { // On Background clicked
222             if (this._cy.$('node:selected').length === 0) { // if the background click but not dragged
223                 if (this.activeZoneInstance) {
224                     this.unsetActiveZoneInstance();
225                     this.selectTopologyTemplate();
226                 } else {
227                     this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED);
228                     this.selectTopologyTemplate();
229                 }
230
231             }
232         } else if (event.cyTarget[0].isEdge()) { // and Edge clicked
233             this.compositionGraphLinkUtils.handleLinkClick(this._cy, event);
234             if (event.cyTarget[0].data().type === CompositionCiServicePathLink.LINK_TYPE) {
235                 return;
236             }
237             this.openModifyLinkMenu(this.compositionGraphLinkUtils.getModifyLinkMenu(event.cyTarget[0], event), event);
238         } else { // On Node clicked
239
240             this._cy.nodes(':grabbed').style({'overlay-opacity': 0});
241
242             const newPosition = event.cyTarget[0].position();
243             // node position changed (drop after drag event) - we need to update position
244             if (this.currentlyClickedNodePosition.x !== newPosition.x || this.currentlyClickedNodePosition.y !== newPosition.y) {
245                 const nodesMoved: Cy.CollectionNodes = this._cy.$(':grabbed');
246                 this.nodesGraphUtils.onNodesPositionChanged(this._cy, this.topologyTemplate, nodesMoved);
247             } else {
248                 if (this.activeZoneInstance) {
249                     this.unsetActiveZoneInstance();
250                 }
251                 this.selectComponentInstance(event.cyTarget[0].data().componentInstance);
252             }
253         }
254     }
255
256     private registerCytoscapeGraphEvents() {
257
258         this._cy.on('addedgemouseup', (event, data) => {
259             const connectRelationModel: ConnectRelationModel = this.compositionGraphLinkUtils.onLinkDrawn(this._cy, data.source, data.target);
260             if (connectRelationModel != null) {
261                 this.connectionWizardService.setRelationMenuDirectiveObj(connectRelationModel);
262                 this.connectionWizardService.selectedMatch = null;
263
264                 const steps: StepModel[] = [];
265                 const fromNodeName: string = connectRelationModel.fromNode.componentInstance.name;
266                 const toNodeName: string = connectRelationModel.toNode.componentInstance.name;
267                 steps.push(new StepModel(fromNodeName, FromNodeStepComponent));
268                 steps.push(new StepModel(toNodeName, ToNodeStepComponent));
269                 steps.push(new StepModel('Properties', PropertiesStepComponent));
270                 steps.push(new StepModel('Operations', RelationshipOperationsStepComponent));
271                 const wizardTitle = 'Connect: ' + fromNodeName + ' to ' + toNodeName;
272                 const modalInstance = this.modalService.createMultiStepsWizard(wizardTitle, steps, this.createLinkFromMenu, ConnectionWizardHeaderComponent);
273                 modalInstance.instance.open();
274             }
275         });
276
277         this._cy.on('tapstart', 'node', (event: Cy.EventObject) => {
278             this.currentlyClickedNodePosition = angular.copy(event.cyTarget[0].position()); // update node position on drag
279         });
280
281         this._cy.on('drag', 'node', (event: Cy.EventObject) => {
282             if (event.cyTarget.data().componentSubType !== SdcElementType.POLICY && event.cyTarget.data().componentSubType !== SdcElementType.GROUP) {
283                 event.cyTarget.style({'overlay-opacity': 0.24});
284                 if (this.generalGraphUtils.isValidDrop(this._cy, event.cyTarget)) {
285                     event.cyTarget.style({'overlay-color': GraphColors.NODE_BACKGROUND_COLOR});
286                 } else {
287                     event.cyTarget.style({'overlay-color': GraphColors.NODE_OVERLAPPING_BACKGROUND_COLOR});
288                 }
289             }
290         });
291
292         this._cy.on('handlemouseover', (event, payload) => {
293             // no need to add opacity while we are dragging and hovering othe nodes- or if opacity was already calculated for these nodes
294             if (payload.node.grabbed() || this._cy.scratch('_edge_editation_highlights') === true) {
295                 return;
296             }
297
298             if (this.zoneTagMode) {
299                 this.zoneTagMode = this.zones[this.activeZoneInstance.type].getHoverTagModeId();
300                 return;
301             }
302
303             const nodesData = this.nodesGraphUtils.getAllNodesData(this._cy.nodes());
304             const nodesLinks = this.generalGraphUtils.getAllCompositionCiLinks(this._cy);
305             const instance = payload.node.data().componentInstance;
306             const filteredNodesData = this.matchCapabilitiesRequirementsUtils.findMatchingNodesToComponentInstance(instance, nodesData, nodesLinks);
307             this.matchCapabilitiesRequirementsUtils.highlightMatchingComponents(filteredNodesData, this._cy);
308             this.matchCapabilitiesRequirementsUtils.fadeNonMachingComponents(filteredNodesData, nodesData, this._cy, payload.node.data());
309
310             this._cy.scratch()._edge_editation_highlights = true;
311         });
312
313         this._cy.on('handlemouseout', () => {
314             if (this.zoneTagMode) {
315                 this.zoneTagMode = this.zones[this.activeZoneInstance.type].getTagModeId();
316                 return;
317             }
318             if (this._cy.scratch('_edge_editation_highlights') === true) {
319                 this._cy.removeScratch('_edge_editation_highlights');
320                 this._cy.emit('hidehandles');
321                 this.matchCapabilitiesRequirementsUtils.resetFadedNodes(this._cy);
322             }
323         });
324
325         this._cy.on('tapend', (event: Cy.EventObject) => {
326             this.onTapEnd(event);
327         });
328
329         this._cy.on('boxselect', 'node', (event: Cy.EventObject) => {
330             this.unsetActiveZoneInstance();
331             this.selectComponentInstance(event.cyTarget.data().componentInstance);
332         });
333
334         this._cy.on('canvasredraw', (event: Cy.EventObject) => {
335             if (this.zoneTagMode) {
336                 this.compositionGraphZoneUtils.showZoneTagIndications(this._cy, this.activeZoneInstance);
337             }
338         });
339
340         this._cy.on('handletagclick', (event: Cy.EventObject, eventData: any) => {
341             this.compositionGraphZoneUtils.handleTagClick(this._cy, this.activeZoneInstance, eventData.nodeId);
342         });
343     }
344
345     private initViewMode() {
346
347         if (this.isViewOnly()) {
348             // remove event listeners
349             this._cy.off('drag');
350             this._cy.off('handlemouseout');
351             this._cy.off('handlemouseover');
352             this._cy.off('canvasredraw');
353             this._cy.off('handletagclick');
354             this._cy.edges().unselectify();
355         }
356     }
357
358     private saveChangedCapabilityProperties = (): Promise<PropertyBEModel[]> => {
359         return new Promise<PropertyBEModel[]>((resolve) => {
360             const capabilityPropertiesBE: PropertyBEModel[] = this.connectionWizardService.changedCapabilityProperties.map((prop) => {
361                 prop.value = prop.getJSONValue();
362                 const propBE = new PropertyBEModel(prop);
363                 propBE.parentUniqueId = this.connectionWizardService.selectedMatch.relationship.relation.capabilityOwnerId;
364                 return propBE;
365             });
366             if (capabilityPropertiesBE.length > 0) {
367                 // if there are capability properties to update, then first update capability properties and then resolve promise
368                 this.componentInstanceService
369                     .updateInstanceCapabilityProperties(
370                         this.topologyTemplate,
371                         this.connectionWizardService.selectedMatch.toNode,
372                         this.connectionWizardService.selectedMatch.capability,
373                         capabilityPropertiesBE
374                     )
375                     .subscribe((response) => {
376                         console.log('Update resource instance capability properties response: ', response);
377                         this.connectionWizardService.changedCapabilityProperties = [];
378                         resolve(capabilityPropertiesBE);
379                     });
380             } else {
381                 // no capability properties to update, immediately resolve promise
382                 resolve(capabilityPropertiesBE);
383             }
384         });
385     }
386
387     private loadCompositionData = () => {
388         this.loaderService.activate();
389         this.topologyTemplateService.getComponentCompositionData(this.topologyTemplateId, this.topologyTemplateType).subscribe((response: ComponentGenericResponse) => {
390             if (this.topologyTemplateType === ComponentType.SERVICE) {
391                 this.compositionService.forwardingPaths = (response as ServiceGenericResponse).forwardingPaths;
392             }
393             this.compositionService.componentInstances = response.componentInstances;
394             this.compositionService.componentInstancesRelations = response.componentInstancesRelations;
395             this.compositionService.groupInstances = response.groupInstances;
396             this.compositionService.policies = response.policies;
397             this.loadGraphData();
398             this.loaderService.deactivate();
399         }, (error) => { this.loaderService.deactivate(); });
400     }
401
402     private loadGraph = () => {
403         const graphEl = this.elRef.nativeElement.querySelector('.sdc-composition-graph-wrapper');
404         this.initGraph(graphEl);
405         this.zones = this.compositionGraphZoneUtils.createCompositionZones();
406         this.registerCytoscapeGraphEvents();
407         this.registerCustomEvents();
408         this.initViewMode();
409     }
410
411     private initGraphNodes() {
412
413         setTimeout(() => {
414             const handles = new CytoscapeEdgeEditation();
415             handles.init(this._cy);
416             if (!this.isViewOnly()) { // Init nodes handle extension - enable dynamic links
417                 handles.initNodeEvents();
418                 handles.registerHandle(ComponentInstanceNodesStyle.getAddEdgeHandle());
419             }
420             handles.registerHandle(ComponentInstanceNodesStyle.getTagHandle());
421             handles.registerHandle(ComponentInstanceNodesStyle.getTaggedPolicyHandle());
422             handles.registerHandle(ComponentInstanceNodesStyle.getTaggedGroupHandle());
423         }, 0);
424
425         _.each(this.compositionService.componentInstances, (instance) => {
426             const compositionGraphNode: CompositionCiNodeBase = this.nodesFactory.createNode(instance);
427             this.commonGraphUtils.addComponentInstanceNodeToGraph(this._cy, compositionGraphNode);
428         });
429
430     }
431
432     private loadGraphData = () => {
433         this.initGraphNodes();
434         this.compositionGraphLinkUtils.initGraphLinks(this._cy, this.compositionService.componentInstancesRelations);
435         this.compositionGraphZoneUtils.initZoneInstances(this.zones);
436         setTimeout(() => { // Need setTimeout so that angular canvas changes will take effect before resize & center
437             this.generalGraphUtils.zoomAllWithMax(this._cy, 1);
438         });
439         this.componentInstanceNames = _.map(this._cy.nodes(), (node) => node.data('name'));
440     }
441
442     private initGraph(graphEl: JQuery) {
443
444         this._cy = cytoscape({
445             container: graphEl,
446             style: ComponentInstanceNodesStyle.getCompositionGraphStyle(),
447             zoomingEnabled: true,
448             maxZoom: 1.2,
449             minZoom: .1,
450             userZoomingEnabled: false,
451             userPanningEnabled: true,
452             selectionType: 'single',
453             boxSelectionEnabled: true,
454             autolock: this.isViewOnly(),
455             autoungrabify: this.isViewOnly()
456         });
457
458         // Testing Bridge that allows Cypress tests to select a component on canvas not via DOM
459         if (window.Cypress) {
460             window.testBridge = this.createCanvasTestBridge();
461         }
462     }
463
464     private createCanvasTestBridge(): any {
465         return {
466             selectComponentInstance: (componentName: string) => {
467                 const matchingNodesByName = this.nodesGraphUtils.getMatchingNodesByName(this._cy, componentName);
468                 const component = new ComponentInstance(matchingNodesByName.first().data().componentInstance);
469                 this.selectComponentInstance(component);
470             }
471         };
472     }
473
474     // -------------------------------------------- ZONES---------------------------------------------------------//
475     private zoneMinimizeToggle = (zoneType: ZoneInstanceType): void => {
476         this.zones[zoneType].minimized = !this.zones[zoneType].minimized;
477     }
478
479     private zoneInstanceModeChanged = (newMode: ZoneInstanceMode, instance: ZoneInstance, zoneId: ZoneInstanceType): void => {
480         if (this.zoneTagMode) { // we're in tag mode.
481             if (instance === this.activeZoneInstance && newMode === ZoneInstanceMode.NONE) { // we want to turn tag mode off.
482                 this.zoneTagMode = null;
483                 this.activeZoneInstance.mode = ZoneInstanceMode.SELECTED;
484                 this.compositionGraphZoneUtils.endCyTagMode(this._cy);
485                 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_CANVAS_TAG_END, instance);
486
487             }
488         } else {
489             // when active zone instance gets hover/none, don't actually change mode, just show/hide indications
490             if (instance !== this.activeZoneInstance || (instance === this.activeZoneInstance && newMode > ZoneInstanceMode.HOVER)) {
491                 instance.mode = newMode;
492             }
493
494             if (newMode === ZoneInstanceMode.NONE) {
495                 this.compositionGraphZoneUtils.hideZoneTagIndications(this._cy);
496                 if (this.zones[ZoneInstanceType.GROUP]) {
497                     this.compositionGraphZoneUtils.hideGroupZoneIndications(this.zones[ZoneInstanceType.GROUP].instances);
498                 }
499             }
500             if (newMode >= ZoneInstanceMode.HOVER) {
501                 this.compositionGraphZoneUtils.showZoneTagIndications(this._cy, instance);
502                 if (instance.type === ZoneInstanceType.POLICY && this.zones[ZoneInstanceType.GROUP]) {
503                     this.compositionGraphZoneUtils.showGroupZoneIndications(this.zones[ZoneInstanceType.GROUP].instances, instance);
504                 }
505             }
506             if (newMode >= ZoneInstanceMode.SELECTED) {
507                 this._cy.$('node:selected').unselect();
508                 if (this.activeZoneInstance && this.activeZoneInstance !== instance && newMode >= ZoneInstanceMode.SELECTED) {
509                     this.activeZoneInstance.mode = ZoneInstanceMode.NONE;
510                 }
511                 this.activeZoneInstance = instance;
512                 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_ZONE_INSTANCE_SELECTED, instance);
513                 this.store.dispatch(new SetSelectedComponentAction({
514                     component: instance.instanceData,
515                     type: SelectedComponentType[ZoneInstanceType[instance.type]]
516                 }));
517             }
518             if (newMode === ZoneInstanceMode.TAG) {
519                 this.compositionGraphZoneUtils.startCyTagMode(this._cy);
520                 this.zoneTagMode = this.zones[zoneId].getTagModeId();
521                 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_CANVAS_TAG_START, zoneId);
522             }
523         }
524     }
525
526     private zoneInstanceTagged = (taggedInstance: ZoneInstance) => {
527         this.activeZoneInstance.addOrRemoveAssignment(taggedInstance.instanceData.uniqueId, ZoneInstanceAssignmentType.GROUPS);
528         const newHandle: string = this.compositionGraphZoneUtils.getCorrectHandleForNode(taggedInstance.instanceData.uniqueId, this.activeZoneInstance);
529         taggedInstance.showHandle(newHandle);
530     }
531
532     private unsetActiveZoneInstance = (): void => {
533         if (this.activeZoneInstance) {
534             this.activeZoneInstance.mode = ZoneInstanceMode.NONE;
535             this.activeZoneInstance = null;
536             this.zoneTagMode = null;
537         }
538     }
539
540     private selectComponentInstance = (componentInstance: ComponentInstance) => {
541         this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_NODE_SELECTED, componentInstance);
542         this.store.dispatch(new SetSelectedComponentAction({
543             component: componentInstance,
544             type: SelectedComponentType.COMPONENT_INSTANCE
545         }));
546     }
547
548     private selectTopologyTemplate = () => {
549         this.store.dispatch(new SetSelectedComponentAction({
550             component: this.topologyTemplate,
551             type: SelectedComponentType.TOPOLOGY_TEMPLATE
552         }));
553     }
554
555     private zoneBackgroundClicked = (): void => {
556         if (!this.zoneTagMode && this.activeZoneInstance) {
557             this.unsetActiveZoneInstance();
558             this.selectTopologyTemplate();
559         }
560     }
561
562     private zoneAssignmentSaveStart = () => {
563         this.loaderService.activate();
564     }
565
566     private zoneAssignmentSaveComplete = (success: boolean) => {
567         this.loaderService.deactivate();
568         if (!success) {
569             this.notificationService.push(new NotificationSettings('error', 'Update Failed', 'Error'));
570         }
571     }
572
573     private deleteZoneInstance = (deletedInstance: UIZoneInstanceObject) => {
574         if (deletedInstance.type === ZoneInstanceType.POLICY) {
575             this.compositionService.policies = this.compositionService.policies.filter((policy) => policy.uniqueId !== deletedInstance.uniqueId);
576         } else if (deletedInstance.type === ZoneInstanceType.GROUP) {
577             this.compositionService.groupInstances = this.compositionService.groupInstances.filter((group) => group.uniqueId !== deletedInstance.uniqueId);
578         }
579         // remove it from zones
580         this.zones[deletedInstance.type].removeInstance(deletedInstance.uniqueId);
581         if (deletedInstance.type === ZoneInstanceType.GROUP && !_.isEmpty(this.zones[ZoneInstanceType.POLICY])) {
582             this.compositionGraphZoneUtils.updateTargetsOrMembersOnCanvasDelete(deletedInstance.uniqueId, [this.zones[ZoneInstanceType.POLICY]], ZoneInstanceAssignmentType.GROUPS);
583         }
584         this.selectTopologyTemplate();
585     }
586     // -------------------------------------------------------------------------------------------------------------//
587
588     private registerCustomEvents() {
589
590         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_GROUP_INSTANCE_UPDATE, (groupInstance: GroupInstance) => {
591             this.compositionGraphZoneUtils.findAndUpdateZoneInstanceData(this.zones, groupInstance);
592             this.notificationService.push(new NotificationSettings('success', 'Group Updated', 'Success'));
593         });
594
595         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_POLICY_INSTANCE_UPDATE, (policyInstance: PolicyInstance) => {
596             this.compositionGraphZoneUtils.findAndUpdateZoneInstanceData(this.zones, policyInstance);
597             this.notificationService.push(new NotificationSettings('success', 'Policy Updated', 'Success'));
598         });
599
600         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HOVER_IN, (leftPaletteComponent: LeftPaletteComponent) => {
601             this.compositionGraphPaletteUtils.onComponentHoverIn(leftPaletteComponent, this._cy);
602         });
603
604         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_ADD_ZONE_INSTANCE_FROM_PALETTE,
605             (component: TopologyTemplate, paletteComponent: LeftPaletteComponent, startPosition: Point) => {
606
607                 const zoneType: ZoneInstanceType = this.compositionGraphZoneUtils.getZoneTypeForPaletteComponent(paletteComponent.categoryType);
608                 this.compositionGraphZoneUtils.showZone(this.zones[zoneType]);
609
610                 this.loaderService.activate();
611                 this.compositionGraphZoneUtils.createZoneInstanceFromLeftPalette(zoneType, paletteComponent.type).subscribe((zoneInstance: ZoneInstance) => {
612                     this.loaderService.deactivate();
613                     this.compositionGraphZoneUtils.addInstanceToZone(this.zones[zoneInstance.type], zoneInstance, true);
614                     this.compositionGraphZoneUtils.createPaletteToZoneAnimation(startPosition, zoneType, zoneInstance);
615                 }, (error) => {
616                     this.loaderService.deactivate();
617                 });
618             });
619
620         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HOVER_OUT, () => {
621             this._cy.emit('hidehandles');
622             this.matchCapabilitiesRequirementsUtils.resetFadedNodes(this._cy);
623         });
624
625         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_DRAG_START, (dragElement, dragComponent) => {
626             this.dragElement = dragElement;
627             this.dragComponent = ComponentInstanceFactory.createComponentInstanceFromComponent(dragComponent, this.workspaceService.metadata.categories[0].useServiceSubstitutionForNestedServices);
628         });
629
630         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_DRAG_ACTION, (position: Point) => {
631             const draggedElement = document.getElementById('draggable_element');
632             draggedElement.className = this.compositionGraphPaletteUtils.isDragValid(this._cy, position) ? 'valid-drag' : 'invalid-drag';
633         });
634
635         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_DROP, (event: DndDropEvent) => {
636             this.onDrop(event);
637         });
638
639         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_COMPONENT_INSTANCE_NAME_CHANGED, (component: ComponentInstance) => {
640             const selectedNode = this._cy.getElementById(component.uniqueId);
641             selectedNode.data().componentInstance.name = component.name;
642             selectedNode.data('name', component.name); // used for tooltip
643             selectedNode.data('displayName', selectedNode.data().getDisplayName()); // abbreviated
644         });
645
646         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, (componentInstanceId: string) => {
647             const nodeToDelete = this._cy.getElementById(componentInstanceId);
648             this.nodesGraphUtils.deleteNode(this._cy, this.topologyTemplate, nodeToDelete);
649             this.selectTopologyTemplate();
650         });
651
652         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_ZONE_INSTANCE, (deletedInstance: UIZoneInstanceObject) => {
653             this.deleteZoneInstance(deletedInstance);
654         });
655
656         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE_SUCCESS, (componentInstanceId: string) => {
657             if (!_.isEmpty(this.zones)) {
658                 this.compositionGraphZoneUtils.updateTargetsOrMembersOnCanvasDelete(componentInstanceId, this.zones, ZoneInstanceAssignmentType.COMPONENT_INSTANCES);
659             }
660         });
661
662         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_EDGE, (releaseLoading: boolean, linksToDelete: Cy.CollectionEdges) => {
663             this.compositionGraphLinkUtils.deleteLink(this._cy, this.topologyTemplate, releaseLoading, linksToDelete);
664         });
665
666         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_VERSION_CHANGED, (component: ComponentInstance) => {
667             // Remove everything from graph and reload it all
668             this._cy.elements().remove();
669             this.loadCompositionData();
670             setTimeout(() => { this._cy.getElementById(component.uniqueId).select(); }, 1000);
671             this.selectComponentInstance(component);
672         });
673         this.eventListenerService.registerObserverCallback(DEPENDENCY_EVENTS.ON_DEPENDENCY_CHANGE, (ischecked: boolean) => {
674             if (ischecked) {
675                 this._cy.$('node:selected').addClass('dependent');
676             } else {
677                 // 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.
678                 this._cy.$('node:selected').removeClass('dependent');
679                 this._cy.$('node:selected').style({'background-image': this._cy.$('node:selected').data('originalImg')});
680             }
681         });
682     }
683     private createLinkFromMenu = (): void => {
684         this.saveChangedCapabilityProperties().then(() => {
685             this.compositionGraphLinkUtils.createLinkFromMenu(this._cy, this.connectionWizardService.selectedMatch);
686         });
687     }
688
689     private deleteRelation = (link: Cy.CollectionEdges) => {
690         // if multiple edges selected, delete the VL itself so edges get deleted automatically
691         if (this._cy.$('edge:selected').length > 1) {
692             this.nodesGraphUtils.deleteNode(this._cy, this.topologyTemplate, this._cy.$('node:selected'));
693         } else {
694             this.compositionGraphLinkUtils.deleteLink(this._cy, this.topologyTemplate, true, link);
695         }
696     }
697
698     private viewRelation = (link: Cy.CollectionEdges) => {
699
700         const linkData = link.data();
701         const sourceNode: CompositionCiNodeBase = link.source().data();
702         const targetNode: CompositionCiNodeBase = link.target().data();
703         const relationship: Relationship = linkData.relation.relationships[0];
704
705         this.compositionGraphLinkUtils.getRelationRequirementCapability(relationship, sourceNode.componentInstance, targetNode.componentInstance).then((objReqCap) => {
706             const capability = objReqCap.capability;
707             const requirement = objReqCap.requirement;
708
709             this.connectionWizardService.connectRelationModel = new ConnectRelationModel(sourceNode, targetNode, []);
710             this.connectionWizardService.selectedMatch = new Match(requirement, capability, true, linkData.source, linkData.target);
711             this.connectionWizardService.selectedMatch.relationship = relationship;
712
713             const title = `Connection Properties`;
714             const saveButton: ButtonModel = new ButtonModel('Save', 'blue', () => {
715                 this.saveChangedCapabilityProperties().then(() => {
716                     this.modalService.closeCurrentModal();
717                 });
718             });
719             const cancelButton: ButtonModel = new ButtonModel('Cancel', 'white', () => {
720                 this.modalService.closeCurrentModal();
721             });
722             const modal = new ModalModel('xl', title, '', [saveButton, cancelButton]);
723             const modalInstance = this.modalService.createCustomModal(modal);
724             this.modalService.addDynamicContentToModal(modalInstance, ConnectionPropertiesViewComponent);
725             modalInstance.instance.open();
726
727             new Promise((resolve) => {
728                 if (!this.connectionWizardService.selectedMatch.capability.properties) {
729                     this.componentInstanceService.getInstanceCapabilityProperties(this.topologyTemplateType, this.topologyTemplateId, linkData.target, capability)
730                         .subscribe(() => {
731                             resolve();
732                         }, () => { /* do nothing */ });
733                 } else {
734                     resolve();
735                 }
736             }).then(() => {
737                 this.modalService.addDynamicContentToModal(modalInstance, ConnectionPropertiesViewComponent);
738             });
739         }, () => { /* do nothing */ });
740     }
741
742     private openModifyLinkMenu = (linkMenuObject: LinkMenu, $event) => {
743
744         const menuConfig: menuItem[] = [{
745             text: 'View',
746             iconName: 'eye-o',
747             iconType: 'common',
748             iconMode: 'secondary',
749             iconSize: 'small',
750             type: '',
751             action: () => this.viewRelation(linkMenuObject.link as Cy.CollectionEdges)
752         }];
753
754         if (!this.isViewOnly()) {
755             menuConfig.push({
756                 text: 'Delete',
757                 iconName: 'trash-o',
758                 iconType: 'common',
759                 iconMode: 'secondary',
760                 iconSize: 'small',
761                 type: '',
762                 action: () => this.deleteRelation(linkMenuObject.link as Cy.CollectionEdges)
763             });
764         }
765         this.simplePopupMenuService.openBaseMenu(menuConfig, {
766             x: $event.originalEvent.x,
767             y: $event.originalEvent.y
768         });
769     }
770
771 }