diff --git a/packages/jaeger-ui/src/model/ddg/transformTracesToPaths.test.js b/packages/jaeger-ui/src/model/ddg/transformTracesToPaths.test.js index 05970215e7..9980512ed0 100644 --- a/packages/jaeger-ui/src/model/ddg/transformTracesToPaths.test.js +++ b/packages/jaeger-ui/src/model/ddg/transformTracesToPaths.test.js @@ -15,19 +15,35 @@ import transformTracesToPaths from './transformTracesToPaths'; describe('transform traces to ddg paths', () => { - const makeSpan = (spanName, childOf) => ({ + const makeExpectedPath = (pathSpans, trace) => ({ + path: pathSpans.map(({ processID, operationName: operation }) => ({ + service: trace.data.processes[processID].serviceName, + operation, + })), + attributes: [{ key: 'exemplar_trace_id', value: trace.data.traceID }], + }); + const makeSpan = (spanName, parent, kind) => ({ hasChildren: true, operationName: `${spanName} operation`, processID: `${spanName} processID`, - references: childOf + references: parent ? [ { refType: 'CHILD_OF', - span: childOf, - spanID: childOf.spanID, + span: parent, + spanID: parent.spanID, }, ] : [], + tags: + kind === false + ? [] + : [ + { + key: 'span.kind', + value: kind === undefined ? 'server' : kind, + }, + ], spanID: `${spanName} spanID`, }); const makeTrace = (spans, traceID) => ({ @@ -45,109 +61,114 @@ describe('transform traces to ddg paths', () => { traceID, }, }); + const makeTraces = (...traces) => + traces.reduce((res, trace) => ({ ...res, [trace.data.traceID]: trace }), {}); - const linearTraceID = 'linearTraceID'; + const branchingTraceID = 'branchingTraceID'; const missTraceID = 'missTraceID'; - const shortTraceID = 'shortTraceID'; const rootSpan = makeSpan('root'); - const childSpan = makeSpan('child', rootSpan); - const grandchildSpan = makeSpan('grandchild', childSpan); + const focalSpan = makeSpan('focal', rootSpan); + const followsFocalSpan = makeSpan('followsFocal', focalSpan); + followsFocalSpan.hasChildren = false; + const notPathSpan = makeSpan('notPath', rootSpan); + notPathSpan.hasChildren = false; - it('transforms single short trace result payload', () => { - const traces = { - [shortTraceID]: makeTrace([rootSpan, { ...childSpan, hasChildren: false }], shortTraceID), - }; + const shortTrace = makeTrace([rootSpan, { ...focalSpan, hasChildren: false }], 'shortTraceID'); + const shortPath = makeExpectedPath([rootSpan, focalSpan], shortTrace); + const longerTrace = makeTrace([rootSpan, focalSpan, followsFocalSpan], 'longerTraceID'); + const longerPath = makeExpectedPath([rootSpan, focalSpan, followsFocalSpan], longerTrace); + const missTrace = makeTrace([rootSpan, notPathSpan], missTraceID); - const { dependencies: result } = transformTracesToPaths(traces, 'child service'); - expect(result.length).toBe(1); - expect(result[0].path.length).toBe(2); + const focalSvc = shortTrace.data.processes[focalSpan.processID].serviceName; + + it('transforms single short trace result payload', () => { + const { dependencies: result } = transformTracesToPaths(makeTraces(shortTrace), focalSvc); + expect(result).toEqual([shortPath]); }); it('transforms multiple traces result payload', () => { - const traces = { - [shortTraceID]: makeTrace([rootSpan, { ...childSpan, hasChildren: false }], shortTraceID), - [linearTraceID]: makeTrace( - [rootSpan, childSpan, { ...grandchildSpan, hasChildren: false }], - linearTraceID - ), - }; - - const { dependencies: result } = transformTracesToPaths(traces, 'child service'); - expect(result.length).toBe(2); - expect(result[0].path.length).toBe(2); - expect(result[1].path.length).toBe(3); + const { dependencies: result } = transformTracesToPaths(makeTraces(shortTrace, longerTrace), focalSvc); + expect(new Set(result)).toEqual(new Set([shortPath, longerPath])); }); it('ignores paths without focalService', () => { - const branchingTraceID = 'branchingTraceID'; - const uncleSpan = makeSpan('uncle', rootSpan); - uncleSpan.hasChildren = false; - const traces = { - [missTraceID]: makeTrace([rootSpan, childSpan, uncleSpan], missTraceID), - [branchingTraceID]: makeTrace( - [rootSpan, childSpan, uncleSpan, { ...grandchildSpan, hasChildren: false }], - branchingTraceID - ), - }; + const branchingTrace = makeTrace([rootSpan, focalSpan, notPathSpan, followsFocalSpan], branchingTraceID); - const { dependencies: result } = transformTracesToPaths(traces, 'child service'); - expect(result.length).toBe(1); - expect(result[0].path.length).toBe(3); + const { dependencies: result } = transformTracesToPaths(makeTraces(missTrace, branchingTrace), focalSvc); + expect(result).toEqual([makeExpectedPath([rootSpan, focalSpan, followsFocalSpan], branchingTrace)]); }); it('matches service and operation names', () => { - const childSpanWithDiffOp = { - ...childSpan, + const focalSpanWithDiffOp = { + ...focalSpan, hasChildren: false, operationName: 'diff operation', }; - const traces = { - [missTraceID]: makeTrace([rootSpan, childSpanWithDiffOp], missTraceID), - [linearTraceID]: makeTrace( - [rootSpan, childSpan, { ...grandchildSpan, hasChildren: false }], - linearTraceID - ), - }; - - const { dependencies: result } = transformTracesToPaths(traces, 'child service'); - expect(result.length).toBe(2); - expect(result[0].path.length).toBe(2); - expect(result[1].path.length).toBe(3); - - const { dependencies: resultWithOp } = transformTracesToPaths(traces, 'child service', 'child operation'); - expect(resultWithOp.length).toBe(1); - expect(resultWithOp[0].path.length).toBe(3); + const diffOpTrace = makeTrace([rootSpan, focalSpanWithDiffOp], 'diffOpTraceID'); + const traces = makeTraces(diffOpTrace, longerTrace); + + const { dependencies: result } = transformTracesToPaths(traces, focalSvc); + expect(new Set(result)).toEqual( + new Set([makeExpectedPath([rootSpan, focalSpanWithDiffOp], diffOpTrace), longerPath]) + ); + + const { dependencies: resultWithFocalOp } = transformTracesToPaths( + traces, + focalSvc, + focalSpan.operationName + ); + expect(resultWithFocalOp).toEqual([longerPath]); }); it('transforms multiple paths from single trace', () => { - const traces = { - [linearTraceID]: makeTrace( - [rootSpan, { ...childSpan, hasChildren: false }, { ...grandchildSpan, hasChildren: false }], - linearTraceID - ), - }; - - const { dependencies: result } = transformTracesToPaths(traces, 'child service'); - expect(result.length).toBe(2); - expect(result[0].path.length).toBe(2); - expect(result[1].path.length).toBe(3); + const alsoFollowsFocalSpan = makeSpan('alsoFollows', focalSpan); + alsoFollowsFocalSpan.hasChildren = false; + const branchingTrace = makeTrace( + [rootSpan, focalSpan, followsFocalSpan, alsoFollowsFocalSpan], + branchingTraceID + ); + + const { dependencies: result } = transformTracesToPaths(makeTraces(branchingTrace), focalSvc); + expect(new Set(result)).toEqual( + new Set([ + makeExpectedPath([rootSpan, focalSpan, alsoFollowsFocalSpan], branchingTrace), + makeExpectedPath([rootSpan, focalSpan, followsFocalSpan], branchingTrace), + ]) + ); }); it('errors if span has ancestor id not in trace data', () => { - const traces = { - [linearTraceID]: makeTrace([rootSpan, { ...grandchildSpan, hasChildren: false }], linearTraceID), - }; - - expect(() => transformTracesToPaths(traces, 'child service')).toThrowError(/Ancestor spanID.*not found/); + const traces = makeTraces(makeTrace([rootSpan, followsFocalSpan], missTraceID)); + expect(() => transformTracesToPaths(traces, focalSvc)).toThrowError(/Ancestor spanID.*not found/); }); it('skips trace without data', () => { const traces = { - [shortTraceID]: makeTrace([rootSpan, { ...childSpan, hasChildren: false }], shortTraceID), + ...makeTraces(shortTrace), noData: {}, }; - const { dependencies: result } = transformTracesToPaths(traces, 'child service'); + const { dependencies: result } = transformTracesToPaths(traces, focalSvc); expect(result.length).toBe(1); }); + + it("omits span if tags does not have span.kind === 'server'", () => { + const badSpanName = 'test bad span name'; + + const clientSpan = makeSpan(badSpanName, focalSpan, 'client'); + clientSpan.hasChildren = false; + const clientTrace = makeTrace([rootSpan, focalSpan, clientSpan], 'clientTraceID'); + + const kindlessSpan = makeSpan(badSpanName, focalSpan, false); + kindlessSpan.hasChildren = false; + const kindlessTrace = makeTrace([rootSpan, focalSpan, kindlessSpan], 'kindlessTraceID'); + + const { dependencies: result } = transformTracesToPaths(makeTraces(clientTrace, kindlessTrace), focalSvc); + expect(new Set(result)).toEqual( + new Set([ + makeExpectedPath([rootSpan, focalSpan], clientTrace), + makeExpectedPath([rootSpan, focalSpan], kindlessTrace), + ]) + ); + }); }); diff --git a/packages/jaeger-ui/src/model/ddg/transformTracesToPaths.tsx b/packages/jaeger-ui/src/model/ddg/transformTracesToPaths.tsx index d5e5d4b514..76e7b10c59 100644 --- a/packages/jaeger-ui/src/model/ddg/transformTracesToPaths.tsx +++ b/packages/jaeger-ui/src/model/ddg/transformTracesToPaths.tsx @@ -18,18 +18,12 @@ import spanAncestorIds from '../../utils/span-ancestor-ids'; import { TDdgPayloadEntry, TDdgPayloadPath, TDdgPayload } from './types'; import { FetchedTrace } from '../../types'; -import { Span, Trace } from '../../types/trace'; - -function convertSpan(span: Span, trace: Trace): TDdgPayloadEntry { - const serviceName = trace.processes[span.processID].serviceName; - const operationName = span.operationName; - return { service: serviceName, operation: operationName }; -} +import { Span } from '../../types/trace'; function transformTracesToPaths( traces: Record, focalService: string, - focalOperation: string | undefined + focalOperation?: string ): TDdgPayload { const dependencies: TDdgPayloadPath[] = []; Object.values(traces).forEach(({ data }) => { @@ -42,13 +36,20 @@ function transformTracesToPaths( return !span.hasChildren; }) .forEach(leaf => { - const path = spanAncestorIds(leaf).map(id => { + const spans = spanAncestorIds(leaf).map(id => { const span = spanMap.get(id); if (!span) throw new Error(`Ancestor spanID ${id} not found in trace ${traceID}`); - - return convertSpan(span, data); + return span; }); - path.push(convertSpan(leaf, data)); + spans.reverse(); + spans.push(leaf); + + const path: TDdgPayloadEntry[] = spans + .filter(span => span.tags.find(({ key, value }) => key === 'span.kind' && value === 'server')) + .map(({ processID, operationName: operation }) => ({ + service: data.processes[processID].serviceName, + operation, + })); if ( path.some( @@ -56,6 +57,7 @@ function transformTracesToPaths( service === focalService && (!focalOperation || operation === focalOperation) ) ) { + // TODO: Paths should be deduped with all traceIDs #503 dependencies.push({ path, attributes: [ diff --git a/packages/jaeger-ui/src/utils/span-ancestor-ids.tsx b/packages/jaeger-ui/src/utils/span-ancestor-ids.tsx index fa999b3fce..a3fe0d7e72 100644 --- a/packages/jaeger-ui/src/utils/span-ancestor-ids.tsx +++ b/packages/jaeger-ui/src/utils/span-ancestor-ids.tsx @@ -29,12 +29,12 @@ function getFirstAncestor(span: Span): Span | TNil { } export default function spanAncestorIds(span: Span | TNil): string[] { - if (!span) return []; - const ancestorIDs: Set = new Set(); + const ancestorIDs: string[] = []; + if (!span) return ancestorIDs; let ref = getFirstAncestor(span); while (ref) { - ancestorIDs.add(ref.spanID); + ancestorIDs.push(ref.spanID); ref = getFirstAncestor(ref); } - return Array.from(ancestorIDs); + return ancestorIDs; }