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=========================================================
22 * Created by obarda on 6/28/2016.
24 import * as _ from "lodash";
25 import {GraphUIObjects} from "app/utils";
28 CompositionCiNodeBase,
35 CompositionCiLinkBase,
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";
53 export class CompositionGraphLinkUtils {
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) {
69 * Delete the link on server and then remove it from graph
71 * @param releaseLoading - true/false release the loader when finished
72 * @param link - the link to delete
74 public deleteLink = (cy: Cy.Instance, component: Component, releaseLoading: boolean, link: Cy.CollectionEdges) => {
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);
81 this.loaderService.deactivate();
82 }, (error) => {this.loaderService.deactivate()});
87 * create the link on server and than draw it on graph
88 * @param link - the link to create
92 public createLink = (link: CompositionCiLinkBase, cy: Cy.Instance): void => {
94 this.loaderService.activate();
95 link.updateLinkDirection();
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()})
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);
113 public createLinkFromMenu = (cy: Cy.Instance, chosenMatch: Match): void => {
116 if (chosenMatch && chosenMatch instanceof Match) {
117 this.createSimpleLink(chosenMatch, cy);
123 * open the connect link menu if the link drawn is valid - match requirements & capabilities
129 public onLinkDrawn(cy: Cy.Instance, fromNode: Cy.CollectionFirstNode, toNode: Cy.CollectionFirstNode): ConnectRelationModel {
131 let linkModel: Array<CompositionCiLinkBase> = this.generalGraphUtils.getAllCompositionCiLinks(cy);
133 let possibleRelations: Array<Match> = this.matchCapabilitiesRequirementsUtils.getMatchedRequirementsCapabilities(fromNode.data().componentInstance,
134 toNode.data().componentInstance, linkModel);
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);
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) {
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) {
161 connectedEdges.select();
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.
174 public handleLinkClick(cy: Cy.Instance, event: Cy.EventObject) {
175 if (cy.$('edge:selected').length > 1 && event.cyTarget[0].selected()) {
176 cy.$(':selected').unselect();
178 if (event.cyTarget[0].data().type === CompositionCiServicePathLink.LINK_TYPE) {
179 this.handlePathLink(cy, event);
182 this.handleVLLink(event);
189 * Calculates the position for the menu that modifies an existing link
191 * @param elementWidth
192 * @param elementHeight
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;
200 if (event.originalEvent.view.screen.width - elementWidth < point.x) {
201 point.x = event.originalEvent.view.screen.width - elementWidth;
208 * Gets the menu that is displayed when you click an existing link.
211 * @returns {LinkMenu}
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);
220 * Returns relation source and target nodes.
221 * @param nodes - all nodes in graph in order to find the edge connecting the two nodes
224 * @returns [source, target] array of source node and target node.
226 public getRelationNodes(nodes: Cy.CollectionNodes, fromNodeId: string, toNodeId: string) {
228 _.find(nodes, (node: Cy.CollectionFirst) => node.data().id === fromNodeId),
229 _.find(nodes, (node: Cy.CollectionFirst) => node.data().id === toNodeId)
235 * go over the relations and draw links on the graph
237 * @param getRelationRequirementCapability - function to get requirement and capability of a relation
239 public initGraphLinks(cy: Cy.Instance, relations: RelationshipModel[]) {
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);
251 * Add link to graph - only draw the link
254 * @param getRelationRequirementCapability
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';
263 let linkElement = cy.add({
266 classes: link.classes
269 const getLinkRequirementCapability = () =>
270 this.getRelationRequirementCapability(link.relation.relationships[0], sourceNode.componentInstance, targetNode.componentInstance);
271 this.commonGraphUtils.initLinkTooltip(linkElement, link.relation.relationships[0], getLinkRequirementCapability);
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
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);
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
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);
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);
325 return new Promise<{ requirement: Requirement, capability: Capability }>((resolve, reject) => {
326 if (capability && requirement) {
327 resolve({capability, requirement});
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);
334 capability: capability || fetchedRelation.relationships[0].capability,
335 requirement: requirement || fetchedRelation.relationships[0].requirement