705367c5f75e7424d9193d99be2de8322b7deac0
[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 createLinkFromMenu = (cy:Cy.Instance, chosenMatch:Match, component:Component):void => {
109
110         if (chosenMatch) {
111             if (chosenMatch && chosenMatch instanceof Match) {
112                 this.createSimpleLink(chosenMatch, cy, component);
113             }
114         }
115     };
116
117
118     /**
119      * Filters the matches for UCPE links so that shown requirements and capabilites are only related to the selected ucpe-cp
120      * @param fromNode
121      * @param toNode
122      * @param matchesArray
123      * @returns {Array<MatchBase>}
124      */
125     public filterUcpeLinks(fromNode:CompositionCiNodeBase, toNode:CompositionCiNodeBase, matchesArray:Array<Match>):any {
126
127         let matchLink:Array<Match>;
128
129         if (fromNode.isUcpePart) {
130             matchLink = _.filter(matchesArray, (match:Match) => {
131                 return match.isOwner(fromNode.id);
132             });
133         }
134
135         if (toNode.isUcpePart) {
136             matchLink = _.filter(matchesArray, (match:Match) => {
137                 return match.isOwner(toNode.id);
138             });
139         }
140         return matchLink ? matchLink : matchesArray;
141     }
142
143
144     /**
145      * open the connect link menu if the link drawn is valid - match  requirements & capabilities
146      * @param cy
147      * @param fromNode
148      * @param toNode
149      * @returns {any}
150      */
151     public onLinkDrawn(cy:Cy.Instance, fromNode:Cy.CollectionFirstNode, toNode:Cy.CollectionFirstNode):ConnectRelationModel {
152
153         if (!this.commonGraphUtils.nodeLocationsCompatible(cy, fromNode, toNode)) {
154             return null;
155         }
156         let linkModel:Array<CompositionCiLinkBase> = this.generalGraphUtils.getAllCompositionCiLinks(cy);
157
158         let possibleRelations:Array<Match> = this.matchCapabilitiesRequirementsUtils.getMatchedRequirementsCapabilities(fromNode.data().componentInstance,
159             toNode.data().componentInstance, linkModel);
160
161         //filter relations found to limit to specific ucpe-cp
162         possibleRelations = this.filterUcpeLinks(fromNode.data(), toNode.data(), possibleRelations);
163
164         //if found possibleRelations between the nodes we create relation menu directive and open the link menu
165         if (possibleRelations.length) {
166             // let menuPosition = this.generalGraphUtils.getLinkMenuPosition(cy, toNode.renderedPoint());
167             return new ConnectRelationModel(fromNode.data(), toNode.data(), possibleRelations);
168         }
169         return null;
170     };
171
172
173     /**
174      *  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
175      * @param node - the node we dragged into or out of the ucpe
176      */
177     public deleteLinksWhenNodeMovedFromOrToUCPE(component:Component, cy:Cy.Instance, nodeMoved:Cy.CollectionNodes, vlsPendingDeletion?:Cy.CollectionNodes):void {
178
179
180         let linksToDelete:Cy.CollectionElements = cy.collection();
181         _.forEach(nodeMoved.neighborhood('node'), (neighborNode)=> {
182
183             if (neighborNode.data().isUcpePart) { //existing connections to ucpe or ucpe-cp - we want to delete even though nodeLocationsCompatible will technically return true
184                 linksToDelete = linksToDelete.add(nodeMoved.edgesWith(neighborNode)); // This will delete the ucpe-host-link, or the vl-ucpe-link if nodeMoved is vl
185             } else if (!this.commonGraphUtils.nodeLocationsCompatible(cy, nodeMoved, neighborNode)) { //connection to regular node or vl - check if locations are compatible
186                 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
187                     linksToDelete = linksToDelete.add(nodeMoved.edgesWith(neighborNode));
188                 }
189             }
190         });
191
192         linksToDelete.each((i, link)=> {
193             this.deleteLink(cy, component, false, link);
194         });
195
196     };
197
198     /**
199      * Creates a hostedOn link between a VF and UCPE
200      * @param component
201      * @param cy
202      * @param ucpeNode
203      * @param vfNode
204      */
205     public createVfToUcpeLink = (component:Component, cy:Cy.Instance, ucpeNode:NodeUcpe, vfNode:CompositionCiNodeVf):void => {
206         let hostedOnMatch:Match = this.generalGraphUtils.canBeHostedOn(cy, ucpeNode.componentInstance, vfNode.componentInstance);
207         /* create relation */
208         let newRelation = new RelationshipModel();
209         newRelation.fromNode = ucpeNode.id;
210         newRelation.toNode = vfNode.id;
211
212         let link:CompositionCiLinkBase = this.linksFactory.createUcpeHostLink(newRelation);
213         link.relation = hostedOnMatch.matchToRelationModel();
214         this.createLink(link, cy, component);
215     };
216
217     private handlePathLink(cy:Cy.Instance, event:Cy.EventObject) {
218         let linkData = event.cyTarget.data();
219         let selectedPathId = linkData.pathId;
220         let pathEdges = cy.collection(`[pathId='${selectedPathId}']`);
221         if (pathEdges.length > 1) {
222             setTimeout(() => {
223                 pathEdges.select();
224             }, 0);
225         }
226     }
227
228     private handleVLLink(event:Cy.EventObject) {
229         let vl:Cy.CollectionNodes = event.cyTarget[0].target('.vl-node');
230         let connectedEdges:Cy.CollectionEdges = vl.connectedEdges(`[type!="${CompositionCiServicePathLink.LINK_TYPE}"]`);
231         if (vl.length && connectedEdges.length > 1) {
232             setTimeout(() => {
233                 vl.select();
234                 connectedEdges.select();
235             }, 0);
236         }
237     }
238
239
240     /**
241      * Handles click event on links.
242      * If one edge selected: do nothing.
243      * Two or more edges: first click - select all, secondary click - select single.
244      * @param cy
245      * @param event
246      */
247     public handleLinkClick(cy:Cy.Instance, event:Cy.EventObject) {
248         if (cy.$('edge:selected').length > 1 && event.cyTarget[0].selected()) {
249             cy.$(':selected').unselect();
250         } else {
251             if (event.cyTarget[0].data().type === CompositionCiServicePathLink.LINK_TYPE) {
252                 this.handlePathLink(cy, event);
253             }
254             else {
255                 this.handleVLLink(event);
256             }
257         }
258     }
259
260
261     /**
262      * Calculates the position for the menu that modifies an existing link
263      * @param event
264      * @param elementWidth
265      * @param elementHeight
266      * @returns {Point}
267      */
268     public calculateLinkMenuPosition(event, elementWidth, elementHeight):Point {
269         let point:Point = new Point(event.originalEvent.clientX, event.originalEvent.clientY);
270         if (event.originalEvent.view.screen.height - elementHeight < point.y) {
271             point.y = event.originalEvent.view.screen.height - elementHeight;
272         }
273         if (event.originalEvent.view.screen.width - elementWidth < point.x) {
274             point.x = event.originalEvent.view.screen.width - elementWidth;
275         }
276         return point;
277     };
278
279
280     /**
281      * Gets the menu that is displayed when you click an existing link.
282      * @param link
283      * @param event
284      * @returns {LinkMenu}
285      */
286     public getModifyLinkMenu(link:Cy.CollectionFirstEdge, event:Cy.EventObject):LinkMenu {
287         let point:Point = this.calculateLinkMenuPosition(event, GraphUIObjects.MENU_LINK_VL_WIDTH_OFFSET, GraphUIObjects.MENU_LINK_VL_HEIGHT_OFFSET);
288         let menu:LinkMenu = new LinkMenu(point, true, link);
289         return menu;
290     };
291
292 }
293
294
295 CompositionGraphLinkUtils.$inject = [
296     'LinksFactory',
297     'LoaderService',
298     'CompositionGraphGeneralUtils',
299     'CommonGraphUtils',
300     'MatchCapabilitiesRequirementsUtils'
301 ];