diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/VirtualizedTraceView.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/VirtualizedTraceView.tsx index d139da234d..4259668654 100644 --- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/VirtualizedTraceView.tsx +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/VirtualizedTraceView.tsx @@ -316,7 +316,10 @@ export class VirtualizedTraceViewImpl extends React.Component getLinks(span, items, itemIndex); + linksGetter = (span: Span, items: KeyValuePair[], itemIndex: number) => { + const { trace } = this.props; + return getLinks(span, items, itemIndex, trace); + } renderRow = (key: string, style: React.CSSProperties, index: number, attrs: {}) => { const { isDetail, span, spanIndex } = this.getRowStates()[index]; diff --git a/packages/jaeger-ui/src/model/link-patterns.test.js b/packages/jaeger-ui/src/model/link-patterns.test.js index 8ff42b5925..30e3dbcb70 100644 --- a/packages/jaeger-ui/src/model/link-patterns.test.js +++ b/packages/jaeger-ui/src/model/link-patterns.test.js @@ -17,6 +17,7 @@ import { createTestFunction, getParameterInArray, getParameterInAncestor, + getParameterInTrace, processLinkPattern, computeLinks, createGetLinks, @@ -296,6 +297,35 @@ describe('getParameterInAncestor()', () => { }); }); +describe('getParameterInTrace()', () => { + const trace = { + processes: [], + traceName: 'theTrace', + traceID: 'trc1', + spans: [], + startTime: 1000, + endTime: 3000, + duration: 2000, + services: [], + }; + + it('returns an entry that is present', () => { + expect(getParameterInTrace('traceID', trace)).toEqual({ + key: 'traceID', + value: trace.traceID, + }); + }); + + it('returns undefined when the entry cannot be found', () => { + expect(getParameterInTrace('someThingElse', trace)).toBeUndefined(); + }); + + it('returns undefined when there is no trace', () => { + expect(getParameterInTrace('traceID')).toBeUndefined(); + }); + +}); + describe('computeTraceLink()', () => { const linkPatterns = [ { @@ -354,11 +384,18 @@ describe('computeLinks()', () => { url: 'http://example.com/?myKey=#{myOtherKey}&myKey=#{myKey}', text: 'second link (#{myOtherKey})', }, + { + type: 'tags', + key: 'myThirdKey', + url: 'http://example.com/?myKey1=#{myKey}&myKey=#{myThirdKey}&traceID=#{traceID}&startTime=#{startTime}', + text: 'third link (#{myThirdKey}) for traceID - #{traceID}', + } ].map(processLinkPattern); const spans = [ { depth: 0, process: {}, tags: [{ key: 'myKey', value: 'valueOfMyKey' }] }, { depth: 1, process: {}, logs: [{ fields: [{ key: 'myOtherKey', value: 'valueOfMy+Other+Key' }] }] }, + { depth: 1, process: {}, tags: [{ key: 'myThirdKey', value: 'valueOfThirdMyKey' }] }, ]; spans[1].references = [ { @@ -366,6 +403,23 @@ describe('computeLinks()', () => { span: spans[0], }, ]; + spans[2].references = [ + { + refType: 'CHILD_OF', + span: spans[0], + }, + ]; + + const trace = { + processes: [], + traceName: 'theTrace', + traceID: 'trc1', + spans: [], + startTime: 1000, + endTime: 3000, + duration: 2000, + services: [], + }; it('correctly computes links', () => { expect(computeLinks(linkPatterns, spans[0], spans[0].tags, 0)).toEqual([ @@ -380,6 +434,12 @@ describe('computeLinks()', () => { text: 'second link (valueOfMy+Other+Key)', }, ]); + expect(computeLinks(linkPatterns, spans[2], spans[2].tags, 0, trace)).toEqual([ + { + url: 'http://example.com/?myKey1=valueOfMyKey&myKey=valueOfThirdMyKey&traceID=trc1&startTime=1000', + text: 'third link (valueOfThirdMyKey) for traceID - trc1', + }, + ]); }); }); diff --git a/packages/jaeger-ui/src/model/link-patterns.tsx b/packages/jaeger-ui/src/model/link-patterns.tsx index 5a95dfeb1b..f817db5735 100644 --- a/packages/jaeger-ui/src/model/link-patterns.tsx +++ b/packages/jaeger-ui/src/model/link-patterns.tsx @@ -110,6 +110,22 @@ export function getParameterInAncestor(name: string, span: Span) { } currentSpan = getParent(currentSpan); } + + return undefined; +} + +export function getParameterInTrace(name: string, trace: Trace | undefined) { + if(trace) { + const validTraceKeys = (Object.keys(trace) as (keyof Trace)[]).filter( + key => typeof trace[key] === 'string' || typeof trace[key] === 'number' + ); + + const key = name as keyof Trace; + if(validTraceKeys.includes(key)) { + return { key: key, value: trace[key] }; + } + } + return undefined; } @@ -152,7 +168,8 @@ export function computeLinks( linkPatterns: ProcessedLinkPattern[], span: Span, items: KeyValuePair[], - itemIndex: number + itemIndex: number, + trace: Trace | undefined ) { const item = items[itemIndex]; let type = 'logs'; @@ -170,15 +187,23 @@ export function computeLinks( const parameterValues: Record = {}; const allParameters = pattern.parameters.every(parameter => { let entry = getParameterInArray(parameter, items); + if (!entry && !processTags) { // do not look in ancestors for process tags because the same object may appear in different places in the hierarchy // and the cache in getLinks uses that object as a key entry = getParameterInAncestor(parameter, span); } + + // look up in trace for matching keys + if(!entry) { + entry = getParameterInTrace(parameter, trace); + } + if (entry) { parameterValues[parameter] = entry.value; return true; } + // eslint-disable-next-line no-console console.warn( `Skipping link pattern, missing parameter ${parameter} for key ${item.key} in ${type}.`, @@ -198,14 +223,14 @@ export function computeLinks( } export function createGetLinks(linkPatterns: ProcessedLinkPattern[], cache: WeakMap) { - return (span: Span, items: KeyValuePair[], itemIndex: number) => { + return (span: Span, items: KeyValuePair[], itemIndex: number, trace: Trace | undefined) => { if (linkPatterns.length === 0) { return []; } const item = items[itemIndex]; let result = cache.get(item); if (!result) { - result = computeLinks(linkPatterns, span, items, itemIndex); + result = computeLinks(linkPatterns, span, items, itemIndex, trace); cache.set(item, result); } return result;