2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 import * as _ from "lodash";
31 CompositionCiNodeBase,
38 import { ComponentInstanceFactory, ComponentFactory, GRAPH_EVENTS, GraphColors, GraphUIObjects, ModalsHandler } 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";
65 ZoneInstance, ZoneInstanceMode, ZoneInstanceType,
66 ZoneInstanceAssignmentType
67 } from "app/models/graph/zones/zone-instance";
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";
76 export interface ICompositionGraphScope extends ng.IScope {
86 activeZoneInstance: ZoneInstance;
88 zoneMinimizeToggle(zoneType: ZoneInstanceType): void;
89 zoneInstanceTagged(taggedInstance: ZoneInstance): void;
90 zoneInstanceModeChanged(newMode: ZoneInstanceMode, instance: ZoneInstance, zoneId: ZoneInstanceType);
91 unsetActiveZoneInstance(): void;
92 clickOutsideZoneInstance(): void;
93 zoneAssignmentSaveStart(): void;
94 zoneAssignmentSaveComplete(success: boolean): void;
96 // Link menu - create link menu
97 relationMenuDirectiveObj: ConnectRelationModel;
98 isLinkMenuOpen: boolean;
99 createLinkFromMenu: (chosenMatch: Match, vl: Component) => void;
100 saveChangedCapabilityProperties: () => Promise<PropertyBEModel[]>;
102 //modify link menu - for now only delete menu
103 relationMenuTimeout: ng.IPromise<any>;
104 linkMenuObject: LinkMenu;
107 //left palette functions callbacks
108 dropCallback(event: JQueryEventObject, ui: any): void;
109 beforeDropCallback(event: IDragDropEvent): void;
110 verifyDrop(event: JQueryEventObject, ui: any): void;
113 viewRelation(link: Cy.CollectionEdges): void;
114 deleteRelation(link: Cy.CollectionEdges): void;
117 //search,zoom in/out/all
118 componentInstanceNames: Array<string>; //id, name
119 zoom(zoomIn: boolean): void;
120 zoomAllWithoutSidebar(): void;
121 getAutoCompleteValues(searchTerm: string): void;
122 highlightSearchMatches(searchTerm: string): void;
124 canvasMenuProps: any;
126 createOrUpdateServicePath(data: any): void;
127 deletePathsOnCy(): void;
128 drawPathOnCy(data: ForwardingPath): void;
129 selectedPathId: string;
131 copyComponentInstance(): void;
132 pasteComponentInstance(event: IDragDropEvent): void;
134 origComponentId: string;
137 export class CompositionGraph implements ng.IDirective {
138 private _cy: Cy.Instance;
139 private _currentlyCLickedNodePosition: Cy.Position;
140 private dragElement: JQuery;
141 private dragComponent: ComponentInstance;
142 private cyBackgroundClickEvent: any;
144 constructor(private $q: ng.IQService,
145 private $log: ng.ILogService,
146 private $timeout: ng.ITimeoutService,
147 private NodesFactory: NodesFactory,
148 private CompositionGraphLinkUtils: CompositionGraphLinkUtils,
149 private GeneralGraphUtils: CompositionGraphGeneralUtils,
150 private ComponentInstanceFactory: ComponentInstanceFactory,
151 private NodesGraphUtils: CompositionGraphNodesUtils,
152 private eventListenerService: EventListenerService,
153 private ComponentFactory: ComponentFactory,
154 private LoaderService: LoaderService,
155 private commonGraphUtils: CommonGraphUtils,
156 private matchCapabilitiesRequirementsUtils: MatchCapabilitiesRequirementsUtils,
157 private CompositionGraphPaletteUtils: CompositionGraphPaletteUtils,
158 private compositionGraphZoneUtils: CompositionGraphZoneUtils,
159 private ComponentServiceNg2: ComponentServiceNg2,
160 private ModalServiceNg2: ModalService,
161 private ConnectionWizardServiceNg2: ConnectionWizardService,
162 private ComponentInstanceServiceNg2: ComponentInstanceServiceNg2,
163 private servicePathGraphUtils: ServicePathGraphUtils,
164 private ModalsHandler: ModalsHandler) {
169 template = require('./composition-graph.html');
176 link = (scope: ICompositionGraphScope, el: JQuery) => {
177 this.loadGraph(scope, el);
179 if (!scope.component.groupInstances || !scope.component.policies) {
180 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_COMPOSITION_GRAPH_DATA_LOADED, () => {
181 this.loadGraphData(scope);
184 this.loadGraphData(scope);
188 scope.$on('$destroy', () => {
190 _.forEach(GRAPH_EVENTS, (event) => {
191 this.eventListenerService.unRegisterObserver(event);
193 this.eventListenerService.unRegisterObserver(EVENTS.SHOW_LOADER_EVENT + 'composition-graph');
194 this.eventListenerService.unRegisterObserver(EVENTS.HIDE_LOADER_EVENT + 'composition-graph');
199 private loadGraphData = (scope: ICompositionGraphScope) => {
200 this.initGraphNodes(scope.component.componentInstances, scope.isViewOnly);
201 this.commonGraphUtils.initGraphLinks(this._cy, scope.component.componentInstancesRelations, scope.component.getRelationRequirementCapability.bind(scope.component));
202 this.commonGraphUtils.initUcpeChildren(this._cy);
203 this.compositionGraphZoneUtils.initZoneInstances(scope.zones, scope.component);
204 setTimeout(() => {//Need settimeout so that angular canvas changes will take effect before resize & center
205 this.GeneralGraphUtils.zoomAllWithMax(this._cy, 1);
209 private loadGraph = (scope: ICompositionGraphScope, el: JQuery) => {
210 let graphEl = el.find('.sdc-composition-graph-wrapper');
211 this.initGraph(graphEl, scope.isViewOnly);
212 this.initDropZone(scope);
213 this.initZones(scope);
214 this.registerCytoscapeGraphEvents(scope);
215 this.registerCustomEvents(scope, el);
216 this.initViewMode(scope.isViewOnly);
219 private initGraph(graphEl: JQuery, isViewOnly: boolean) {
221 this._cy = cytoscape({
223 style: ComponentInstanceNodesStyle.getCompositionGraphStyle(),
224 zoomingEnabled: true,
227 userZoomingEnabled: false,
228 userPanningEnabled: true,
229 selectionType: 'single',
230 boxSelectionEnabled: true,
231 autolock: isViewOnly,
232 autoungrabify: isViewOnly
236 private initViewMode(isViewOnly: boolean) {
239 //remove event listeners
240 this._cy.off('drag');
241 this._cy.off('handlemouseout');
242 this._cy.off('handlemouseover');
243 this._cy.off('canvasredraw');
244 this._cy.off('handletagclick')
245 this._cy.edges().unselectify();
249 private registerCustomEvents(scope: ICompositionGraphScope, el: JQuery) {
251 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_GROUP_INSTANCE_UPDATE, (groupInstance: GroupInstance) => {
252 this.compositionGraphZoneUtils.findAndUpdateZoneInstanceData(scope.zones, groupInstance);
253 this.GeneralGraphUtils.showGroupUpdateSuccess();
256 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_POLICY_INSTANCE_UPDATE, (policyInstance: PolicyInstance) => {
257 this.compositionGraphZoneUtils.findAndUpdateZoneInstanceData(scope.zones, policyInstance);
258 this.GeneralGraphUtils.showPolicyUpdateSuccess();
261 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HOVER_IN, (leftPaletteComponent: LeftPaletteComponent) => {
262 if (scope.isOnDrag) {
266 this.$log.info(`composition-graph::registerEventServiceEvents:: palette hover on component: ${leftPaletteComponent.uniqueId}`);
268 let nodesData = this.NodesGraphUtils.getAllNodesData(this._cy.nodes());
269 let nodesLinks = this.GeneralGraphUtils.getAllCompositionCiLinks(this._cy);
271 if (this.GeneralGraphUtils.componentRequirementsAndCapabilitiesCaching.containsKey(leftPaletteComponent.uniqueId)) {
272 let cacheComponent = this.GeneralGraphUtils.componentRequirementsAndCapabilitiesCaching.getValue(leftPaletteComponent.uniqueId);
273 let filteredNodesData = this.matchCapabilitiesRequirementsUtils.findMatchingNodes(cacheComponent, nodesData, nodesLinks);
275 this.matchCapabilitiesRequirementsUtils.highlightMatchingComponents(filteredNodesData, this._cy);
276 this.matchCapabilitiesRequirementsUtils.fadeNonMachingComponents(filteredNodesData, nodesData, this._cy);
281 //----------------------- ORIT TO FIX------------------------//
283 this.ComponentServiceNg2.getCapabilitiesAndRequirements(leftPaletteComponent.componentType, leftPaletteComponent.uniqueId).subscribe((response: ComponentGenericResponse) => {
285 let component = this.ComponentFactory.createEmptyComponent(leftPaletteComponent.componentType);
286 component.uniqueId = component.uniqueId;
287 component.capabilities = response.capabilities;
288 component.requirements = response.requirements;
289 this.GeneralGraphUtils.componentRequirementsAndCapabilitiesCaching.setValue(leftPaletteComponent.uniqueId, component);
293 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_ADD_ZONE_INSTANCE_FROM_PALETTE, (component: Component, paletteComponent: LeftPaletteComponent, startPosition: Point) => {
295 let zoneType: ZoneInstanceType = this.compositionGraphZoneUtils.getZoneTypeForPaletteComponent(paletteComponent.categoryType);
296 this.compositionGraphZoneUtils.showZone(scope.zones[zoneType]);
298 this.LoaderService.showLoader('composition-graph');
299 this.compositionGraphZoneUtils.createZoneInstanceFromLeftPalette(zoneType, component, paletteComponent.type).subscribe((zoneInstance: ZoneInstance) => {
300 this.LoaderService.hideLoader('composition-graph');
301 this.compositionGraphZoneUtils.addInstanceToZone(scope.zones[zoneInstance.type], zoneInstance, true);
302 this.compositionGraphZoneUtils.createPaletteToZoneAnimation(startPosition, zoneType, zoneInstance);
304 this.LoaderService.hideLoader('composition-graph');
308 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HOVER_OUT, () => {
310 this._cy.emit('hidehandles');
311 this.matchCapabilitiesRequirementsUtils.resetFadedNodes(this._cy);
314 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_DRAG_START, (dragElement, dragComponent) => {
316 this.dragElement = dragElement;
317 this.dragComponent = this.ComponentInstanceFactory.createComponentInstanceFromComponent(dragComponent);
320 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_DRAG_ACTION, (event: IDragDropEvent) => {
321 this.CompositionGraphPaletteUtils.onComponentDrag(this._cy, event, this.dragElement, this.dragComponent);
325 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_COMPONENT_INSTANCE_NAME_CHANGED, (component: ComponentInstance) => {
327 let selectedNode = this._cy.getElementById(component.uniqueId);
328 selectedNode.data().componentInstance.name = component.name;
329 selectedNode.data('name', component.name); //used for tooltip
330 selectedNode.data('displayName', selectedNode.data().getDisplayName()); //abbreviated
334 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, (componentInstance: ComponentInstance) => {
335 let nodeToDelete = this._cy.getElementById(componentInstance.uniqueId);
336 this.NodesGraphUtils.deleteNode(this._cy, scope.component, nodeToDelete);
339 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_ZONE_INSTANCE, (deletedInstance: UIZoneInstanceObject) => {
341 if (deletedInstance.type === ZoneInstanceType.POLICY) {
342 scope.component.policies = scope.component.policies.filter(policy => policy.uniqueId !== deletedInstance.uniqueId);
343 } else if (deletedInstance.type === ZoneInstanceType.GROUP) {
344 scope.component.groupInstances = scope.component.groupInstances.filter(group => group.uniqueId !== deletedInstance.uniqueId);
346 //remove it from zones
347 scope.zones[deletedInstance.type].removeInstance(deletedInstance.uniqueId);
348 if (deletedInstance.type === ZoneInstanceType.GROUP && !_.isEmpty(scope.zones[ZoneInstanceType.POLICY])) {
349 this.compositionGraphZoneUtils.updateTargetsOrMembersOnCanvasDelete(deletedInstance.uniqueId, [scope.zones[ZoneInstanceType.POLICY]], ZoneInstanceAssignmentType.GROUPS);
351 this.eventListenerService.notifyObservers(EVENTS.UPDATE_PANEL);
354 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE_SUCCESS, (componentInstanceId: string) => {
355 if (!_.isEmpty(scope.zones)) {
356 this.compositionGraphZoneUtils.updateTargetsOrMembersOnCanvasDelete(componentInstanceId, scope.zones, ZoneInstanceAssignmentType.COMPONENT_INSTANCES);
360 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_EDGE, (releaseLoading: boolean, linksToDelete: Cy.CollectionEdges) => {
361 this.CompositionGraphLinkUtils.deleteLink(this._cy, scope.component, releaseLoading, linksToDelete);
364 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_INSERT_NODE_TO_UCPE, (node: Cy.CollectionNodes, ucpe: Cy.CollectionNodes, updateExistingNode: boolean) => {
366 this.commonGraphUtils.initUcpeChildData(node, ucpe);
367 //check if item is a VL, and if so, skip adding the binding to ucpe
368 if (!(node.data() instanceof CompositionCiNodeVl)) {
369 this.CompositionGraphLinkUtils.createVfToUcpeLink(scope.component, this._cy, ucpe.data(), node.data()); //create link from the node to the ucpe
372 if (updateExistingNode) {
373 let vlsPendingDeletion: Cy.CollectionNodes = this.NodesGraphUtils.deleteNodeVLsUponMoveToOrFromUCPE(scope.component, node.cy(), node); //delete connected VLs that no longer have 2 links
374 this.CompositionGraphLinkUtils.deleteLinksWhenNodeMovedFromOrToUCPE(scope.component, node.cy(), node, vlsPendingDeletion); //delete all connected links if needed
375 this.GeneralGraphUtils.pushUpdateComponentInstanceActionToQueue(scope.component, true, node.data().componentInstance); //update componentInstance position
380 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_REMOVE_NODE_FROM_UCPE, (node: Cy.CollectionNodes, ucpe: Cy.CollectionNodes) => {
381 this.commonGraphUtils.removeUcpeChildData(node);
382 let vlsPendingDeletion: Cy.CollectionNodes = this.NodesGraphUtils.deleteNodeVLsUponMoveToOrFromUCPE(scope.component, node.cy(), node);
383 this.CompositionGraphLinkUtils.deleteLinksWhenNodeMovedFromOrToUCPE(scope.component, node.cy(), node, vlsPendingDeletion); //delete all connected links if needed
384 this.GeneralGraphUtils.pushUpdateComponentInstanceActionToQueue(scope.component, true, node.data().componentInstance); //update componentInstance position
387 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_VERSION_CHANGED, (component: Component) => {
388 scope.component = component;
389 this._cy.elements().remove();
390 this.loadGraphData(scope);
394 scope.zoom = (zoomIn: boolean): void => {
395 let currentZoom: number = this._cy.zoom();
397 this.GeneralGraphUtils.zoomGraphTo(this._cy, currentZoom + .1);
399 this.GeneralGraphUtils.zoomGraphTo(this._cy, currentZoom - .1);
404 scope.zoomAllWithoutSidebar = () => {
405 scope.withSidebar = false;
406 setTimeout(() => { //wait for sidebar changes to take effect before zooming
407 this.GeneralGraphUtils.zoomAll(this._cy);
411 scope.getAutoCompleteValues = (searchTerm: string) => {
412 if (searchTerm.length > 1) { //US requirement: only display search results after 2nd letter typed.
413 let nodes: Cy.CollectionNodes = this.NodesGraphUtils.getMatchingNodesByName(this._cy, searchTerm);
414 scope.componentInstanceNames = _.map(nodes, node => node.data('name'));
416 scope.componentInstanceNames = [];
420 scope.highlightSearchMatches = (searchTerm: string) => {
421 this.NodesGraphUtils.highlightMatchingNodesByName(this._cy, searchTerm);
422 let matchingNodes: Cy.CollectionNodes = this.NodesGraphUtils.getMatchingNodesByName(this._cy, searchTerm);
423 this.GeneralGraphUtils.zoomAll(this._cy, matchingNodes);
426 scope.saveChangedCapabilityProperties = (): Promise<PropertyBEModel[]> => {
427 return new Promise<PropertyBEModel[]>((resolve) => {
428 const capabilityPropertiesBE: PropertyBEModel[] = this.ConnectionWizardServiceNg2.changedCapabilityProperties.map((prop) => {
429 prop.value = prop.getJSONValue();
430 const propBE = new PropertyBEModel(prop);
431 propBE.parentUniqueId = this.ConnectionWizardServiceNg2.selectedMatch.relationship.relation.capabilityOwnerId;
434 if (capabilityPropertiesBE.length > 0) {
435 // if there are capability properties to update, then first update capability properties and then resolve promise
436 this.ComponentInstanceServiceNg2
437 .updateInstanceCapabilityProperties(
439 this.ConnectionWizardServiceNg2.selectedMatch.toNode,
440 this.ConnectionWizardServiceNg2.selectedMatch.capability,
441 capabilityPropertiesBE
443 .subscribe((response) => {
444 console.log("Update resource instance capability properties response: ", response);
445 this.ConnectionWizardServiceNg2.changedCapabilityProperties = [];
446 resolve(capabilityPropertiesBE);
449 // no capability properties to update, immediately resolve promise
450 resolve(capabilityPropertiesBE);
455 scope.createLinkFromMenu = (): void => {
456 scope.isLinkMenuOpen = false;
458 scope.saveChangedCapabilityProperties().then(() => {
460 this.CompositionGraphLinkUtils
461 .createLinkFromMenu(this._cy, this.ConnectionWizardServiceNg2.selectedMatch, scope.component);
465 scope.hideRelationMenu = () => {
466 this.commonGraphUtils.safeApply(scope, () => {
467 delete scope.canvasMenuProps;
468 this.$timeout.cancel(scope.relationMenuTimeout);
472 scope.createOrUpdateServicePath = (data: any) => {
473 this.servicePathGraphUtils.createOrUpdateServicePath(scope, data);
475 scope.deletePathsOnCy = () => {
476 this.servicePathGraphUtils.deletePathsFromGraph(this._cy, <Service>scope.component);
478 scope.drawPathOnCy = (data: ForwardingPath) => {
479 this.servicePathGraphUtils.drawPath(this._cy, data, <Service>scope.component);
482 scope.viewRelation = (link: Cy.CollectionEdges) => {
483 scope.hideRelationMenu();
485 const linkData = link.data();
486 const sourceNode: CompositionCiNodeBase = link.source().data();
487 const targetNode: CompositionCiNodeBase = link.target().data();
488 const relationship: Relationship = linkData.relation.relationships[0];
490 scope.component.getRelationRequirementCapability(relationship, sourceNode.componentInstance, targetNode.componentInstance).then((objReqCap) => {
491 const capability = objReqCap.capability;
492 const requirement = objReqCap.requirement;
494 this.ConnectionWizardServiceNg2.currentComponent = scope.component;
495 this.ConnectionWizardServiceNg2.connectRelationModel = new ConnectRelationModel(sourceNode, targetNode, []);
496 this.ConnectionWizardServiceNg2.selectedMatch = new Match(requirement, capability, true, linkData.source, linkData.target);
497 this.ConnectionWizardServiceNg2.selectedMatch.relationship = relationship;
499 const title = `Connection Properties`;
500 const saveButton: ButtonModel = new ButtonModel('Save', 'blue', () => {
501 scope.saveChangedCapabilityProperties().then(() => {
502 this.ModalServiceNg2.closeCurrentModal();
505 const cancelButton: ButtonModel = new ButtonModel('Cancel', 'white', () => {
506 this.ModalServiceNg2.closeCurrentModal();
508 const modal = new ModalModel('xl', title, '', [saveButton, cancelButton]);
509 const modalInstance = this.ModalServiceNg2.createCustomModal(modal);
510 this.ModalServiceNg2.addDynamicContentToModal(modalInstance, ConnectionPropertiesViewComponent);
511 modalInstance.instance.open();
513 new Promise((resolve) => {
514 if (!this.ConnectionWizardServiceNg2.selectedMatch.capability.properties) {
515 this.ComponentInstanceServiceNg2.getInstanceCapabilityProperties(scope.component, linkData.target, capability)
524 this.ModalServiceNg2.addDynamicContentToModal(modalInstance, ConnectionPropertiesViewComponent);
531 scope.deleteRelation = (link: Cy.CollectionEdges) => {
532 scope.hideRelationMenu();
534 //if multiple edges selected, delete the VL itself so edges get deleted automatically
535 if (this._cy.$('edge:selected').length > 1) {
536 this.NodesGraphUtils.deleteNode(this._cy, scope.component, this._cy.$('node:selected'));
538 this.CompositionGraphLinkUtils.deleteLink(this._cy, scope.component, true, link);
543 document.onkeydown = (event) => {
544 let isModalExist = document.getElementsByTagName('body')[0].classList.contains('modal-open');
546 if (!scope.isViewOnly && !isModalExist) {
548 switch (event.keyCode) {
551 scope.deleteSelectedElements();
554 case 67: //ctrl+c : copy componentInstance
555 if (event.ctrlKey && scope.component.isService()) {
556 scope.copyComponentInstance();
559 case 86: // ctrl+v: paste componentInstance
560 if (event.ctrlKey && scope.component.isService()) {
561 let hidePasteObj = $(".w-canvas-content-paste");
562 hidePasteObj.click();
570 scope.deleteSelectedElements = (): void => {
572 let nodesToDelete = this._cy.$('node:selected');
573 let edgesSelected = this._cy.$('edge:selected');
574 if (nodesToDelete.length + edgesSelected.length <= 0) {
577 let componentInstancetobeDele;
578 let title: string = "Delete Confirmation";
579 let message: string = "Are you sure you would like to delete selected elements?";
580 if (nodesToDelete.size() == 1 && edgesSelected.size() == 0) {
582 componentInstancetobeDele = nodesToDelete[0].data().componentInstance;
583 let showName = nodesToDelete[0].data().componentInstance.name;
584 message = "Are you sure you would like to delete" + " " + showName + "?";
587 let onOk = (): void => {
588 if (nodesToDelete.size() == 1 && edgesSelected.size() == 0) {
589 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, componentInstancetobeDele);
592 this.NodesGraphUtils.batchDeleteNodes(this._cy, scope.component, nodesToDelete);
597 this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk);
602 scope.copyComponentInstance = (): void => {
603 scope.origComponentId = scope.component.uniqueId;
604 if (scope.component.selectedInstance) {
605 scope.origSelectedInstance = scope.component.selectedInstance;
610 scope.pasteComponentInstance = (event: IDragDropEvent): void => {
611 event.clientX = event.clientX ? event.clientX : this.cyBackgroundClickEvent ? this.cyBackgroundClickEvent.originalEvent.clientX : null;
612 event.clientY = event.clientY ? event.clientY : this.cyBackgroundClickEvent ? this.cyBackgroundClickEvent.originalEvent.clientY : null;
614 if (event.clientX == null || event.clientY == null) {
617 let offsetPosition = {
618 x: event.clientX - GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET,
619 y: event.clientY - GraphUIObjects.DIAGRAM_HEADER_OFFSET
622 let mousePosition = this.commonGraphUtils.HTMLCoordsToCytoscapeCoords(this._cy.extent(), offsetPosition);
623 let newPositionX = mousePosition.x;
624 let newPositionY = mousePosition.y;
625 let origSelectedInstance = scope.origSelectedInstance;
627 let copyComponentInstance = `{
628 "posX":"${newPositionX}",
629 "posY":"${newPositionY}",
630 "name":"${origSelectedInstance.componentName}",
631 "componentVersion":"${origSelectedInstance.componentVersion}",
632 "originType":"${origSelectedInstance.originType}",
633 "icon":"${origSelectedInstance.icon}",
634 "componentUid":"${origSelectedInstance.componentUid}"
637 this.isComponentPasteValid(scope, this._cy, event, offsetPosition, origSelectedInstance);
640 let onSuccess = (response): void => {
641 let success = (component: Component) => {
642 scope.component.componentInstances = component.componentInstances;
643 if (component.isService() && component.componentInstances.length > 0) {
644 _.each(component.componentInstances, (instance) => {
645 if (instance.uniqueId == response.componentInstance.uniqueId) {
646 let compositionGraphNode: CompositionCiNodeBase = this.NodesFactory.createNode(instance);
647 this.commonGraphUtils.addComponentInstanceNodeToGraph(this._cy, compositionGraphNode);
651 scope.isLoading = false;
653 scope.component.getComponent().then(success);
655 let onFailed = (error: any): void => {
657 scope.isLoading = false;
660 if (scope.pasteValid) {
661 scope.isLoading = true;
662 scope.component.pasteMenuComponentInstance(origSelectedInstance.uniqueId, copyComponentInstance).then(onSuccess, onFailed);
670 private registerCytoscapeGraphEvents(scope: ICompositionGraphScope) {
672 this._cy.on('addedgemouseup', (event, data) => {
673 scope.relationMenuDirectiveObj = this.CompositionGraphLinkUtils.onLinkDrawn(this._cy, data.source, data.target);
674 if (scope.relationMenuDirectiveObj != null) {
675 this.ConnectionWizardServiceNg2.setRelationMenuDirectiveObj(scope.relationMenuDirectiveObj);
676 this.ConnectionWizardServiceNg2.currentComponent = scope.component;
677 //TODO: init with the selected values
678 this.ConnectionWizardServiceNg2.selectedMatch = null;
680 let steps: Array<StepModel> = [];
681 let fromNodeName: string = scope.relationMenuDirectiveObj.fromNode.componentInstance.name;
682 let toNodeName: string = scope.relationMenuDirectiveObj.toNode.componentInstance.name;
683 steps.push(new StepModel(fromNodeName, FromNodeStepComponent));
684 steps.push(new StepModel(toNodeName, ToNodeStepComponent));
685 steps.push(new StepModel('Properties', PropertiesStepComponent));
686 let wizardTitle = 'Connect: ' + fromNodeName + ' to ' + toNodeName;
687 let modalInstance = this.ModalServiceNg2.createMultiStepsWizard(wizardTitle, steps, scope.createLinkFromMenu, ConnectionWizardHeaderComponent);
688 modalInstance.instance.open();
691 this._cy.on('tapstart', 'node', (event: Cy.EventObject) => {
692 scope.isOnDrag = true;
693 this._currentlyCLickedNodePosition = angular.copy(event.cyTarget[0].position()); //update node position on drag
694 if (event.cyTarget.data().isUcpe) {
695 this._cy.nodes('.ucpe-cp').unlock();
696 event.cyTarget.style('opacity', 0.5);
700 this._cy.on('drag', 'node', (event: Cy.EventObject) => {
702 if (event.cyTarget.data().isDraggable) {
703 event.cyTarget.style({ 'overlay-opacity': 0.24 });
704 if (this.GeneralGraphUtils.isValidDrop(this._cy, event.cyTarget)) {
705 event.cyTarget.style({ 'overlay-color': GraphColors.NODE_BACKGROUND_COLOR });
707 event.cyTarget.style({ 'overlay-color': GraphColors.NODE_OVERLAPPING_BACKGROUND_COLOR });
711 if (event.cyTarget.data().isUcpe) {
712 let pos = event.cyTarget.position();
714 this._cy.nodes('[?isInsideGroup]').positions((i, node) => {
716 x: pos.x + node.data("ucpeOffset").x,
717 y: pos.y + node.data("ucpeOffset").y
723 this._cy.on('handlemouseover', (event, payload) => {
725 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
729 if (scope.zoneTagMode) {
730 scope.zoneTagMode = scope.zones[scope.activeZoneInstance.type].getHoverTagModeId();
734 let nodesData = this.NodesGraphUtils.getAllNodesData(this._cy.nodes());
735 let nodesLinks = this.GeneralGraphUtils.getAllCompositionCiLinks(this._cy);
737 let linkableNodes = this.commonGraphUtils.getLinkableNodes(this._cy, payload.node);
738 let filteredNodesData = this.matchCapabilitiesRequirementsUtils.findMatchingNodes(payload.node.data().componentInstance, linkableNodes, nodesLinks);
739 this.matchCapabilitiesRequirementsUtils.highlightMatchingComponents(filteredNodesData, this._cy);
740 this.matchCapabilitiesRequirementsUtils.fadeNonMachingComponents(filteredNodesData, nodesData, this._cy, payload.node.data());
742 this._cy.scratch()._edge_editation_highlights = true;
745 this._cy.on('handlemouseout', () => {
746 if (scope.zoneTagMode) {
747 scope.zoneTagMode = scope.zones[scope.activeZoneInstance.type].getTagModeId();
750 if (this._cy.scratch('_edge_editation_highlights') === true) {
751 this._cy.removeScratch('_edge_editation_highlights');
752 this._cy.emit('hidehandles');
753 this.matchCapabilitiesRequirementsUtils.resetFadedNodes(this._cy);
758 this._cy.on('tapend', (event: Cy.EventObject) => {
759 scope.isOnDrag = false;
760 if (scope.zoneTagMode) {
763 if (event.cyTarget === this._cy) { //On Background clicked
764 if (this._cy.$('node:selected').length === 0) { //if the background click but not dragged
765 if (scope.activeZoneInstance) {
766 scope.unsetActiveZoneInstance();
768 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED);
770 this.cyBackgroundClickEvent = event;
771 scope.hideRelationMenu();
774 else if (event.cyTarget.isEdge()) { //On Edge clicked
775 this.CompositionGraphLinkUtils.handleLinkClick(this._cy, event);
776 if (event.cyTarget.data().type === CompositionCiServicePathLink.LINK_TYPE) {
779 this.openModifyLinkMenu(scope, this.CompositionGraphLinkUtils.getModifyLinkMenu(event.cyTarget[0], event), 6000);
782 else { //On Node clicked
784 this._cy.nodes(':grabbed').style({ 'overlay-opacity': 0 });
786 let isUcpe: boolean = event.cyTarget.data().isUcpe;
787 let newPosition = event.cyTarget[0].position();
788 //node position changed (drop after drag event) - we need to update position
789 if (this._currentlyCLickedNodePosition.x !== newPosition.x || this._currentlyCLickedNodePosition.y !== newPosition.y) {
790 let nodesMoved: Cy.CollectionNodes = this._cy.$(':grabbed');
792 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.
794 this.NodesGraphUtils.onNodesPositionChanged(this._cy, scope.component, nodesMoved);
796 this.$log.debug('composition-graph::onNodeSelectedEvent:: fired');
797 if (scope.activeZoneInstance) {
798 scope.unsetActiveZoneInstance();
801 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_NODE_SELECTED, event.cyTarget.data().componentInstance);
806 this._cy.nodes('.ucpe-cp').lock();
807 event.cyTarget.style('opacity', 1);
813 this._cy.on('boxselect', 'node', (event: Cy.EventObject) => {
814 scope.unsetActiveZoneInstance();
815 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_NODE_SELECTED, event.cyTarget.data().componentInstance);
818 this._cy.on('canvasredraw', (event: Cy.EventObject) => {
819 if (scope.zoneTagMode) {
820 this.compositionGraphZoneUtils.showZoneTagIndications(this._cy, scope.activeZoneInstance);
824 this._cy.on('handletagclick', (event: Cy.EventObject, eventData: any) => {
825 this.compositionGraphZoneUtils.handleTagClick(this._cy, scope.activeZoneInstance, eventData.nodeId);
831 private openModifyLinkMenu = (scope: ICompositionGraphScope, linkMenuObject: LinkMenu, timeOutInMilliseconds?: number) => {
832 scope.hideRelationMenu();
833 this.$timeout(() => {
834 scope.canvasMenuProps = {
836 styleClass: 'w-sdc-canvas-menu-list',
839 x: `${linkMenuObject.position.x}px`,
840 y: `${linkMenuObject.position.y}px`
844 if (this._cy.$('edge:selected').length === 1) {
845 scope.canvasMenuProps.items.push({
847 styleClass: 'w-sdc-canvas-menu-item-view',
849 scope.viewRelation(<Cy.CollectionEdges>linkMenuObject.link);
853 if (!scope.isViewOnly) {
854 scope.canvasMenuProps.items.push({
856 styleClass: 'w-sdc-canvas-menu-item-delete',
858 scope.deleteRelation(<Cy.CollectionEdges>linkMenuObject.link);
862 scope.relationMenuTimeout = this.$timeout(() => {
863 scope.hideRelationMenu();
864 }, timeOutInMilliseconds ? timeOutInMilliseconds : 6000);
868 private initGraphNodes(componentInstances: ComponentInstance[], isViewOnly: boolean) {
872 let handles = new CytoscapeEdgeEditation;
873 handles.init(this._cy);
874 if (!isViewOnly) { //Init nodes handle extension - enable dynamic links
875 handles.initNodeEvents();
876 handles.registerHandle(ComponentInstanceNodesStyle.getAddEdgeHandle());
878 handles.registerHandle(ComponentInstanceNodesStyle.getTagHandle());
879 handles.registerHandle(ComponentInstanceNodesStyle.getTaggedPolicyHandle());
880 handles.registerHandle(ComponentInstanceNodesStyle.getTaggedGroupHandle());
884 _.each(componentInstances, (instance) => {
885 let compositionGraphNode: CompositionCiNodeBase = this.NodesFactory.createNode(instance);
886 this.commonGraphUtils.addComponentInstanceNodeToGraph(this._cy, compositionGraphNode);
890 private initDropZone(scope: ICompositionGraphScope) {
892 if (scope.isViewOnly) {
895 scope.dropCallback = (event: IDragDropEvent) => {
896 this.$log.debug(`composition-graph::dropCallback:: fired`);
897 this.CompositionGraphPaletteUtils.addNodeFromPalette(this._cy, event, scope.component);
900 scope.verifyDrop = (event: JQueryEventObject) => {
902 if (!this.dragElement || this.dragElement.hasClass('red')) {
908 scope.beforeDropCallback = (event: IDragDropEvent): ng.IPromise<void> => {
909 let deferred: ng.IDeferred<void> = this.$q.defer<void>();
910 if (this.dragElement.hasClass('red')) {
916 return deferred.promise;
921 private initZones = (scope: ICompositionGraphScope): void => {
922 scope.zones = this.compositionGraphZoneUtils.createCompositionZones();
925 scope.zoneMinimizeToggle = (zoneType: ZoneInstanceType): void => {
926 scope.zones[zoneType].minimized = !scope.zones[zoneType].minimized;
929 scope.zoneInstanceModeChanged = (newMode: ZoneInstanceMode, instance: ZoneInstance, zoneId: ZoneInstanceType): void => {
930 if (scope.zoneTagMode) { //we're in tag mode.
931 if (instance == scope.activeZoneInstance && newMode == ZoneInstanceMode.NONE) { //we want to turn tag mode off.
932 scope.zoneTagMode = null;
933 scope.activeZoneInstance.mode = ZoneInstanceMode.SELECTED;
934 this.compositionGraphZoneUtils.endCyTagMode(this._cy);
935 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_CANVAS_TAG_END, instance);
939 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
940 instance.mode = newMode;
943 if (newMode == ZoneInstanceMode.NONE) {
944 this.compositionGraphZoneUtils.hideZoneTagIndications(this._cy);
945 if (scope.zones[ZoneInstanceType.GROUP]) {
946 this.compositionGraphZoneUtils.hideGroupZoneIndications(scope.zones[ZoneInstanceType.GROUP].instances);
949 if (newMode >= ZoneInstanceMode.HOVER) {
950 this.compositionGraphZoneUtils.showZoneTagIndications(this._cy, instance);
951 if (instance.type == ZoneInstanceType.POLICY && scope.zones[ZoneInstanceType.GROUP]) {
952 this.compositionGraphZoneUtils.showGroupZoneIndications(scope.zones[ZoneInstanceType.GROUP].instances, instance);
955 if (newMode >= ZoneInstanceMode.SELECTED) {
956 this._cy.$('node:selected').unselect();
957 if (scope.activeZoneInstance && scope.activeZoneInstance != instance && newMode >= ZoneInstanceMode.SELECTED) {
958 scope.activeZoneInstance.mode = ZoneInstanceMode.NONE;
960 scope.activeZoneInstance = instance;
961 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_ZONE_INSTANCE_SELECTED, instance);
963 if (newMode == ZoneInstanceMode.TAG) {
964 this.compositionGraphZoneUtils.startCyTagMode(this._cy);
965 scope.zoneTagMode = scope.zones[zoneId].getTagModeId();
966 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_CANVAS_TAG_START, zoneId);
971 scope.zoneInstanceTagged = (taggedInstance: ZoneInstance) => {
972 scope.activeZoneInstance.addOrRemoveAssignment(taggedInstance.instanceData.uniqueId, ZoneInstanceAssignmentType.GROUPS);
973 let newHandle: string = this.compositionGraphZoneUtils.getCorrectHandleForNode(taggedInstance.instanceData.uniqueId, scope.activeZoneInstance);
974 taggedInstance.showHandle(newHandle);
977 scope.zoneBackgroundClicked = (): void => {
978 if (!scope.zoneTagMode && scope.activeZoneInstance) {
979 scope.unsetActiveZoneInstance();
983 scope.zoneAssignmentSaveStart = () => {
984 this.LoaderService.showLoader('composition-graph');
987 scope.zoneAssignmentSaveComplete = (success: boolean) => {
988 this.LoaderService.hideLoader('composition-graph');
990 this.GeneralGraphUtils.showUpdateFailure();
994 scope.unsetActiveZoneInstance = (): void => {
995 if (scope.activeZoneInstance) {
996 scope.activeZoneInstance.mode = ZoneInstanceMode.NONE;
997 scope.activeZoneInstance = null;
998 scope.zoneTagMode = null;
999 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED);
1003 private isComponentPasteValid(scope: ICompositionGraphScope, cy: Cy.Instance, event: IDragDropEvent, offsetPosition: Cy.Position, origSelectedInstance: ComponentInstance) {
1004 let bbox = this._getNodeBBox(cy, event, origSelectedInstance, offsetPosition);
1006 if (this.GeneralGraphUtils.isPaletteDropValid(cy, bbox, origSelectedInstance)) {
1007 scope.pasteValid = true;
1009 scope.pasteValid = false;
1013 private _getNodeBBox(cy: Cy.Instance, event: IDragDropEvent, origSelectedInstance: ComponentInstance, position?: Cy.Position) {
1014 let bbox = <Cy.BoundingBox>{};
1016 position = this.commonGraphUtils.getCytoscapeNodePosition(cy, event);
1018 let cushionWidth: number = 40;
1019 let cushionHeight: number = 40;
1021 bbox.x1 = position.x - cushionWidth / 2;
1022 bbox.y1 = position.y - cushionHeight / 2;
1023 bbox.x2 = position.x + cushionWidth / 2;
1024 bbox.y2 = position.y + cushionHeight / 2;
1028 public static factory = ($q,
1034 ComponentInstanceFactory,
1036 EventListenerService,
1040 MatchCapabilitiesRequirementsUtils,
1041 CompositionGraphPaletteUtils,
1042 CompositionGraphZoneUtils,
1043 ComponentServiceNg2,
1045 ConnectionWizardService,
1046 ComponentInstanceServiceNg2,
1047 ServicePathGraphUtils,
1049 return new CompositionGraph(
1056 ComponentInstanceFactory,
1058 EventListenerService,
1062 MatchCapabilitiesRequirementsUtils,
1063 CompositionGraphPaletteUtils,
1064 CompositionGraphZoneUtils,
1065 ComponentServiceNg2,
1067 ConnectionWizardService,
1068 ComponentInstanceServiceNg2,
1069 ServicePathGraphUtils,
1074 CompositionGraph.factory.$inject = [
1079 'CompositionGraphLinkUtils',
1080 'CompositionGraphGeneralUtils',
1081 'ComponentInstanceFactory',
1082 'CompositionGraphNodesUtils',
1083 'EventListenerService',
1087 'MatchCapabilitiesRequirementsUtils',
1088 'CompositionGraphPaletteUtils',
1089 'CompositionGraphZoneUtils',
1090 'ComponentServiceNg2',
1092 'ConnectionWizardServiceNg2',
1093 'ComponentInstanceServiceNg2',
1094 'ServicePathGraphUtils',