Catalog alignment
[sdc.git] / catalog-ui / src / app / ng2 / pages / 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 {
27     Match,
28     CompositionCiNodeBase,
29     RelationshipModel,
30     ConnectRelationModel,
31     LinksFactory,
32     Component,
33     LinkMenu,
34     Point,
35     CompositionCiLinkBase,
36     Requirement,
37     Capability,
38     Relationship,
39     ComponentInstance
40 } from "app/models";
41 import {CommonGraphUtils} from "../common/common-graph-utils";
42 import {CompositionGraphGeneralUtils} from "./composition-graph-general-utils";
43 import {MatchCapabilitiesRequirementsUtils} from "./match-capability-requierment-utils";
44 import {CompositionCiServicePathLink} from "app/models/graph/graph-links/composition-graph-links/composition-ci-service-path-link";
45 import {Injectable} from "@angular/core";
46 import {QueueServiceUtils} from "app/ng2/utils/queue-service-utils";
47 import {TopologyTemplateService} from "app/ng2/services/component-services/topology-template.service";
48 import {SdcUiServices} from "onap-ui-angular";
49 import {CompositionService} from "../../composition.service";
50 import {WorkspaceService} from "app/ng2/pages/workspace/workspace.service";
51
52 @Injectable()
53 export class CompositionGraphLinkUtils {
54
55     constructor(private linksFactory: LinksFactory,
56                 private generalGraphUtils: CompositionGraphGeneralUtils,
57                 private commonGraphUtils: CommonGraphUtils,
58                 private queueServiceUtils: QueueServiceUtils,
59                 private matchCapabilitiesRequirementsUtils: MatchCapabilitiesRequirementsUtils,
60                 private topologyTemplateService: TopologyTemplateService,
61                 private loaderService: SdcUiServices.LoaderService,
62                 private compositionService: CompositionService,
63                 private workspaceService: WorkspaceService) {
64
65
66     }
67
68     /**
69      * Delete the link on server and then remove it from graph
70      * @param component
71      * @param releaseLoading - true/false release the loader when finished
72      * @param link - the link to delete
73      */
74     public deleteLink = (cy: Cy.Instance, component: Component, releaseLoading: boolean, link: Cy.CollectionEdges) => {
75
76         this.loaderService.activate();
77         this.queueServiceUtils.addBlockingUIAction(() => {
78             this.topologyTemplateService.deleteRelation(this.workspaceService.metadata.uniqueId, this.workspaceService.metadata.componentType, link.data().relation).subscribe((deletedRelation) => {
79                 this.compositionService.deleteRelation(deletedRelation);
80                 cy.remove(link);
81                 this.loaderService.deactivate();
82             }, (error) => {this.loaderService.deactivate()});
83         });
84     };
85
86     /**
87      * create the link on server and than draw it on graph
88      * @param link - the link to create
89      * @param cy
90      * @param component
91      */
92     public createLink = (link: CompositionCiLinkBase, cy: Cy.Instance): void => {
93
94         this.loaderService.activate();
95         link.updateLinkDirection();
96
97         this.queueServiceUtils.addBlockingUIAction(() => {
98             this.topologyTemplateService.createRelation(this.workspaceService.metadata.uniqueId, this.workspaceService.metadata.componentType, link.relation).subscribe((relation) => {
99                 link.setRelation(relation);
100                 this.insertLinkToGraph(cy, link);
101                 this.compositionService.addRelation(relation);
102                 this.loaderService.deactivate();
103             }, (error) => {this.loaderService.deactivate()})
104         });
105     };
106
107     private createSimpleLink = (match: Match, cy: Cy.Instance): void => {
108         let newRelation: RelationshipModel = match.matchToRelationModel();
109         let linkObg: CompositionCiLinkBase = this.linksFactory.createGraphLink(cy, newRelation, newRelation.relationships[0]);
110         this.createLink(linkObg, cy);
111     };
112
113     public createLinkFromMenu = (cy: Cy.Instance, chosenMatch: Match): void => {
114
115         if (chosenMatch) {
116             if (chosenMatch && chosenMatch instanceof Match) {
117                 this.createSimpleLink(chosenMatch, cy);
118             }
119         }
120     }
121
122     /**
123      * open the connect link menu if the link drawn is valid - match  requirements & capabilities
124      * @param cy
125      * @param fromNode
126      * @param toNode
127      * @returns {any}
128      */
129     public onLinkDrawn(cy: Cy.Instance, fromNode: Cy.CollectionFirstNode, toNode: Cy.CollectionFirstNode): ConnectRelationModel {
130
131         let linkModel: Array<CompositionCiLinkBase> = this.generalGraphUtils.getAllCompositionCiLinks(cy);
132
133         let possibleRelations: Array<Match> = this.matchCapabilitiesRequirementsUtils.getMatchedRequirementsCapabilities(fromNode.data().componentInstance,
134             toNode.data().componentInstance, linkModel);
135
136         //if found possibleRelations between the nodes we create relation menu directive and open the link menu
137         if (possibleRelations.length) {
138             // let menuPosition = this.generalGraphUtils.getLinkMenuPosition(cy, toNode.renderedPoint());
139             return new ConnectRelationModel(fromNode.data(), toNode.data(), possibleRelations);
140         }
141         return null;
142     };
143
144     private handlePathLink(cy: Cy.Instance, event: Cy.EventObject) {
145         let linkData = event.cyTarget.data();
146         let selectedPathId = linkData.pathId;
147         let pathEdges = cy.collection(`[pathId='${selectedPathId}']`);
148         if (pathEdges.length > 1) {
149             setTimeout(() => {
150                 pathEdges.select();
151             }, 0);
152         }
153     }
154
155     private handleVLLink(event: Cy.EventObject) {
156         let vl: Cy.CollectionNodes = event.cyTarget[0].target('.vl-node');
157         let connectedEdges: Cy.CollectionEdges = vl.connectedEdges(`[type!="${CompositionCiServicePathLink.LINK_TYPE}"]`);
158         if (vl.length && connectedEdges.length > 1) {
159             setTimeout(() => {
160                 vl.select();
161                 connectedEdges.select();
162             }, 0);
163         }
164     }
165
166
167     /**
168      * Handles click event on links.
169      * If one edge selected: do nothing.
170      * Two or more edges: first click - select all, secondary click - select single.
171      * @param cy
172      * @param event
173      */
174     public handleLinkClick(cy: Cy.Instance, event: Cy.EventObject) {
175         if (cy.$('edge:selected').length > 1 && event.cyTarget[0].selected()) {
176             cy.$(':selected').unselect();
177         } else {
178             if (event.cyTarget[0].data().type === CompositionCiServicePathLink.LINK_TYPE) {
179                 this.handlePathLink(cy, event);
180             }
181             else {
182                 this.handleVLLink(event);
183             }
184         }
185     }
186
187
188     /**
189      * Calculates the position for the menu that modifies an existing link
190      * @param event
191      * @param elementWidth
192      * @param elementHeight
193      * @returns {Point}
194      */
195     public calculateLinkMenuPosition(event, elementWidth, elementHeight): Point {
196         let point: Point = new Point(event.originalEvent.clientX, event.originalEvent.clientY);
197         if (event.originalEvent.view.screen.height - elementHeight < point.y) {
198             point.y = event.originalEvent.view.screen.height - elementHeight;
199         }
200         if (event.originalEvent.view.screen.width - elementWidth < point.x) {
201             point.x = event.originalEvent.view.screen.width - elementWidth;
202         }
203         return point;
204     };
205
206
207     /**
208      * Gets the menu that is displayed when you click an existing link.
209      * @param link
210      * @param event
211      * @returns {LinkMenu}
212      */
213     public getModifyLinkMenu(link: Cy.CollectionFirstEdge, event: Cy.EventObject): LinkMenu {
214         let point: Point = this.calculateLinkMenuPosition(event, GraphUIObjects.MENU_LINK_VL_WIDTH_OFFSET, GraphUIObjects.MENU_LINK_VL_HEIGHT_OFFSET);
215         let menu: LinkMenu = new LinkMenu(point, true, link);
216         return menu;
217     };
218
219     /**
220      * Returns relation source and target nodes.
221      * @param nodes - all nodes in graph in order to find the edge connecting the two nodes
222      * @param fromNodeId
223      * @param toNodeId
224      * @returns [source, target] array of source node and target node.
225      */
226     public getRelationNodes(nodes: Cy.CollectionNodes, fromNodeId: string, toNodeId: string) {
227         return [
228             _.find(nodes, (node: Cy.CollectionFirst) => node.data().id === fromNodeId),
229             _.find(nodes, (node: Cy.CollectionFirst) => node.data().id === toNodeId)
230         ];
231     }
232
233
234     /**
235      *  go over the relations and draw links on the graph
236      * @param cy
237      * @param getRelationRequirementCapability - function to get requirement and capability of a relation
238      */
239     public initGraphLinks(cy: Cy.Instance, relations: RelationshipModel[]) {
240         if (relations) {
241             _.forEach(relations, (relationshipModel: RelationshipModel) => {
242                 _.forEach(relationshipModel.relationships, (relationship: Relationship) => {
243                     let linkToCreate = this.linksFactory.createGraphLink(cy, relationshipModel, relationship);
244                     this.insertLinkToGraph(cy, linkToCreate);
245                 });
246             });
247         }
248     }
249
250     /**
251      * Add link to graph - only draw the link
252      * @param cy
253      * @param link
254      * @param getRelationRequirementCapability
255      */
256     public insertLinkToGraph = (cy: Cy.Instance, link: CompositionCiLinkBase) => {
257         const relationNodes = this.getRelationNodes(cy.nodes(), link.source, link.target);
258         const sourceNode: CompositionCiNodeBase = relationNodes[0] && relationNodes[0].data();
259         const targetNode: CompositionCiNodeBase = relationNodes[1] && relationNodes[1].data();
260         if ((sourceNode && !sourceNode.certified) || (targetNode && !targetNode.certified)) {
261             link.classes = 'not-certified-link';
262         }
263         let linkElement = cy.add({
264             group: 'edges',
265             data: link,
266             classes: link.classes
267         });
268
269         const getLinkRequirementCapability = () =>
270             this.getRelationRequirementCapability(link.relation.relationships[0], sourceNode.componentInstance, targetNode.componentInstance);
271         this.commonGraphUtils.initLinkTooltip(linkElement, link.relation.relationships[0], getLinkRequirementCapability);
272     };
273
274     public syncComponentByRelation(relation: RelationshipModel) {
275         let componentInstances = this.compositionService.getComponentInstances();
276         relation.relationships.forEach((rel) => {
277             if (rel.capability) {
278                 const toComponentInstance: ComponentInstance = componentInstances.find((inst) => inst.uniqueId === relation.toNode);
279                 const toComponentInstanceCapability: Capability = toComponentInstance.findCapability(
280                     rel.capability.type, rel.capability.uniqueId, rel.capability.ownerId, rel.capability.name);
281                 const isCapabilityFulfilled: boolean = rel.capability.isFulfilled();
282                 if (isCapabilityFulfilled && toComponentInstanceCapability) {
283                     // if capability is fulfilled and in component, then remove it
284                     console.log('Capability is fulfilled', rel.capability.getFullTitle(), rel.capability.leftOccurrences);
285                     toComponentInstance.capabilities[rel.capability.type].splice(
286                         toComponentInstance.capabilities[rel.capability.type].findIndex((cap) => cap === toComponentInstanceCapability), 1
287                     )
288                 } else if (!isCapabilityFulfilled && !toComponentInstanceCapability) {
289                     // if capability is unfulfilled and not in component, then add it
290                     console.log('Capability is unfulfilled', rel.capability.getFullTitle(), rel.capability.leftOccurrences);
291                     toComponentInstance.capabilities[rel.capability.type].push(rel.capability);
292                 }
293             }
294             if (rel.requirement) {
295                 const fromComponentInstance: ComponentInstance = componentInstances.find((inst) => inst.uniqueId === relation.fromNode);
296                 const fromComponentInstanceRequirement: Requirement = fromComponentInstance.findRequirement(
297                     rel.requirement.capability, rel.requirement.uniqueId, rel.requirement.ownerId, rel.requirement.name);
298                 const isRequirementFulfilled: boolean = rel.requirement.isFulfilled();
299                 if (isRequirementFulfilled && fromComponentInstanceRequirement) {
300                     // if requirement is fulfilled and in component, then remove it
301                     console.log('Requirement is fulfilled', rel.requirement.getFullTitle(), rel.requirement.leftOccurrences);
302                     fromComponentInstance.requirements[rel.requirement.capability].splice(
303                         fromComponentInstance.requirements[rel.requirement.capability].findIndex((req) => req === fromComponentInstanceRequirement), 1
304                     )
305                 } else if (!isRequirementFulfilled && !fromComponentInstanceRequirement) {
306                     // if requirement is unfulfilled and not in component, then add it
307                     console.log('Requirement is unfulfilled', rel.requirement.getFullTitle(), rel.requirement.leftOccurrences);
308                     fromComponentInstance.requirements[rel.requirement.capability].push(rel.requirement);
309                 }
310             }
311         });
312     }
313
314     public getRelationRequirementCapability(relationship: Relationship, sourceNode: ComponentInstance, targetNode: ComponentInstance): Promise<{ requirement: Requirement, capability: Capability }> {
315         // try find the requirement and capability in the source and target component instances:
316         let capability: Capability = targetNode.findCapability(undefined,
317             relationship.relation.capabilityUid,
318             relationship.relation.capabilityOwnerId,
319             relationship.relation.capability);
320         let requirement: Requirement = sourceNode.findRequirement(undefined,
321             relationship.relation.requirementUid,
322             relationship.relation.requirementOwnerId,
323             relationship.relation.requirement);
324
325         return new Promise<{ requirement: Requirement, capability: Capability }>((resolve, reject) => {
326             if (capability && requirement) {
327                 resolve({capability, requirement});
328             }
329             else {
330                 // if requirement and/or capability is missing, then fetch the full relation with its requirement and capability:
331                 this.topologyTemplateService.fetchRelation(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, relationship.relation.id).subscribe((fetchedRelation) => {
332                     this.syncComponentByRelation(fetchedRelation);
333                     resolve({
334                         capability: capability || fetchedRelation.relationships[0].capability,
335                         requirement: requirement || fetchedRelation.relationships[0].requirement
336                     });
337                 }, reject);
338             }
339         });
340     }
341 }
342