diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/MultiParentIcon.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/MultiParentIcon.tsx
new file mode 100644
index 0000000000..2ef3b7cc5c
--- /dev/null
+++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/MultiParentIcon.tsx
@@ -0,0 +1,117 @@
+// Copyright (c) 2019 Uber Technologies, Inc.
+//
+// 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 * as React from 'react';
+import { Button, Dropdown, Menu, Tooltip } from 'antd';
+import { bindActionCreators } from 'redux';
+import { connect, Dispatch } from 'react-redux';
+import { History as RouterHistory, Location } from 'history';
+import { withRouter } from 'react-router-dom';
+
+import { FetchedTrace, ReduxState, TNil } from '../../../types';
+import { actions as timelineActions } from './duck';
+import { extractUiFindFromState } from '../../common/UiFindInput';
+import { SpanReference, Trace } from '../../../types/trace';
+import updateUiFind from '../../../utils/update-ui-find';
+
+type TDispatchProps = {
+ focusUiFindMatches: (trace: Trace, uiFind: string | TNil) => void;
+};
+
+type TReduxProps = {
+ uiFind: string | TNil;
+ trace: FetchedTrace | TNil;
+};
+
+type TOwnProps = {
+ references: SpanReference[];
+ traceID: string;
+ history: RouterHistory;
+ location: Location;
+ match: any;
+};
+
+type MultiParentIconProps = TDispatchProps & TReduxProps & TOwnProps;
+
+const linkValueList = (links: SpanReference[], focusUiFindMatches: (spanID: string) => void) => (
+
+ {links.map(({ spanID, span }, index) => {
+ let text = spanID;
+ if (span) {
+ text = `${span.operationName} (${spanID})`;
+ }
+ return (
+ // `index` is necessary in the key because url can repeat
+ // eslint-disable-next-line react/no-array-index-key
+
+ focusUiFindMatches(spanID)}>
+ {' '}
+ {text}{' '}
+
+
+ );
+ })}
+
+);
+
+class MultiParentIconImpl extends React.PureComponent {
+ focusUiFindMatches = (uiFind: string) => {
+ const { trace, focusUiFindMatches, location, history } = this.props;
+ if (trace && trace.data) {
+ updateUiFind({
+ location,
+ history,
+ uiFind,
+ });
+ focusUiFindMatches(trace.data, uiFind);
+ }
+ };
+
+ render() {
+ const { references } = this.props;
+ return (
+
+
+
+
+
+ );
+ }
+}
+
+export function mapDispatchToProps(dispatch: Dispatch): TDispatchProps {
+ const { focusUiFindMatches } = bindActionCreators(timelineActions, dispatch);
+ return { focusUiFindMatches };
+}
+
+// export for tests
+export function mapStateToProps(state: ReduxState, ownProps: TOwnProps): TReduxProps {
+ const { traces } = state.trace;
+ const trace = ownProps.traceID ? traces[ownProps.traceID] : null;
+ return {
+ trace,
+ ...extractUiFindFromState(state),
+ };
+}
+
+export default withRouter(
+ connect(
+ mapStateToProps,
+ mapDispatchToProps
+ )(MultiParentIconImpl)
+);
diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.tsx
index f69c0f4eb8..cc2d2961a7 100644
--- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.tsx
+++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.tsx
@@ -34,8 +34,8 @@ type SpanBarRowProps = {
isChildrenExpanded: boolean;
isDetailExpanded: boolean;
isMatchingFilter: boolean;
- onDetailToggled: (spanID: string) => void;
- onChildrenToggled: (spanID: string) => void;
+ onDetailToggled: (UiID: string) => void;
+ onChildrenToggled: (UiID: string) => void;
numTicks: number;
rpc?:
| {
@@ -67,11 +67,11 @@ export default class SpanBarRow extends React.PureComponent {
};
_detailToggle = () => {
- this.props.onDetailToggled(this.props.span.spanID);
+ this.props.onDetailToggled(this.props.span.uiID);
};
_childrenToggle = () => {
- this.props.onChildrenToggled(this.props.span.spanID);
+ this.props.onChildrenToggled(this.props.span.uiID);
};
render() {
diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/index.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/index.tsx
index 19d92dd261..33559b0cfd 100644
--- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/index.tsx
+++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/index.tsx
@@ -24,9 +24,10 @@ import CopyIcon from '../../../common/CopyIcon';
import LabeledList from '../../../common/LabeledList';
import { TNil } from '../../../../types';
-import { KeyValuePair, Link, Log, Span } from '../../../../types/trace';
+import { KeyValuePair, Link, Log, Span, SpanReference } from '../../../../types/trace';
import './index.css';
+import MultiParentIcon from '../MultiParentIcon';
type SpanDetailProps = {
detailState: DetailState;
@@ -38,6 +39,7 @@ type SpanDetailProps = {
tagsToggle: (spanID: string) => void;
traceStartTime: number;
warningsToggle: (spanID: string) => void;
+ references: SpanReference[];
};
export default function SpanDetail(props: SpanDetailProps) {
@@ -51,9 +53,10 @@ export default function SpanDetail(props: SpanDetailProps) {
tagsToggle,
traceStartTime,
warningsToggle,
+ references,
} = props;
const { isTagsOpen, isProcessOpen, logs: logsState, isWarningsOpen } = detailState;
- const { operationName, process, duration, relativeStartTime, spanID, logs, tags, warnings } = span;
+ const { operationName, process, duration, relativeStartTime, spanID, logs, tags, warnings, uiID } = span;
const overviewItems = [
{
key: 'svc',
@@ -72,11 +75,13 @@ export default function SpanDetail(props: SpanDetailProps) {
},
];
const deepLinkCopyText = `${window.location.origin}${window.location.pathname}?uiFind=${spanID}`;
-
return (
-
{operationName}
+
+ {operationName}
+ {references.length > 1 && }
+
tagsToggle(spanID)}
+ onToggle={() => tagsToggle(uiID)}
/>
{process.tags && (
processToggle(spanID)}
+ onToggle={() => processToggle(uiID)}
/>
)}
@@ -111,7 +116,7 @@ export default function SpanDetail(props: SpanDetailProps) {
isOpen={logsState.isOpen}
openedItems={logsState.openedItems}
onToggle={() => logsToggle(spanID)}
- onItemToggle={logItem => logItemToggle(spanID, logItem)}
+ onItemToggle={logItem => logItemToggle(uiID, logItem)}
timestamp={traceStartTime}
/>
)}
@@ -122,7 +127,7 @@ export default function SpanDetail(props: SpanDetailProps) {
label={
Warnings }
data={warnings}
isOpen={isWarningsOpen}
- onToggle={() => warningsToggle(spanID)}
+ onToggle={() => warningsToggle(uiID)}
/>
)}
diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetailRow.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetailRow.tsx
index 685f2d149e..3420e614cc 100644
--- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetailRow.tsx
+++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetailRow.tsx
@@ -19,7 +19,7 @@ import DetailState from './SpanDetail/DetailState';
import SpanTreeOffset from './SpanTreeOffset';
import TimelineRow from './TimelineRow';
-import { Log, Span, KeyValuePair, Link } from '../../../types/trace';
+import { Log, Span, KeyValuePair, Link, SpanReference } from '../../../types/trace';
import './SpanDetailRow.css';
@@ -27,20 +27,21 @@ type SpanDetailRowProps = {
color: string;
columnDivision: number;
detailState: DetailState;
- onDetailToggled: (spanID: string) => void;
+ onDetailToggled: (UiID: string) => void;
linksGetter: (span: Span, links: KeyValuePair[], index: number) => Link[];
- logItemToggle: (spanID: string, log: Log) => void;
- logsToggle: (spanID: string) => void;
- processToggle: (spanID: string) => void;
- warningsToggle: (spanID: string) => void;
+ logItemToggle: (UiID: string, log: Log) => void;
+ logsToggle: (UiID: string) => void;
+ processToggle: (UiID: string) => void;
+ warningsToggle: (UiID: string) => void;
span: Span;
- tagsToggle: (spanID: string) => void;
+ tagsToggle: (UiID: string) => void;
traceStartTime: number;
+ references: SpanReference[];
};
export default class SpanDetailRow extends React.PureComponent {
_detailToggle = () => {
- this.props.onDetailToggled(this.props.span.spanID);
+ this.props.onDetailToggled(this.props.span.uiID);
};
_linksGetter = (items: KeyValuePair[], itemIndex: number) => {
@@ -60,6 +61,7 @@ export default class SpanDetailRow extends React.PureComponent
@@ -87,6 +89,7 @@ export default class SpanDetailRow extends React.PureComponent
diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/VirtualizedTraceView.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/VirtualizedTraceView.tsx
index 2b129fa264..f73b79b454 100644
--- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/VirtualizedTraceView.tsx
+++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/VirtualizedTraceView.tsx
@@ -62,6 +62,7 @@ type TDispatchProps = {
detailLogItemToggle: (spanID: string, log: Log) => void;
detailLogsToggle: (spanID: string) => void;
detailWarningsToggle: (spanID: string) => void;
+ detailReferencesToggle: (UiID: string) => void;
detailProcessToggle: (spanID: string) => void;
detailTagsToggle: (spanID: string) => void;
detailToggle: (spanID: string) => void;
@@ -96,7 +97,7 @@ function generateRowStates(
const rowStates = [];
for (let i = 0; i < spans.length; i++) {
const span = spans[i];
- const { spanID, depth } = span;
+ const { uiID, depth } = span;
let hidden = false;
if (collapseDepth != null) {
if (depth >= collapseDepth) {
@@ -108,7 +109,7 @@ function generateRowStates(
if (hidden) {
continue;
}
- if (childrenHiddenIDs.has(spanID)) {
+ if (childrenHiddenIDs.has(uiID)) {
collapseDepth = depth + 1;
}
rowStates.push({
@@ -116,7 +117,7 @@ function generateRowStates(
isDetail: false,
spanIndex: i,
});
- if (detailStates.has(spanID)) {
+ if (detailStates.has(uiID)) {
rowStates.push({
span,
isDetail: true,
@@ -156,7 +157,6 @@ export class VirtualizedTraceViewImpl extends React.Component {
const { isDetail, span } = this.rowStates[index];
- return `${span.spanID}--${isDetail ? 'detail' : 'bar'}`;
+ return `${span.uiID}--${isDetail ? 'detail' : 'bar'}`;
};
getIndexFromKey = (key: string) => {
const parts = key.split('--');
- const _spanID = parts[0];
+ const _UiID = parts[0];
const _isDetail = parts[1] === 'detail';
const max = this.rowStates.length;
for (let i = 0; i < max; i++) {
const { span, isDetail } = this.rowStates[i];
- if (span.spanID === _spanID && isDetail === _isDetail) {
+ if (span.uiID === _UiID && isDetail === _isDetail) {
return i;
}
}
@@ -307,7 +307,7 @@ export class VirtualizedTraceViewImpl extends React.Component
);
diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/duck.track.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/duck.track.tsx
index 2604ee9244..e46275eb5b 100644
--- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/duck.track.tsx
+++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/duck.track.tsx
@@ -15,14 +15,14 @@
import { Store } from 'redux';
import { Action } from 'redux-actions';
-import { actionTypes as types, TSpanIdValue, TSpanIdLogValue, TWidthValue } from './duck';
+import { actionTypes as types, TSpanIdLogValue, TWidthValue, TUiIdValue } from './duck';
import DetailState from './SpanDetail/DetailState';
import { ReduxState } from '../../../types';
import { trackEvent } from '../../../utils/tracking';
import { getToggleValue } from '../../../utils/tracking/common';
type TSpanIdHooks = {
- [actionType: string]: (store: Store, action: Action) => void;
+ [actionType: string]: (store: Store, action: Action) => void;
};
const ACTION_RESIZE = 'resize';
@@ -37,13 +37,13 @@ export const CATEGORY_COLUMN = `${CATEGORY_BASE}/column`;
export const CATEGORY_PARENT = `${CATEGORY_BASE}/parent`;
export const CATEGORY_ROW = `${CATEGORY_BASE}/row`;
-function getDetail(store: Store, { payload }: Action) {
- return payload ? store.getState().traceTimeline.detailStates.get(payload.spanID) : undefined;
+function getDetail(store: Store, { payload }: Action) {
+ return payload ? store.getState().traceTimeline.detailStates.get(payload.UiID) : undefined;
}
function trackDetailState(
store: Store,
- action: Action,
+ action: Action,
trackFn: (detailState: DetailState) => void
) {
const detailState = getDetail(store, action);
@@ -52,7 +52,7 @@ function trackDetailState(
}
}
-function trackParent(store: Store, { payload }: Action) {
+function trackParent(store: Store, { payload }: Action) {
if (!payload) {
return;
}
@@ -61,13 +61,13 @@ function trackParent(store: Store, { payload }: Action
if (!traceID) {
return;
}
- const { spanID } = payload;
- const isHidden = st.traceTimeline.childrenHiddenIDs.has(spanID);
+ const { UiID } = payload;
+ const isHidden = st.traceTimeline.childrenHiddenIDs.has(UiID);
const trace = st.trace.traces[traceID].data;
if (!trace) {
return;
}
- const span = trace.spans.find(sp => sp.spanID === spanID);
+ const span = trace.spans.find(sp => sp.uiID === UiID);
if (span) {
trackEvent(CATEGORY_PARENT, getToggleValue(!isHidden), span.depth);
}
diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/duck.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/duck.tsx
index 528c2a282f..6816ba2903 100644
--- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/duck.tsx
+++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/duck.tsx
@@ -24,8 +24,10 @@ import guardReducer from '../../../utils/guardReducer';
import spanAncestorIds from '../../../utils/span-ancestor-ids';
// payloads
-export type TSpanIdLogValue = { logItem: Log; spanID: string };
+export type TSpanIdLogValue = { logItem: Log; UiID: string };
export type TSpanIdValue = { spanID: string };
+export type TUiIdValue = { UiID: string };
+
type TSpansValue = { spans: Span[] };
type TTraceUiFindValue = { trace: Trace; uiFind: string | TNil };
export type TWidthValue = { width: number };
@@ -35,6 +37,7 @@ export type TActionTypes =
| TSpansValue
| TTraceUiFindValue
| TWidthValue
+ | TUiIdValue
| {};
type TTimelineViewerActions = {
@@ -69,6 +72,7 @@ export const actionTypes = generateActionTypes('@jaeger-ui/trace-timeline-viewer
'DETAIL_LOGS_TOGGLE',
'DETAIL_LOG_ITEM_TOGGLE',
'DETAIL_WARNINGS_TOGGLE',
+ 'DETAIL_REFERENCES_TOGGLE',
'EXPAND_ALL',
'EXPAND_ONE',
'FOCUS_UI_FIND_MATCHES',
@@ -79,7 +83,7 @@ export const actionTypes = generateActionTypes('@jaeger-ui/trace-timeline-viewer
const fullActions = createActions({
[actionTypes.ADD_HOVER_INDENT_GUIDE_ID]: (spanID: string) => ({ spanID }),
- [actionTypes.CHILDREN_TOGGLE]: (spanID: string) => ({ spanID }),
+ [actionTypes.CHILDREN_TOGGLE]: (UiID: string) => ({ UiID }),
[actionTypes.CLEAR_SHOULD_SCROLL_TO_FIRST_UI_FIND_MATCH]: () => ({}),
[actionTypes.COLLAPSE_ALL]: (spans: Span[]) => ({ spans }),
[actionTypes.COLLAPSE_ONE]: (spans: Span[]) => ({ spans }),
@@ -87,10 +91,11 @@ const fullActions = createActions({
[actionTypes.DETAIL_LOGS_TOGGLE]: (spanID: string) => ({ spanID }),
[actionTypes.EXPAND_ALL]: () => ({}),
[actionTypes.EXPAND_ONE]: (spans: Span[]) => ({ spans }),
- [actionTypes.DETAIL_PROCESS_TOGGLE]: (spanID: string) => ({ spanID }),
- [actionTypes.DETAIL_WARNINGS_TOGGLE]: (spanID: string) => ({ spanID }),
- [actionTypes.DETAIL_TAGS_TOGGLE]: (spanID: string) => ({ spanID }),
- [actionTypes.DETAIL_TOGGLE]: (spanID: string) => ({ spanID }),
+ [actionTypes.DETAIL_PROCESS_TOGGLE]: (UiID: string) => ({ UiID }),
+ [actionTypes.DETAIL_WARNINGS_TOGGLE]: (UiID: string) => ({ UiID }),
+ [actionTypes.DETAIL_REFERENCES_TOGGLE]: (UiID: string) => ({ UiID }),
+ [actionTypes.DETAIL_TAGS_TOGGLE]: (UiID: string) => ({ UiID }),
+ [actionTypes.DETAIL_TOGGLE]: (UiID: string) => ({ UiID }),
[actionTypes.FOCUS_UI_FIND_MATCHES]: (trace: Trace, uiFind: string | TNil) => ({ trace, uiFind }),
[actionTypes.REMOVE_HOVER_INDENT_GUIDE_ID]: (spanID: string) => ({ spanID }),
[actionTypes.SET_SPAN_NAME_COLUMN_WIDTH]: (width: number) => ({ width }),
@@ -106,14 +111,14 @@ function calculateFocusedFindRowStates(uiFind: string, spans: Span[]) {
let shouldScrollToFirstUiFindMatch: boolean = false;
spans.forEach(span => {
- spansMap.set(span.spanID, span);
- childrenHiddenIDs.add(span.spanID);
+ spansMap.set(span.uiID, span);
+ childrenHiddenIDs.add(span.uiID);
});
- const matchedSpanIds = filterSpans(uiFind, spans);
- if (matchedSpanIds && matchedSpanIds.size) {
- matchedSpanIds.forEach(spanID => {
- const span = spansMap.get(spanID);
- detailStates.set(spanID, new DetailState());
+ const matchedSpanUiIds = filterSpans(uiFind, spans);
+ if (matchedSpanUiIds && matchedSpanUiIds.size) {
+ matchedSpanUiIds.forEach(spanUiID => {
+ const span = spansMap.get(spanUiID);
+ detailStates.set(spanUiID, new DetailState());
spanAncestorIds(span).forEach(ancestorID => childrenHiddenIDs.delete(ancestorID));
});
shouldScrollToFirstUiFindMatch = true;
@@ -157,12 +162,12 @@ function setColumnWidth(state: TTraceTimeline, { width }: TWidthValue): TTraceTi
return { ...state, spanNameColumnWidth: width };
}
-function childrenToggle(state: TTraceTimeline, { spanID }: TSpanIdValue): TTraceTimeline {
+function childrenToggle(state: TTraceTimeline, { UiID }: TUiIdValue): TTraceTimeline {
const childrenHiddenIDs = new Set(state.childrenHiddenIDs);
- if (childrenHiddenIDs.has(spanID)) {
- childrenHiddenIDs.delete(spanID);
+ if (childrenHiddenIDs.has(UiID)) {
+ childrenHiddenIDs.delete(UiID);
} else {
- childrenHiddenIDs.add(spanID);
+ childrenHiddenIDs.add(UiID);
}
return { ...state, childrenHiddenIDs };
}
@@ -192,18 +197,18 @@ export function collapseOne(state: TTraceTimeline, { spans }: TSpansValue) {
let nearestCollapsedAncestor: Span | undefined;
const childrenHiddenIDs = spans.reduce((res, curSpan) => {
if (nearestCollapsedAncestor && curSpan.depth <= nearestCollapsedAncestor.depth) {
- res.add(nearestCollapsedAncestor.spanID);
+ res.add(nearestCollapsedAncestor.uiID);
if (curSpan.hasChildren) {
nearestCollapsedAncestor = curSpan;
}
- } else if (curSpan.hasChildren && !res.has(curSpan.spanID)) {
+ } else if (curSpan.hasChildren && !res.has(curSpan.uiID)) {
nearestCollapsedAncestor = curSpan;
}
return res;
}, new Set(state.childrenHiddenIDs));
// The last one
if (nearestCollapsedAncestor) {
- childrenHiddenIDs.add(nearestCollapsedAncestor.spanID);
+ childrenHiddenIDs.add(nearestCollapsedAncestor.uiID);
}
return { ...state, childrenHiddenIDs };
}
@@ -218,8 +223,8 @@ export function expandOne(state: TTraceTimeline, { spans }: TSpansValue) {
if (s.depth <= prevExpandedDepth) {
expandNextHiddenSpan = true;
}
- if (expandNextHiddenSpan && res.has(s.spanID)) {
- res.delete(s.spanID);
+ if (expandNextHiddenSpan && res.has(s.uiID)) {
+ res.delete(s.uiID);
expandNextHiddenSpan = false;
prevExpandedDepth = s.depth;
}
@@ -228,22 +233,22 @@ export function expandOne(state: TTraceTimeline, { spans }: TSpansValue) {
return { ...state, childrenHiddenIDs };
}
-function detailToggle(state: TTraceTimeline, { spanID }: TSpanIdValue) {
+function detailToggle(state: TTraceTimeline, { UiID }: TUiIdValue) {
const detailStates = new Map(state.detailStates);
- if (detailStates.has(spanID)) {
- detailStates.delete(spanID);
+ if (detailStates.has(UiID)) {
+ detailStates.delete(UiID);
} else {
- detailStates.set(spanID, new DetailState());
+ detailStates.set(UiID, new DetailState());
}
return { ...state, detailStates };
}
function detailSubsectionToggle(
- subSection: 'tags' | 'process' | 'logs' | 'warnings',
+ subSection: 'tags' | 'process' | 'logs' | 'warnings' | 'references',
state: TTraceTimeline,
- { spanID }: TSpanIdValue
+ { UiID }: TUiIdValue
) {
- const old = state.detailStates.get(spanID);
+ const old = state.detailStates.get(UiID);
if (!old) {
return state;
}
@@ -258,7 +263,7 @@ function detailSubsectionToggle(
detailState = old.toggleLogs();
}
const detailStates = new Map(state.detailStates);
- detailStates.set(spanID, detailState);
+ detailStates.set(UiID, detailState);
return { ...state, detailStates };
}
@@ -267,14 +272,14 @@ const detailProcessToggle = detailSubsectionToggle.bind(null, 'process');
const detailLogsToggle = detailSubsectionToggle.bind(null, 'logs');
const detailWarningsToggle = detailSubsectionToggle.bind(null, 'warnings');
-function detailLogItemToggle(state: TTraceTimeline, { spanID, logItem }: TSpanIdLogValue) {
- const old = state.detailStates.get(spanID);
+function detailLogItemToggle(state: TTraceTimeline, { UiID, logItem }: TSpanIdLogValue) {
+ const old = state.detailStates.get(UiID);
if (!old) {
return state;
}
const detailState = old.toggleLogItem(logItem);
const detailStates = new Map(state.detailStates);
- detailStates.set(spanID, detailState);
+ detailStates.set(UiID, detailState);
return { ...state, detailStates };
}
diff --git a/packages/jaeger-ui/src/model/transform-trace-data.tsx b/packages/jaeger-ui/src/model/transform-trace-data.tsx
index 46eeb83c3d..827a458d3c 100644
--- a/packages/jaeger-ui/src/model/transform-trace-data.tsx
+++ b/packages/jaeger-ui/src/model/transform-trace-data.tsx
@@ -89,14 +89,21 @@ export default function transformTraceData(data: TraceData & { spans: SpanData[]
const svcCounts: Record = {};
let traceName = '';
- tree.walk((spanID: string, node: TreeNode, depth: number = 0) => {
- if (spanID === '__root__') {
+ tree.walk((spanValue: any, node: TreeNode, depth: number = 0) => {
+ if (spanValue.spanID === '__root__') {
return;
}
- const span = spanMap.get(spanID) as Span;
- if (!span) {
- return;
+ const spanID = spanValue.spanID;
+ const nodeID = spanValue.nodeID;
+ let span = spanMap.get(spanID) as Span;
+ if (nodeID !== spanID) {
+ span = Object.assign({}, span) as Span;
+ // Not sure if this is correct, but it should work because the way we
+ // walk over the tree.
+ spanMap.set(nodeID, span);
}
+
+ span.uiID = nodeID;
const { serviceName } = span.process;
svcCounts[serviceName] = (svcCounts[serviceName] || 0) + 1;
if (!span.references || !span.references.length) {
@@ -111,6 +118,7 @@ export default function transformTraceData(data: TraceData & { spans: SpanData[]
const tagsInfo = deduplicateTags(span.tags);
span.tags = tagsInfo.tags;
span.warnings = span.warnings.concat(tagsInfo.warnings);
+ span.uiParent = spanMap.get(spanValue.parentID);
span.references.forEach(ref => {
const refSpan = spanMap.get(ref.spanID) as Span;
if (refSpan) {
diff --git a/packages/jaeger-ui/src/selectors/trace.js b/packages/jaeger-ui/src/selectors/trace.js
index 0c092f06da..79de609102 100644
--- a/packages/jaeger-ui/src/selectors/trace.js
+++ b/packages/jaeger-ui/src/selectors/trace.js
@@ -49,6 +49,41 @@ export const getTraceSpansAsMap = createSelector(
export const TREE_ROOT_ID = '__root__';
+function addMultipleReferences(multipleParents, spansById, nodesMap) {
+ multipleParents.forEach(node => {
+ spansById.get(node.value.spanID).references.forEach((reference, index) => {
+ const { refType, spanID: parentID } = reference;
+ if (refType === 'CHILD_OF' || refType === 'FOLLOWS_FROM') {
+ const parent = nodesMap.get(parentID);
+ if (index === 0) {
+ // Only do a copy of the n+1 nodes
+ // eslint-disable-next-line no-param-reassign
+ node.value.parentID = parentID;
+ parent.addChild(node);
+ } else {
+ const root = node.copyTree(n => {
+ const { spanID } = n.value;
+ const nodeID = `${spanID}__${index}`;
+ const newNode = new TreeNode({ spanID, nodeID, parentID: `${parentID}__${index}` });
+ nodesMap.set(nodeID, newNode);
+ return newNode;
+ });
+ root.value.parentID = parent.value.nodeID;
+ parent.addChild(root);
+ }
+ }
+ });
+ });
+}
+
+// creates an object { spanI
+function nodeValue(spanID, nodeID) {
+ if (nodeID === undefined) {
+ return { spanID, nodeID: spanID };
+ }
+ return { spanID, nodeID };
+}
+
/**
* Build a tree of { value: spanID, children } items derived from the
* `span.references` information. The tree represents the grouping of parent /
@@ -64,30 +99,37 @@ export const TREE_ROOT_ID = '__root__';
* between spans in the trace.
*/
export function getTraceSpanIdsAsTree(trace) {
- const nodesById = new Map(trace.spans.map(span => [span.spanID, new TreeNode(span.spanID)]));
+ const nodesById = new Map(trace.spans.map(span => [span.spanID, new TreeNode(nodeValue(span.spanID))]));
const spansById = new Map(trace.spans.map(span => [span.spanID, span]));
- const root = new TreeNode(TREE_ROOT_ID);
+ const multipleParents = [];
+ const root = new TreeNode({ spanID: TREE_ROOT_ID });
trace.spans.forEach(span => {
const node = nodesById.get(span.spanID);
if (Array.isArray(span.references) && span.references.length) {
- const { refType, spanID: parentID } = span.references[0];
- if (refType === 'CHILD_OF' || refType === 'FOLLOWS_FROM') {
- const parent = nodesById.get(parentID) || root;
- parent.children.push(node);
- } else {
- throw new Error(`Unrecognized ref type: ${refType}`);
+ if (span.references.length > 1) {
+ multipleParents.push(node);
+ } else if (span.references.length === 1) {
+ const { refType, spanID: parentID } = span.references[0];
+ if (refType === 'CHILD_OF' || refType === 'FOLLOWS_FROM') {
+ const parent = nodesById.get(parentID) || root;
+ node.value.parentID = parent.value.nodeID;
+ parent.children.push(node);
+ } else {
+ throw new Error(`Unrecognized ref type: ${refType}`);
+ }
}
} else {
root.children.push(node);
}
});
+ addMultipleReferences(multipleParents, spansById, nodesById);
const comparator = (nodeA, nodeB) => {
- const a = spansById.get(nodeA.value);
- const b = spansById.get(nodeB.value);
+ const a = spansById.get(nodeA.value.spanID);
+ const b = spansById.get(nodeB.value.spanID);
return +(a.startTime > b.startTime) || +(a.startTime === b.startTime) - 1;
};
- trace.spans.forEach(span => {
- const node = nodesById.get(span.spanID);
+
+ Array.from(nodesById.values()).forEach(node => {
if (node.children.length > 1) {
node.children.sort(comparator);
}
diff --git a/packages/jaeger-ui/src/types/trace.tsx b/packages/jaeger-ui/src/types/trace.tsx
index c06b1f4933..a0fac3cfa7 100644
--- a/packages/jaeger-ui/src/types/trace.tsx
+++ b/packages/jaeger-ui/src/types/trace.tsx
@@ -58,16 +58,22 @@ export type SpanData = {
warnings?: Array | null;
};
-export type Span = SpanData & {
- depth: number;
- hasChildren: boolean;
- process: Process;
- relativeStartTime: number;
- tags: NonNullable;
- references: NonNullable;
- warnings: NonNullable;
+type SpanUIData = {
+ uiID: string;
+ uiParent: Span | undefined;
};
+export type Span = SpanData &
+ SpanUIData & {
+ depth: number;
+ hasChildren: boolean;
+ process: Process;
+ relativeStartTime: number;
+ tags: NonNullable;
+ references: NonNullable;
+ warnings: NonNullable;
+ };
+
export type TraceData = {
processes: Record;
traceID: string;
diff --git a/packages/jaeger-ui/src/utils/TreeNode.js b/packages/jaeger-ui/src/utils/TreeNode.js
index b3ef63c8cc..04f90b77e9 100644
--- a/packages/jaeger-ui/src/utils/TreeNode.js
+++ b/packages/jaeger-ui/src/utils/TreeNode.js
@@ -82,6 +82,27 @@ export default class TreeNode {
return findPath(this, []);
}
+ copyTree(copyFn) {
+ const nodeStack = [];
+ const copyStack = [];
+ nodeStack.push(this);
+ const copyRoot = copyFn ? copyFn(this) : new TreeNode(this.value);
+ copyStack.push(copyRoot);
+ while (nodeStack.length) {
+ const node = nodeStack.pop();
+ const parent = copyStack.pop();
+ let i = node.children.length - 1;
+ while (i >= 0) {
+ const childCopy = copyFn ? copyFn(node.children[i]) : new TreeNode(node.children[i].value);
+ parent.addChild(childCopy);
+ nodeStack.push(node.children[i]);
+ copyStack.push(childCopy);
+ i--;
+ }
+ }
+ return copyRoot;
+ }
+
walk(fn, depth = 0) {
const nodeStack = [];
let actualDepth = depth;
diff --git a/packages/jaeger-ui/src/utils/filter-spans.tsx b/packages/jaeger-ui/src/utils/filter-spans.tsx
index 2eb4406179..712a1663f3 100644
--- a/packages/jaeger-ui/src/utils/filter-spans.tsx
+++ b/packages/jaeger-ui/src/utils/filter-spans.tsx
@@ -62,6 +62,6 @@ export default function filterSpans(textFilter: string, spans: Span[] | TNil) {
includeFilters.some(filter => filter === span.spanID);
// declare as const because need to disambiguate the type
- const rv: Set = new Set(spans.filter(isSpanAMatch).map((span: Span) => span.spanID));
+ const rv: Set = new Set(spans.filter(isSpanAMatch).map((span: Span) => span.uiID));
return rv;
}
diff --git a/packages/jaeger-ui/src/utils/span-ancestor-ids.tsx b/packages/jaeger-ui/src/utils/span-ancestor-ids.tsx
index fa999b3fce..db41267142 100644
--- a/packages/jaeger-ui/src/utils/span-ancestor-ids.tsx
+++ b/packages/jaeger-ui/src/utils/span-ancestor-ids.tsx
@@ -12,29 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import _find from 'lodash/find';
-import _get from 'lodash/get';
-
import { TNil } from '../types';
import { Span } from '../types/trace';
-function getFirstAncestor(span: Span): Span | TNil {
- return _get(
- _find(
- span.references,
- ({ span: ref, refType }) => ref && ref.spanID && (refType === 'CHILD_OF' || refType === 'FOLLOWS_FROM')
- ),
- 'span'
- );
-}
-
export default function spanAncestorIds(span: Span | TNil): string[] {
if (!span) return [];
const ancestorIDs: Set = new Set();
- let ref = getFirstAncestor(span);
+ let ref = span.uiParent;
while (ref) {
- ancestorIDs.add(ref.spanID);
- ref = getFirstAncestor(ref);
+ ancestorIDs.add(ref.uiID);
+ ref = ref.uiParent;
}
return Array.from(ancestorIDs);
}