Skip to content

Commit

Permalink
Merge pull request jaegertracing#477 from rubenvp8510/Issue-467
Browse files Browse the repository at this point in the history
Jaeger UI visualizing span with multiple parents
  • Loading branch information
everett980 authored Dec 13, 2019
2 parents 5f2a05d + 24a3759 commit 5af9ed2
Show file tree
Hide file tree
Showing 23 changed files with 931 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
Copyright (c) 2019 The Jaeger Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

.ReferencesButton-MultiParent {
padding: 0 5px;
color: #000;
}
.ReferencesButton-MultiParent ~ .ReferencesButton-MultiParent {
margin-left: 5px;
}

a.ReferencesButton--TraceRefLink {
display: flex;
justify-content: space-between;
}

a.ReferencesButton--TraceRefLink > .NewWindowIcon {
margin: 0.2em 0 0;
}

.ReferencesButton-tooltip {
max-width: none;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) 2019 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import React from 'react';
import { shallow } from 'enzyme';
import { Menu, Dropdown, Tooltip } from 'antd';

import ReferencesButton from './ReferencesButton';
import transformTraceData from '../../../model/transform-trace-data';
import traceGenerator from '../../../demo/trace-generators';
import ReferenceLink from '../url/ReferenceLink';

describe(ReferencesButton, () => {
const trace = transformTraceData(traceGenerator.trace({ numberOfSpans: 10 }));
const oneReference = trace.spans[1].references;

const moreReferences = oneReference.slice();
const externalSpanID = 'extSpan';

moreReferences.push(
{
refType: 'CHILD_OF',
traceID: trace.traceID,
spanID: trace.spans[2].spanID,
span: trace.spans[2],
},
{
refType: 'CHILD_OF',
traceID: 'otherTrace',
spanID: externalSpanID,
}
);

const baseProps = {
focusSpan: () => {},
};

it('renders single reference', () => {
const props = { ...baseProps, references: oneReference };
const wrapper = shallow(<ReferencesButton {...props} />);
const dropdown = wrapper.find(Dropdown);
const refLink = wrapper.find(ReferenceLink);
const tooltip = wrapper.find(Tooltip);

expect(dropdown.length).toBe(0);
expect(refLink.length).toBe(1);
expect(refLink.prop('reference')).toBe(oneReference[0]);
expect(refLink.first().props().className).toBe('ReferencesButton-MultiParent');
expect(tooltip.length).toBe(1);
expect(tooltip.prop('title')).toBe(props.tooltipText);
});

it('renders multiple references', () => {
const props = { ...baseProps, references: moreReferences };
const wrapper = shallow(<ReferencesButton {...props} />);
const dropdown = wrapper.find(Dropdown);
expect(dropdown.length).toBe(1);
const menuInstance = shallow(dropdown.first().props().overlay);
const submenuItems = menuInstance.find(Menu.Item);
expect(submenuItems.length).toBe(3);
submenuItems.forEach((submenuItem, i) => {
expect(submenuItem.find(ReferenceLink).prop('reference')).toBe(moreReferences[i]);
});
expect(
submenuItems
.at(2)
.find(ReferenceLink)
.childAt(0)
.text()
).toBe(`(another trace) - ${moreReferences[2].spanID}`);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) 2019 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import React from 'react';
import { Dropdown, Menu, Tooltip } from 'antd';
import { TooltipPlacement } from 'antd/lib/tooltip';
import NewWindowIcon from '../../common/NewWindowIcon';
import { SpanReference } from '../../../types/trace';

import './ReferencesButton.css';
import ReferenceLink from '../url/ReferenceLink';

type TReferencesButtonProps = {
references: SpanReference[];
children: React.ReactNode;
tooltipText: string;
focusSpan: (spanID: string) => void;
};

export default class ReferencesButton extends React.PureComponent<TReferencesButtonProps> {
referencesList = (references: SpanReference[]) => (
<Menu>
{references.map(ref => {
const { span, spanID } = ref;
return (
<Menu.Item key={`${spanID}`}>
<ReferenceLink
reference={ref}
focusSpan={this.props.focusSpan}
className="ReferencesButton--TraceRefLink"
>
{span
? `${span.process.serviceName}:${span.operationName} - ${ref.spanID}`
: `(another trace) - ${ref.spanID}`}
{!span && <NewWindowIcon />}
</ReferenceLink>
</Menu.Item>
);
})}
</Menu>
);

render() {
const { references, children, tooltipText, focusSpan } = this.props;

const tooltipProps = {
arrowPointAtCenter: true,
mouseLeaveDelay: 0.5,
placement: 'bottom' as TooltipPlacement,
title: tooltipText,
overlayClassName: 'ReferencesButton--tooltip',
};

if (references.length > 1) {
return (
<Tooltip {...tooltipProps}>
<Dropdown overlay={this.referencesList(references)} placement="bottomRight" trigger={['click']}>
<a className="ReferencesButton-MultiParent">{children}</a>
</Dropdown>
</Tooltip>
);
}
const ref = references[0];
return (
<Tooltip {...tooltipProps}>
<ReferenceLink reference={ref} focusSpan={focusSpan} className="ReferencesButton-MultiParent">
{children}
</ReferenceLink>
</Tooltip>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
// limitations under the License.

import React from 'react';
import { mount } from 'enzyme';
import { mount, shallow } from 'enzyme';

import SpanBarRow from './SpanBarRow';
import SpanTreeOffset from './SpanTreeOffset';
import ReferencesButton from './ReferencesButton';

jest.mock('./SpanTreeOffset');

Expand Down Expand Up @@ -78,4 +79,87 @@ describe('<SpanBarRow>', () => {
wrapper.find(SpanTreeOffset).prop('onClick')();
expect(onChildrenToggled.mock.calls).toEqual([[spanID]]);
});

it('render references button', () => {
const span = Object.assign(
{
references: [
{
refType: 'CHILD_OF',
traceID: 'trace1',
spanID: 'span0',
span: {
spanID: 'span0',
},
},
{
refType: 'CHILD_OF',
traceID: 'otherTrace',
spanID: 'span1',
span: {
spanID: 'span1',
},
},
],
},
props.span
);

const spanRow = shallow(<SpanBarRow {...props} span={span} />);
const refButton = spanRow.find(ReferencesButton);
expect(refButton.length).toEqual(1);
expect(refButton.at(0).props().tooltipText).toEqual('Contains multiple references');
});

it('render referenced to by single span', () => {
const span = Object.assign(
{
subsidiarilyReferencedBy: [
{
refType: 'CHILD_OF',
traceID: 'trace1',
spanID: 'span0',
span: {
spanID: 'span0',
},
},
],
},
props.span
);
const spanRow = shallow(<SpanBarRow {...props} span={span} />);
const refButton = spanRow.find(ReferencesButton);
expect(refButton.length).toEqual(1);
expect(refButton.at(0).props().tooltipText).toEqual('This span is referenced by another span');
});

it('render referenced to by multiple span', () => {
const span = Object.assign(
{
subsidiarilyReferencedBy: [
{
refType: 'CHILD_OF',
traceID: 'trace1',
spanID: 'span0',
span: {
spanID: 'span0',
},
},
{
refType: 'CHILD_OF',
traceID: 'trace1',
spanID: 'span1',
span: {
spanID: 'span1',
},
},
],
},
props.span
);
const spanRow = shallow(<SpanBarRow {...props} span={span} />);
const refButton = spanRow.find(ReferencesButton);
expect(refButton.length).toEqual(1);
expect(refButton.at(0).props().tooltipText).toEqual('This span is referenced by multiple other spans');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
import * as React from 'react';
import IoAlert from 'react-icons/lib/io/alert';
import IoArrowRightA from 'react-icons/lib/io/arrow-right-a';

import IoNetwork from 'react-icons/lib/io/network';
import MdFileUpload from 'react-icons/lib/md/file-upload';
import ReferencesButton from './ReferencesButton';
import TimelineRow from './TimelineRow';
import { formatDuration, ViewedBoundsFunctionType } from './utils';
import SpanTreeOffset from './SpanTreeOffset';
Expand Down Expand Up @@ -50,6 +52,7 @@ type SpanBarRowProps = {
getViewedBounds: ViewedBoundsFunctionType;
traceStartTime: number;
span: Span;
focusSpan: (spanID: string) => void;
};

/**
Expand Down Expand Up @@ -88,6 +91,7 @@ export default class SpanBarRow extends React.PureComponent<SpanBarRowProps> {
getViewedBounds,
traceStartTime,
span,
focusSpan,
} = this.props;
const {
duration,
Expand Down Expand Up @@ -150,6 +154,26 @@ export default class SpanBarRow extends React.PureComponent<SpanBarRowProps> {
</span>
<small className="endpoint-name">{rpc ? rpc.operationName : operationName}</small>
</a>
{span.references && span.references.length > 1 && (
<ReferencesButton
references={span.references}
tooltipText="Contains multiple references"
focusSpan={focusSpan}
>
<IoNetwork />
</ReferencesButton>
)}
{span.subsidiarilyReferencedBy && span.subsidiarilyReferencedBy.length > 0 && (
<ReferencesButton
references={span.subsidiarilyReferencedBy}
tooltipText={`This span is referenced by ${
span.subsidiarilyReferencedBy.length === 1 ? 'another span' : 'multiple other spans'
}`}
focusSpan={focusSpan}
>
<MdFileUpload />
</ReferencesButton>
)}
</div>
</TimelineRow.Cell>
<TimelineRow.Cell
Expand Down
Loading

0 comments on commit 5af9ed2

Please sign in to comment.