Keyboard Shortcut for copy&Paste and delete
[sdc.git] / catalog-ui / src / app / directives / graphs-v2 / composition-graph / utils / composition-graph-links-utils.ts
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 /**
22  * Created by obarda on 6/28/2016.
23  */
24 import * as _ from "lodash";
25 import {GraphUIObjects} from "app/utils";
26 import {LoaderService} from "app/services";
27 import {
28     NodeUcpe,
29     CompositionCiNodeVf,
30     Match,
31     CompositionCiNodeBase,
32     RelationshipModel,
33     ConnectRelationModel,
34     LinksFactory,
35     Component,
36     LinkMenu,
37     Point,
38     CompositionCiLinkBase
39 } from "app/models";
40 import {CommonGraphUtils} from "../../common/common-graph-utils";
41 import {CompositionGraphGeneralUtils} from "./composition-graph-general-utils";
42 import {MatchCapabilitiesRequirementsUtils} from "./match-capability-requierment-utils";
43 import {CompositionCiServicePathLink} from "../../../../models/graph/graph-links/composition-graph-links/composition-ci-service-path-link";
44
45 export class CompositionGraphLinkUtils {
46
47     constructor(private linksFactory:LinksFactory,
48                 private loaderService:LoaderService,
49                 private generalGraphUtils:CompositionGraphGeneralUtils,
50                 private commonGraphUtils:CommonGraphUtils,
51                 private matchCapabilitiesRequirementsUtils:MatchCapabilitiesRequirementsUtils) {
52     }
53
54
55     /**
56      * Delete the link on server and then remove it from graph
57      * @param component
58      * @param releaseLoading - true/false release the loader when finished
59      * @param link - the link to delete
60      */
61     public deleteLink = (cy:Cy.Instance, component:Component, releaseLoading:boolean, link:Cy.CollectionEdges) => {
62
63         this.loaderService.showLoader('composition-graph');
64         let onSuccessDeleteRelation = (response) => {
65             cy.remove(link);
66         };
67
68         if (!releaseLoading) {
69             this.generalGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIAction(
70                 () => component.deleteRelation(link.data().relation).then(onSuccessDeleteRelation)
71             );
72         } else {
73             this.generalGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIActionWithReleaseCallback(
74                 () => component.deleteRelation(link.data().relation).then(onSuccessDeleteRelation),
75                 () => this.loaderService.hideLoader('composition-graph'));
76         }
77     };
78
79     /**
80      * create the link on server and than draw it on graph
81      * @param link - the link to create
82      * @param cy
83      * @param component
84      */
85     public createLink = (link:CompositionCiLinkBase, cy:Cy.Instance, component:Component):void => {
86
87         this.loaderService.showLoader('composition-graph');
88
89         let onSuccess:(response:RelationshipModel) => void = (relation:RelationshipModel) => {
90             link.setRelation(relation);
91             this.commonGraphUtils.insertLinkToGraph(cy, link, component.getRelationRequirementCapability.bind(component));
92         };
93
94         link.updateLinkDirection();
95
96         this.generalGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIActionWithReleaseCallback(
97             () => component.createRelation(link.relation).then(onSuccess),
98             () => this.loaderService.hideLoader('composition-graph')
99         );
100     };
101
102     private createSimpleLink = (match:Match, cy:Cy.Instance, component:Component):void => {
103         let newRelation:RelationshipModel = match.matchToRelationModel();
104         let linkObg:CompositionCiLinkBase = this.linksFactory.createGraphLink(cy, newRelation, newRelation.relationships[0]);
105         this.createLink(linkObg, cy, component);
106     };
107
108     public batchDeleteEdges(cy: Cy.Instance, component: Component, edgesToDelete: Cy.CollectionEdges, alreadyDeleteNodeIds?: Array<string>): void {
109         let toDeleteLinks: Array<RelationshipModel> = new Array<RelationshipModel>();
110         if (alreadyDeleteNodeIds && alreadyDeleteNodeIds.length > 0) {
111             edgesToDelete.each((i: number, link: Cy.CollectionEdges) => {
112                 if (alreadyDeleteNodeIds.indexOf(link.data().source) < 0 && alreadyDeleteNodeIds.indexOf(link.data().target) < 0) {
113                     toDeleteLinks.push(link.data().relation);
114                 }
115             });
116         }
117         else {
118             edgesToDelete.each((i: number, link: Cy.CollectionEdges) => {
119                 toDeleteLinks.push(link.data().relation);
120             });
121         }
122         this.loaderService.showLoader('composition-graph');
123         let onSuccessDeleteRelations = (response: Array<RelationshipModel>) => {
124             console.info('onSuccessDeleteRelations response is ', response);
125             //remove tempSimplePortNodes
126             if (alreadyDeleteNodeIds && alreadyDeleteNodeIds.length > 0) {
127                 edgesToDelete.each((i: number, link: Cy.CollectionEdges) => {
128                     if (alreadyDeleteNodeIds.indexOf(link.data().source) < 0 && alreadyDeleteNodeIds.indexOf(link.data().target) < 0) {
129                         cy.remove(edgesToDelete);
130                     }
131                 });
132             }
133             else {
134                 edgesToDelete.each((i: number, link: Cy.CollectionEdges) => {
135                     cy.remove(edgesToDelete);
136                 });
137             }
138         };
139
140         if (toDeleteLinks.length > 0) {
141             this.generalGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIActionWithReleaseCallback(
142                 () => component.batchDeleteRelation(toDeleteLinks).then(onSuccessDeleteRelations),
143                 () => this.loaderService.hideLoader('composition-graph'));
144
145         }
146     };  
147
148     public createLinkFromMenu = (cy:Cy.Instance, chosenMatch:Match, component:Component):void => {
149
150         if (chosenMatch) {
151             if (chosenMatch && chosenMatch instanceof Match) {
152                 this.createSimpleLink(chosenMatch, cy, component);
153             }
154         }
155     };
156
157
158     /**
159      * Filters the matches for UCPE links so that shown requirements and capabilites are only related to the selected ucpe-cp
160      * @param fromNode
161      * @param toNode
162      * @param matchesArray
163      * @returns {Array<MatchBase>}
164      */
165     public filterUcpeLinks(fromNode:CompositionCiNodeBase, toNode:CompositionCiNodeBase, matchesArray:Array<Match>):any {
166
167         let matchLink:Array<Match>;
168
169         if (fromNode.isUcpePart) {
170             matchLink = _.filter(matchesArray, (match:Match) => {
171                 return match.isOwner(fromNode.id);
172             });
173         }
174
175         if (toNode.isUcpePart) {
176             matchLink = _.filter(matchesArray, (match:Match) => {
177                 return match.isOwner(toNode.id);
178             });
179         }
180         return matchLink ? matchLink : matchesArray;
181     }
182
183
184     /**
185      * open the connect link menu if the link drawn is valid - match  requirements & capabilities
186      * @param cy
187      * @param fromNode
188      * @param toNode
189      * @returns {any}
190      */
191     public onLinkDrawn(cy:Cy.Instance, fromNode:Cy.CollectionFirstNode, toNode:Cy.CollectionFirstNode):ConnectRelationModel {
192
193         if (!this.commonGraphUtils.nodeLocationsCompatible(cy, fromNode, toNode)) {
194             return null;
195         }
196         let linkModel:Array<CompositionCiLinkBase> = this.generalGraphUtils.getAllCompositionCiLinks(cy);
197
198         let possibleRelations:Array<Match> = this.matchCapabilitiesRequirementsUtils.getMatchedRequirementsCapabilities(fromNode.data().componentInstance,
199             toNode.data().componentInstance, linkModel);
200
201         //filter relations found to limit to specific ucpe-cp
202         possibleRelations = this.filterUcpeLinks(fromNode.data(), toNode.data(), possibleRelations);
203
204         //if found possibleRelations between the nodes we create relation menu directive and open the link menu
205         if (possibleRelations.length) {
206             // let menuPosition = this.generalGraphUtils.getLinkMenuPosition(cy, toNode.renderedPoint());
207             return new ConnectRelationModel(fromNode.data(), toNode.data(), possibleRelations);
208         }
209         return null;
210     };
211
212
213     /**
214      *  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
215      * @param node - the node we dragged into or out of the ucpe
216      */
217     public deleteLinksWhenNodeMovedFromOrToUCPE(component:Component, cy:Cy.Instance, nodeMoved:Cy.CollectionNodes, vlsPendingDeletion?:Cy.CollectionNodes):void {
218
219
220         let linksToDelete:Cy.CollectionElements = cy.collection();
221         _.forEach(nodeMoved.neighborhood('node'), (neighborNode)=> {
222
223             if (neighborNode.data().isUcpePart) { //existing connections to ucpe or ucpe-cp - we want to delete even though nodeLocationsCompatible will technically return true
224                 linksToDelete = linksToDelete.add(nodeMoved.edgesWith(neighborNode)); // This will delete the ucpe-host-link, or the vl-ucpe-link if nodeMoved is vl
225             } else if (!this.commonGraphUtils.nodeLocationsCompatible(cy, nodeMoved, neighborNode)) { //connection to regular node or vl - check if locations are compatible
226                 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
227                     linksToDelete = linksToDelete.add(nodeMoved.edgesWith(neighborNode));
228                 }
229             }
230         });
231
232         linksToDelete.each((i, link)=> {
233             this.deleteLink(cy, component, false, link);
234         });
235
236     };
237
238     /**
239      * Creates a hostedOn link between a VF and UCPE
240      * @param component
241      * @param cy
242      * @param ucpeNode
243      * @param vfNode
244      */
245     public createVfToUcpeLink = (component:Component, cy:Cy.Instance, ucpeNode:NodeUcpe, vfNode:CompositionCiNodeVf):void => {
246         let hostedOnMatch:Match = this.generalGraphUtils.canBeHostedOn(cy, ucpeNode.componentInstance, vfNode.componentInstance);
247         /* create relation */
248         let newRelation = new RelationshipModel();
249         newRelation.fromNode = ucpeNode.id;
250         newRelation.toNode = vfNode.id;
251
252         let link:CompositionCiLinkBase = this.linksFactory.createUcpeHostLink(newRelation);
253         link.relation = hostedOnMatch.matchToRelationModel();
254         this.createLink(link, cy, component);
255     };
256
257     private handlePathLink(cy:Cy.Instance, event:Cy.EventObject) {
258         let linkData = event.cyTarget.data();
259         let selectedPathId = linkData.pathId;
260         let pathEdges = cy.collection(`[pathId='${selectedPathId}']`);
261         if (pathEdges.length > 1) {
262             setTimeout(() => {
263                 pathEdges.select();
264             }, 0);
265         }
266     }
267
268     private handleVLLink(event:Cy.EventObject) {
269         let vl:Cy.CollectionNodes = event.cyTarget[0].target('.vl-node');
270         let connectedEdges:Cy.CollectionEdges = vl.connectedEdges(`[type!="${CompositionCiServicePathLink.LINK_TYPE}"]`);
271         if (vl.length && connectedEdges.length > 1) {
272             setTimeout(() => {
273                 vl.select();
274                 connectedEdges.select();
275             }, 0);
276         }
277     }
278
279
280     /**
281      * Handles click event on links.
282      * If one edge selected: do nothing.
283      * Two or more edges: first click - select all, secondary click - select single.
284      * @param cy
285      * @param event
286      */
287     public handleLinkClick(cy:Cy.Instance, event:Cy.EventObject) {
288         if (cy.$('edge:selected').length > 1 && event.cyTarget[0].selected()) {
289             cy.$(':selected').unselect();
290         } else {
291             if (event.cyTarget[0].data().type === CompositionCiServicePathLink.LINK_TYPE) {
292                 this.handlePathLink(cy, event);
293             }
294             else {
295                 this.handleVLLink(event);
296             }
297         }
298     }
299
300
301     /**
302      * Calculates the position for the menu that modifies an existing link
303      * @param event
304      * @param elementWidth
305      * @param elementHeight
306      * @returns {Point}
307      */
308     public calculateLinkMenuPosition(event, elementWidth, elementHeight):Point {
309         let point:Point = new Point(event.originalEvent.clientX, event.originalEvent.clientY);
310         if (event.originalEvent.view.screen.height - elementHeight < point.y) {
311             point.y = event.originalEvent.view.screen.height - elementHeight;
312         }
313         if (event.originalEvent.view.screen.width - elementWidth < point.x) {
314             point.x = event.originalEvent.view.screen.width - elementWidth;
315         }
316         return point;
317     };
318
319
320     /**
321      * Gets the menu that is displayed when you click an existing link.
322      * @param link
323      * @param event
324      * @returns {LinkMenu}
325      */
326     public getModifyLinkMenu(link:Cy.CollectionFirstEdge, event:Cy.EventObject):LinkMenu {
327         let point:Point = this.calculateLinkMenuPosition(event, GraphUIObjects.MENU_LINK_VL_WIDTH_OFFSET, GraphUIObjects.MENU_LINK_VL_HEIGHT_OFFSET);
328         let menu:LinkMenu = new LinkMenu(point, true, link);
329         return menu;
330     };
331
332 }
333
334
335 CompositionGraphLinkUtils.$inject = [
336     'LinksFactory',
337     'LoaderService',
338     'CompositionGraphGeneralUtils',
339     'CommonGraphUtils',
340     'MatchCapabilitiesRequirementsUtils'
341 ];