/** * Created by obarda on 6/28/2016. */ /// module Sdc.Graph.Utils { import ImageCreatorService = Sdc.Utils.ImageCreatorService; import Module = Sdc.Models.Module; export class CompositionGraphLinkUtils { private p2pVL:Models.Components.Component; private mp2mpVL:Models.Components.Component; constructor(private linksFactory:Sdc.Utils.LinksFactory, private loaderService:Services.LoaderService, private generalGraphUtils:Sdc.Graph.Utils.CompositionGraphGeneralUtils, private leftPaletteLoaderService:Services.Components.LeftPaletteLoaderService, private componentInstanceFactory:Sdc.Utils.ComponentInstanceFactory, private nodesFactory:Sdc.Utils.NodesFactory, private commonGraphUtils: Sdc.Graph.Utils.CommonGraphUtils, private matchCapabilitiesRequirementsUtils: Graph.Utils.MatchCapabilitiesRequirementsUtils) { this.initScopeVls(); } /** * Delete the link on server and then remove it from graph * @param component * @param releaseLoading - true/false release the loader when finished * @param link - the link to delete */ public deleteLink = (cy:Cy.Instance, component:Models.Components.Component, releaseLoading:boolean, link:Cy.CollectionEdges) => { this.loaderService.showLoader('composition-graph'); let onSuccessDeleteRelation = (response) => { cy.remove(link); }; if (!releaseLoading) { this.generalGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIAction( () => component.deleteRelation(link.data().relation).then(onSuccessDeleteRelation) ); } else { this.generalGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIActionWithReleaseCallback( () => component.deleteRelation(link.data().relation).then(onSuccessDeleteRelation), () => this.loaderService.hideLoader('composition-graph')); } }; /** * create the link on server and than draw it on graph * @param link - the link to create * @param cy * @param component */ public createLink = (link:Models.CompositionCiLinkBase, cy:Cy.Instance, component:Models.Components.Component):void => { this.loaderService.showLoader('composition-graph'); let onSuccess:(response:Models.RelationshipModel) => void = (relation:Models.RelationshipModel) => { link.setRelation(relation); this.commonGraphUtils.insertLinkToGraph(cy, link); }; link.updateLinkDirection(); this.generalGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIActionWithReleaseCallback( () => component.createRelation(link.relation).then(onSuccess), () => this.loaderService.hideLoader('composition-graph') ); }; public initScopeVls = ():void => { let vls = this.leftPaletteLoaderService.getFullDataComponentList(Sdc.Utils.Constants.ResourceType.VL); vls.forEach((item) => { let key = _.find(Object.keys(item.capabilities), (key) => { return _.includes(key.toLowerCase(), 'linkable'); }); let linkable = item.capabilities[key]; if (linkable) { if (linkable[0].maxOccurrences == '2') { this.p2pVL = _.find(vls, (component:Models.Components.Component) => { return component.uniqueId === item.uniqueId; }); } else {//assuming unbounded occurrences this.mp2mpVL = _.find(vls, (component:Models.Components.Component) => { return component.uniqueId === item.uniqueId; }); } } }); }; private setVLlinks = (match:Models.MatchReqToReq, vl:Models.ComponentsInstances.ComponentInstance):Array => { let relationship1 = new Models.Relationship(); let relationship2 = new Models.Relationship(); let newRelationshipModel1 = new Models.RelationshipModel(); let newRelationshipModel2 = new Models.RelationshipModel(); let capability:Models.Capability = vl.capabilities.findValueByKey('linkable')[0]; relationship1.setRelationProperties(capability, match.requirement); relationship2.setRelationProperties(capability, match.secondRequirement); newRelationshipModel1.setRelationshipModelParams(match.fromNode, vl.uniqueId, [relationship1]); newRelationshipModel2.setRelationshipModelParams(match.toNode, vl.uniqueId, [relationship2]); return [newRelationshipModel1, newRelationshipModel2]; }; private createVlinks = (cy:Cy.Instance, component:Models.Components.Component, matchReqToReq:Models.MatchReqToReq, vl:Models.Components.Component):void => { let componentInstance:Models.ComponentsInstances.ComponentInstance = this.componentInstanceFactory.createComponentInstanceFromComponent(vl); let fromNodePosition:Cy.Position = cy.getElementById(matchReqToReq.fromNode).relativePosition(); let toNodePosition:Cy.Position = cy.getElementById(matchReqToReq.toNode).relativePosition(); let location:Cy.Position = { x: 0.5 * (fromNodePosition.x + toNodePosition.x), y: 0.5 * (fromNodePosition.y + toNodePosition.y) } componentInstance.posX = location.x; componentInstance.posY = location.y; let onFailed:(error:any) => void = (error:any) => { this.loaderService.hideLoader('composition-graph'); console.info('onFailed', error); }; let onSuccess = (response:Models.ComponentsInstances.ComponentInstance):void => { console.info('onSuccses', response); response.requirements = new Models.RequirementsGroup(vl.requirements); response.capabilities = new Models.CapabilitiesGroup(vl.capabilities); response.componentVersion = vl.version; response.setInstanceRC(); let newLinks = this.setVLlinks(matchReqToReq, response); let newNode = this.nodesFactory.createNode(response); this.commonGraphUtils.addComponentInstanceNodeToGraph(cy, newNode); _.forEach(newLinks, (link) => { let linkObg:Models.CompositionCiLinkBase = this.linksFactory.createGraphLink(cy, link, link.relationships[0]); this.createLink(linkObg, cy, component); }); }; component.createComponentInstance(componentInstance).then(onSuccess, onFailed); }; private createSimpleLink = (match:Models.MatchReqToCapability, cy:Cy.Instance, component:Models.Components.Component):void => { let newRelation:Models.RelationshipModel = match.matchToRelationModel(); let linkObg:Models.CompositionCiLinkBase = this.linksFactory.createGraphLink(cy,newRelation, newRelation.relationships[0]); this.createLink(linkObg, cy, component); }; public createLinkFromMenu = (cy:Cy.Instance, chosenMatch:Models.MatchBase, vl:Models.Components.Component, component:Models.Components.Component):void => { if (chosenMatch) { if (chosenMatch && chosenMatch instanceof Models.MatchReqToReq) { this.createVlinks(cy, component, chosenMatch, vl); //TODO orit implement } if (chosenMatch && chosenMatch instanceof Models.MatchReqToCapability) { this.createSimpleLink(chosenMatch, cy, component); } } }; /** * Filters the matches for UCPE links so that shown requirements and capabilites are only related to the selected ucpe-cp * @param fromNode * @param toNode * @param matchesArray * @returns {Array} */ public filterUcpeLinks(fromNode: Models.Graph.CompositionCiNodeBase, toNode: Models.Graph.CompositionCiNodeBase, matchesArray: Array): any { let matchLink: Array; if (fromNode.isUcpePart) { matchLink = _.filter(matchesArray, (match: Models.MatchBase) => { return match.isOwner(fromNode.id); }); } if (toNode.isUcpePart) { matchLink = _.filter(matchesArray, (match: Models.MatchBase) => { return match.isOwner(toNode.id); }); } return matchLink ? matchLink : matchesArray; } /** * open the connect link menu if the link drawn is valid - match requirements & capabilities * @param cy * @param fromNode * @param toNode * @returns {any} */ public onLinkDrawn(cy:Cy.Instance, fromNode:Cy.CollectionFirstNode, toNode:Cy.CollectionFirstNode):Models.RelationMenuDirectiveObj { if(!this.commonGraphUtils.nodeLocationsCompatible(cy, fromNode, toNode)){ return null; } let linkModel:Array = this.generalGraphUtils.getAllCompositionCiLinks(cy); let possibleRelations:Array = this.matchCapabilitiesRequirementsUtils.getMatchedRequirementsCapabilities(fromNode.data().componentInstance, toNode.data().componentInstance, linkModel, this.mp2mpVL); //TODO orit - add p2p and mp2mp //filter relations found to limit to specific ucpe-cp possibleRelations = this.filterUcpeLinks(fromNode.data(), toNode.data(), possibleRelations); //if found possibleRelations between the nodes we create relation menu directive and open the link menu if (possibleRelations.length) { let menuPosition = this.generalGraphUtils.getLinkMenuPosition(cy, toNode.renderedPoint()); return new Models.RelationMenuDirectiveObj(fromNode.data(), toNode.data(), this.mp2mpVL, this.p2pVL, menuPosition, possibleRelations); } return null; }; /** * when we drag instance in to UCPE or out of UCPE - get all links we need to delete - one node in ucpe and one node outside of ucpe * @param node - the node we dragged into or out of the ucpe */ public deleteLinksWhenNodeMovedFromOrToUCPE(component:Models.Components.Component, cy:Cy.Instance, nodeMoved:Cy.CollectionNodes, vlsPendingDeletion?:Cy.CollectionNodes):void { let linksToDelete:Cy.CollectionElements = cy.collection(); _.forEach(nodeMoved.neighborhood('node'), (neighborNode)=>{ if(neighborNode.data().isUcpePart){ //existing connections to ucpe or ucpe-cp - we want to delete even though nodeLocationsCompatible will technically return true linksToDelete = linksToDelete.add(nodeMoved.edgesWith(neighborNode)); // This will delete the ucpe-host-link, or the vl-ucpe-link if nodeMoved is vl } else if(!this.commonGraphUtils.nodeLocationsCompatible(cy, nodeMoved, neighborNode)){ //connection to regular node or vl - check if locations are compatible if(!vlsPendingDeletion || !vlsPendingDeletion.intersect(neighborNode).length){ //Check if this is a link to a VL pending deletion, to prevent double deletion of between the node moved and vl linksToDelete = linksToDelete.add(nodeMoved.edgesWith(neighborNode)); } } }); linksToDelete.each((i, link)=>{ this.deleteLink(cy, component, false, link); }); }; /** * Creates a hostedOn link between a VF and UCPE * @param component * @param cy * @param ucpeNode * @param vfNode */ public createVfToUcpeLink = (component: Models.Components.Component, cy:Cy.Instance, ucpeNode:Models.Graph.NodeUcpe, vfNode:Models.Graph.CompositionCiNodeVf):void => { let hostedOnMatch:Models.MatchReqToCapability = this.generalGraphUtils.canBeHostedOn(cy, ucpeNode.componentInstance, vfNode.componentInstance); /* create relation */ let newRelation = new Models.RelationshipModel(); newRelation.fromNode = ucpeNode.id; newRelation.toNode = vfNode.id; let link:Models.CompositionCiLinkBase = this.linksFactory.createUcpeHostLink(newRelation); link.relation = hostedOnMatch.matchToRelationModel(); this.createLink(link, cy, component); }; /** * Handles click event on links. * If one edge selected: do nothing. /*Two edges selected - always select all /* Three or more edges: first click - select all, secondary click - select single. * @param cy * @param event */ public handleLinkClick(cy:Cy.Instance, event : Cy.EventObject) { if(cy.$('edge:selected').length > 2 && event.cyTarget[0].selected()) { cy.$(':selected').unselect(); } else { let vl: Cy.CollectionNodes = event.cyTarget[0].target('.vl-node'); let connectedEdges:Cy.CollectionEdges = vl.connectedEdges(); if (vl.length && connectedEdges.length > 1) { setTimeout(() => { vl.select(); connectedEdges.select(); }, 0); } } } /** * Calculates the position for the menu that modifies an existing link * @param event * @param elementWidth * @param elementHeight * @returns {Sdc.Models.Graph.Point} */ public calculateLinkMenuPosition(event, elementWidth, elementHeight): Sdc.Models.Graph.Point { let point: Sdc.Models.Graph.Point = new Sdc.Models.Graph.Point(event.originalEvent.x,event.originalEvent.y); if(event.originalEvent.view.screen.height-elementHeight