diff --git a/packages/jaeger-ui/src/components/DependencyGraph/DependencyForceGraph.js b/packages/jaeger-ui/src/components/DependencyGraph/DependencyForceGraph.js index 65551145f2..fede90e51d 100644 --- a/packages/jaeger-ui/src/components/DependencyGraph/DependencyForceGraph.js +++ b/packages/jaeger-ui/src/components/DependencyGraph/DependencyForceGraph.js @@ -13,9 +13,10 @@ // limitations under the License. import React, { Component } from 'react'; -import { InteractiveForceGraph, ForceGraphNode, ForceGraphLink } from 'react-vis-force'; +import { InteractiveForceGraph, ForceGraphNode } from 'react-vis-force'; import { window } from 'global'; import { debounce } from 'lodash'; +import ForceGraphArrowLink from './ForceGraphArrowLink'; import { nodesPropTypes, linksPropTypes } from '../../propTypes/dependencies'; @@ -59,6 +60,7 @@ export default class DependencyForceGraph extends Component { render() { const { nodes, links } = this.props; const { width, height } = this.state; + const nodesMap = new Map(nodes.map(node => [node.id, node])); return (
))} {links.map(({ opacity, ...link }) => ( - ${link.target}`} opacity={opacity} link={link} /> + ${link.target}`} + opacity={opacity} + link={link} + targetRadius={nodesMap.get(link.target).radius} + /> ))}
diff --git a/packages/jaeger-ui/src/components/DependencyGraph/DependencyForceGraph.test.js b/packages/jaeger-ui/src/components/DependencyGraph/DependencyForceGraph.test.js index 225ad88dbc..ff239efcdf 100644 --- a/packages/jaeger-ui/src/components/DependencyGraph/DependencyForceGraph.test.js +++ b/packages/jaeger-ui/src/components/DependencyGraph/DependencyForceGraph.test.js @@ -14,9 +14,10 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { InteractiveForceGraph, ForceGraphNode, ForceGraphLink } from 'react-vis-force'; +import { InteractiveForceGraph, ForceGraphNode } from 'react-vis-force'; import DependencyForceGraph, { chargeStrength } from './DependencyForceGraph'; +import ForceGraphArrowLink from './ForceGraphArrowLink'; describe('chargeStrength', () => { it('returns a number', () => { @@ -109,8 +110,8 @@ describe('', () => { expect(wrapper.find(ForceGraphNode).length).toBe(nodes.length); }); - it('renders a for each link', () => { - expect(wrapper.find(ForceGraphLink).length).toBe(links.length); + it('renders a for each link', () => { + expect(wrapper.find(ForceGraphArrowLink).length).toBe(links.length); }); }); }); diff --git a/packages/jaeger-ui/src/components/DependencyGraph/ForceGraphArrowLink.test.js b/packages/jaeger-ui/src/components/DependencyGraph/ForceGraphArrowLink.test.js new file mode 100644 index 0000000000..f38e15d45d --- /dev/null +++ b/packages/jaeger-ui/src/components/DependencyGraph/ForceGraphArrowLink.test.js @@ -0,0 +1,77 @@ +// Copyright (c) 2019 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import React from 'react'; +import { shallow } from 'enzyme'; + +import ForceGraphArrowLink from './ForceGraphArrowLink'; + +const defaultProps = { + link: { + source: 'a', + target: 'b', + value: 5, + }, +}; + +describe('', () => { + it('should a standard size of the arrow', () => { + const wrapper = shallow( + + ); + + const marker = wrapper + .find('g') + .first() + .find('defs') + .first() + .find('marker'); + expect(marker.prop('markerWidth')).toEqual(6); + expect(marker.prop('markerHeight')).toEqual(4); + expect(marker.prop('markerUnits')).toEqual('strokeWidth'); + }); + + it('should not have arrow overlapping with target node', () => { + const wrapper = shallow( + + ); + + const marker = wrapper + .find('g') + .first() + .find('defs') + .first() + .find('marker'); + expect(marker.prop('refX')).toEqual(2 + 5); + }); + + it('should have an id with the name of source and target', () => { + const testLink = { source: 's_node', target: 't_node', value: 10 }; + + const wrapper = shallow(); + const marker = wrapper + .find('g') + .first() + .find('defs') + .first() + .find('marker'); + expect(marker.prop('id')).toEqual('arrow-s_node=>t_node'); + }); +}); diff --git a/packages/jaeger-ui/src/components/DependencyGraph/ForceGraphArrowLink.tsx b/packages/jaeger-ui/src/components/DependencyGraph/ForceGraphArrowLink.tsx new file mode 100644 index 0000000000..a336740995 --- /dev/null +++ b/packages/jaeger-ui/src/components/DependencyGraph/ForceGraphArrowLink.tsx @@ -0,0 +1,84 @@ +// Copyright (c) 2019 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import * as React from 'react'; +import { ForceGraphLink } from 'react-vis-force'; + +type TLink = { + source: string | { id: string }; + target: string | { id: string }; + value: number; +}; + +type TProps = { + className?: string; + color?: string; + edgeOffset?: number; + link: TLink; + opacity?: number; + stroke?: string; + strokeWidth?: number; + targetRadius?: number; +}; + +function linkId(link: TLink) { + const { source, target } = link; + const srcId = typeof source === 'string' ? source : source.id; + const targetId = typeof target === 'string' ? target : target.id; + return `${srcId}=>${targetId}`; +} + +export default class ForceGraphArrowLink extends React.PureComponent { + static defaultProps = { + className: '', + edgeOffset: 2, + opacity: 0.6, + stroke: '#999', + strokeWidth: 1, + targetRadius: 2, + }; + + render() { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { link, targetRadius, edgeOffset: _, ...spreadable } = this.props; + const id = `arrow-${linkId(link)}`; + return ( + + + + {Number(targetRadius) > 0 && ( + + )} + + + + + + ); + } +} diff --git a/packages/jaeger-ui/src/components/DependencyGraph/index.test.js b/packages/jaeger-ui/src/components/DependencyGraph/index.test.js index 8217131298..8e051422f4 100644 --- a/packages/jaeger-ui/src/components/DependencyGraph/index.test.js +++ b/packages/jaeger-ui/src/components/DependencyGraph/index.test.js @@ -102,10 +102,10 @@ describe('mapStateToProps()', () => { expect(mapStateToProps(state)).toEqual({ ...state.dependencies, nodes: [ - expect.objectContaining({ callCount, orphan: false, id: parentId }), - expect.objectContaining({ callCount, orphan: false, id: childId }), + expect.objectContaining({ callCount, orphan: false, id: parentId, radius: 3 }), + expect.objectContaining({ callCount, orphan: false, id: childId, radius: 3 }), ], - links: [{ callCount, source: parentId, target: childId, value: 1 }], + links: [{ callCount, source: parentId, target: childId, value: 1, target_node_size: 3 }], }); }); }); diff --git a/packages/jaeger-ui/src/propTypes/dependencies.js b/packages/jaeger-ui/src/propTypes/dependencies.js index 41158579da..4165eaa091 100644 --- a/packages/jaeger-ui/src/propTypes/dependencies.js +++ b/packages/jaeger-ui/src/propTypes/dependencies.js @@ -25,6 +25,7 @@ export const linksPropTypes = PropTypes.arrayOf( PropTypes.shape({ source: PropTypes.string.isRequired, target: PropTypes.string.isRequired, + target_node_size: PropTypes.number, value: PropTypes.number.isRequired, }) ); diff --git a/packages/jaeger-ui/src/selectors/dependencies.js b/packages/jaeger-ui/src/selectors/dependencies.js index 71c04ae443..79d797e6bb 100644 --- a/packages/jaeger-ui/src/selectors/dependencies.js +++ b/packages/jaeger-ui/src/selectors/dependencies.js @@ -38,6 +38,7 @@ export const formatDependenciesAsNodesAndLinks = createSelector( target: link.child, callCount: link.callCount, value: Math.max(Math.sqrt(link.callCount / 10000), 1), + target_node_size: Math.max(Math.log(nodeMap[link.child] / 1000), 3), }, ]); } diff --git a/packages/jaeger-ui/typings/custom.d.ts b/packages/jaeger-ui/typings/custom.d.ts index d664242eb5..607058ceee 100644 --- a/packages/jaeger-ui/typings/custom.d.ts +++ b/packages/jaeger-ui/typings/custom.d.ts @@ -43,4 +43,5 @@ declare module 'combokeys' { declare module 'react-helmet'; declare module 'json-markup'; +declare module 'react-vis-force'; declare module 'tween-functions';