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