diff --git a/packages/jaeger-ui/src/components/DeepDependencies/Graph/DdgNodeContent/index.tsx b/packages/jaeger-ui/src/components/DeepDependencies/Graph/DdgNodeContent/index.tsx index de85cc9384..07618b15ba 100644 --- a/packages/jaeger-ui/src/components/DeepDependencies/Graph/DdgNodeContent/index.tsx +++ b/packages/jaeger-ui/src/components/DeepDependencies/Graph/DdgNodeContent/index.tsx @@ -83,25 +83,30 @@ export default class DdgNodeContent extends React.PureComponent { const { vertexKey, getVisiblePathElems } = this.props; const elems = getVisiblePathElems(vertexKey); if (elems) { - const ids: Set = new Set(); + const urlIds: Set = new Set(); let currLength = MIN_LENGTH; - for (let i = 0; i < elems.length; i++) { - const id = elems[i].memberOf.traceID; - if (ids.has(id)) { - continue; - } - // Keep track of the length, then break if it is too long, to avoid opening a tab with a URL that the - // backend cannot process, even if there are more traceIDs - currLength += PARAM_NAME_LENGTH + id.length; - if (currLength > MAX_LENGTH) { - break; - } - ids.add(id); - if (ids.size >= MAX_LINKED_TRACES) { - break; + // Because there is a limit on traceIDs, attempt to get some from each elem rather than all from one. + const allIDs = elems.map(({ memberOf: m }) => m.traceIDs.slice()); + while (allIDs.length) { + const ids = allIDs.shift(); + if (ids && ids.length) { + const id = ids.pop(); + if (id && !urlIds.has(id)) { + // Keep track of the length, then break if it is too long, to avoid opening a tab with a URL that + // the backend cannot process, even if there are more traceIDs + currLength += PARAM_NAME_LENGTH + id.length; + if (currLength > MAX_LENGTH) { + break; + } + urlIds.add(id); + if (urlIds.size >= MAX_LINKED_TRACES) { + break; + } + } + allIDs.push(ids); } } - window.open(getSearchUrl({ traceID: Array.from(ids) }), '_blank'); + window.open(getSearchUrl({ traceID: Array.from(urlIds) }), '_blank'); } }; diff --git a/packages/jaeger-ui/src/components/DeepDependencies/Header/HopsSelector/index.test.js b/packages/jaeger-ui/src/components/DeepDependencies/Header/HopsSelector/index.test.js index 0c1c7aa7dd..ffe971e6d4 100644 --- a/packages/jaeger-ui/src/components/DeepDependencies/Header/HopsSelector/index.test.js +++ b/packages/jaeger-ui/src/components/DeepDependencies/Header/HopsSelector/index.test.js @@ -27,8 +27,8 @@ import * as codec from '../../../../model/ddg/visibility-codec'; import HopsSelector from '.'; describe('HopsSelector', () => { - const { distanceToPathElems } = transformDdgData([longSimplePath, simplePath].map(wrap), focalPayloadElem); - const { distanceToPathElems: shortPathElems } = transformDdgData([shortPath].map(wrap), focalPayloadElem); + const { distanceToPathElems } = transformDdgData(wrap([longSimplePath, simplePath]), focalPayloadElem); + const { distanceToPathElems: shortPathElems } = transformDdgData(wrap([shortPath]), focalPayloadElem); describe('without distanceToPathElems', () => { it('renders empty div', () => { diff --git a/packages/jaeger-ui/src/model/ddg/GraphModel/getPathElemHasher.test.js b/packages/jaeger-ui/src/model/ddg/GraphModel/getPathElemHasher.test.js index 28bbc3ead7..819d5d076d 100644 --- a/packages/jaeger-ui/src/model/ddg/GraphModel/getPathElemHasher.test.js +++ b/packages/jaeger-ui/src/model/ddg/GraphModel/getPathElemHasher.test.js @@ -14,6 +14,7 @@ import GraphModel from './index'; import transformDdgData from '../transformDdgData'; +import { wrap } from '../sample-paths.test.resources'; import { EDdgDensity } from '../types'; function makePayloadEntry(pairStr) { @@ -30,12 +31,12 @@ const payload = ` ` .trim() .split('\n') - .map(line => ({ - path: line + .map(line => + line .trim() .split(/\s+/g) - .map(makePayloadEntry), - })); + .map(makePayloadEntry) + ); const testTable = [ // showOp, density, number of expected vertices @@ -52,7 +53,7 @@ const testTable = [ ]; describe('getPathElemHasher()', () => { - const ddgModel = transformDdgData(payload, makePayloadEntry('focal:focal')); + const ddgModel = transformDdgData(wrap(payload), makePayloadEntry('focal:focal')); describe('creates vertices based on density and showOp', () => { it.each(testTable)('showOp: %p \t density: %p', (showOp, density, verticesCount) => { diff --git a/packages/jaeger-ui/src/model/ddg/GraphModel/index.test.js b/packages/jaeger-ui/src/model/ddg/GraphModel/index.test.js index b5d85cd73e..3b7f111e0c 100644 --- a/packages/jaeger-ui/src/model/ddg/GraphModel/index.test.js +++ b/packages/jaeger-ui/src/model/ddg/GraphModel/index.test.js @@ -25,9 +25,9 @@ import { EDdgDensity } from '../types'; import { encode } from '../visibility-codec'; describe('GraphModel', () => { - const convergentModel = transformDdgData(convergentPaths.map(wrap), focalPayloadElem); - const doubleFocalModel = transformDdgData([doubleFocalPath, simplePath].map(wrap), focalPayloadElem); - const simpleModel = transformDdgData([simplePath].map(wrap), focalPayloadElem); + const convergentModel = transformDdgData(wrap(convergentPaths), focalPayloadElem); + const doubleFocalModel = transformDdgData(wrap([doubleFocalPath, simplePath]), focalPayloadElem); + const simpleModel = transformDdgData(wrap([simplePath]), focalPayloadElem); /** * This function takes in a Graph and validates the structure based on the expected vertices. diff --git a/packages/jaeger-ui/src/model/ddg/sample-paths.test.resources.js b/packages/jaeger-ui/src/model/ddg/sample-paths.test.resources.js index 831a3d078b..0f31a50cee 100644 --- a/packages/jaeger-ui/src/model/ddg/sample-paths.test.resources.js +++ b/packages/jaeger-ui/src/model/ddg/sample-paths.test.resources.js @@ -89,4 +89,6 @@ export const convergentPaths = [ [firstPayloadElem, focalPayloadElem, divergentPayloadElem, afterPayloadElem, lastPayloadElem], ]; -export const wrap = path => ({ path }); +export const wrap = paths => ({ + dependencies: paths.map(path => ({ path, attributes: [] })), +}); diff --git a/packages/jaeger-ui/src/model/ddg/transformDdgData.test.js b/packages/jaeger-ui/src/model/ddg/transformDdgData.test.js index 22ab519a89..3c77519a8f 100644 --- a/packages/jaeger-ui/src/model/ddg/transformDdgData.test.js +++ b/packages/jaeger-ui/src/model/ddg/transformDdgData.test.js @@ -26,7 +26,7 @@ describe('transform ddg data', () => { ? { service: focalPayloadElem.service } : focalPayloadElem; const { paths, services, visIdxToPathElem } = transformDdgData( - payload.map(testResources.wrap), + testResources.wrap(payload), focalPayloadElemArgument ); @@ -124,11 +124,11 @@ describe('transform ddg data', () => { almostDoubleFocalPath, } = testResources; const { visIdxToPathElem: presortedPathsVisIdxToPathElem } = transformDdgData( - [simplePath, doubleFocalPath, almostDoubleFocalPath, longSimplePath].map(testResources.wrap), + testResources.wrap([simplePath, doubleFocalPath, almostDoubleFocalPath, longSimplePath]), focalPayloadElem ); const { visIdxToPathElem: unsortedPathsVisIdxToPathElem } = transformDdgData( - [longSimplePath, almostDoubleFocalPath, simplePath, doubleFocalPath].map(testResources.wrap), + testResources.wrap([longSimplePath, almostDoubleFocalPath, simplePath, doubleFocalPath]), focalPayloadElem ); @@ -166,21 +166,43 @@ describe('transform ddg data', () => { it('throws an error if a path lacks the focalPayloadElem', () => { const { simplePath, noFocalPath, doubleFocalPath, focalPayloadElem } = testResources; expect(() => - transformDdgData([simplePath, noFocalPath, doubleFocalPath].map(testResources.wrap), focalPayloadElem) - ).toThrowError(); + transformDdgData(testResources.wrap([simplePath, noFocalPath, doubleFocalPath]), focalPayloadElem) + ).toThrowError(/focalNode/); }); it('creates equal hashes iff paths are equivalent', () => { const { focalPayloadElem, doubleFocalPath, longSimplePath, simplePath, wrap } = testResources; - const simpleModel = transformDdgData([simplePath, longSimplePath].map(wrap), focalPayloadElem); - const reverseModel = transformDdgData([longSimplePath, simplePath].map(wrap), focalPayloadElem); + const simpleModel = transformDdgData(wrap([simplePath, longSimplePath]), focalPayloadElem); + const reverseModel = transformDdgData(wrap([longSimplePath, simplePath]), focalPayloadElem); expect(reverseModel).not.toEqual(simpleModel); expect(reverseModel).not.toBe(simpleModel); expect(reverseModel.hash).toBe(simpleModel.hash); - const diffModel = transformDdgData([doubleFocalPath].map(wrap), focalPayloadElem); + const diffModel = transformDdgData(wrap([doubleFocalPath]), focalPayloadElem); expect(diffModel.hash).not.toBe(simpleModel.hash); }); + + it('adds traceIDs to paths from attributes', () => { + const { focalPayloadElem, doubleFocalPath, simplePath, wrap } = testResources; + const payload = wrap([simplePath, doubleFocalPath]); + payload.dependencies.forEach((dependency, i) => { + // eslint-disable-next-line no-param-reassign + dependency.attributes = [ + { + key: 'exemplar_trace_id', + value: `trace ${i} a`, + }, + { + key: 'exemplar_trace_id', + value: `trace ${i} b`, + }, + ]; + }); + const model = transformDdgData(payload, focalPayloadElem); + model.paths.forEach((path, i) => { + expect(path.traceIDs).toEqual([`trace ${i} a`, `trace ${i} b`]); + }); + }); }); diff --git a/packages/jaeger-ui/src/model/ddg/transformDdgData.tsx b/packages/jaeger-ui/src/model/ddg/transformDdgData.tsx index 801996f3a5..bd6bff9af3 100644 --- a/packages/jaeger-ui/src/model/ddg/transformDdgData.tsx +++ b/packages/jaeger-ui/src/model/ddg/transformDdgData.tsx @@ -26,8 +26,18 @@ import { const stringifyEntry = ({ service, operation }: TDdgPayloadEntry) => `${service}\v${operation}`; +// TODO: Everett Tech Debt: Fix KeyValuePair types +function group(arg: { key: string; value: any }[]): Record { + const result: Record = {}; + arg.forEach(({ key, value }) => { + if (!result[key]) result[key] = []; + result[key].push(value); + }); + return result; +} + export default function transformDdgData( - payload: TDdgPayload, + { dependencies }: TDdgPayload, { service: focalService, operation: focalOperation }: { service: string; operation?: string } ): TDdgModel { const serviceMap: TDdgServiceMap = new Map(); @@ -35,7 +45,7 @@ export default function transformDdgData( const pathCompareValues: Map = new Map(); const hashArg: string[] = []; - const paths = payload + const paths = dependencies .sort(({ path: a }, { path: b }) => { let aCompareValue = pathCompareValues.get(a); if (!aCompareValue) { @@ -51,13 +61,15 @@ export default function transformDdgData( if (aCompareValue < bCompareValue) return -1; return 0; }) - // eslint-disable-next-line camelcase - .map(({ path: payloadPath, trace_id }) => { + .map(({ path: payloadPath, attributes }) => { // Default value necessary as sort is not called if there is only one path hashArg.push(pathCompareValues.get(payloadPath) || payloadPath.map(stringifyEntry).join()); + // eslint-disable-next-line camelcase + const { exemplar_trace_id: traceIDs } = group(attributes); + // Path with stand-in values is necessary for assigning PathElem.memberOf - const path: TDdgPath = { focalIdx: -1, members: [], traceID: trace_id }; + const path: TDdgPath = { focalIdx: -1, members: [], traceIDs }; path.members = payloadPath.map(({ operation: operationName, service: serviceName }, i) => { // Ensure pathElem.service exists, else create it diff --git a/packages/jaeger-ui/src/model/ddg/types.tsx b/packages/jaeger-ui/src/model/ddg/types.tsx index 5ea5a4c7cb..0cb08932ff 100644 --- a/packages/jaeger-ui/src/model/ddg/types.tsx +++ b/packages/jaeger-ui/src/model/ddg/types.tsx @@ -51,9 +51,15 @@ export type TDdgPayloadEntry = { }; export type TDdgPayload = { - path: TDdgPayloadEntry[]; - trace_id: string; // eslint-disable-line camelcase -}[]; + dependencies: { + path: TDdgPayloadEntry[]; + // TODO: Everett Tech Debt: Fix KeyValuePair types + attributes: { + key: 'exemplar_trace_id'; // eslint-disable-line camelcase + value: string; + }[]; + }[]; +}; export type TDdgService = { name: string; @@ -71,7 +77,7 @@ export type TDdgServiceMap = Map; export type TDdgPath = { focalIdx: number; members: PathElem[]; - traceID: string; + traceIDs: string[]; }; export type TDdgDistanceToPathElems = Map; diff --git a/packages/jaeger-ui/src/model/ddg/visibility-codec.test.js b/packages/jaeger-ui/src/model/ddg/visibility-codec.test.js index a470eaddbc..814d03673e 100644 --- a/packages/jaeger-ui/src/model/ddg/visibility-codec.test.js +++ b/packages/jaeger-ui/src/model/ddg/visibility-codec.test.js @@ -67,8 +67,8 @@ describe('visibility-codec', () => { }); describe('encodeDistance', () => { - const ddgModel = transformDdgData([longSimplePath, simplePath].map(wrap), focalPayloadElem); - const shortModel = transformDdgData([shortPath].map(wrap), focalPayloadElem); + const ddgModel = transformDdgData(wrap([longSimplePath, simplePath]), focalPayloadElem); + const shortModel = transformDdgData(wrap([shortPath]), focalPayloadElem); /** * Creates a visibility encoding containing all indices between two specified hops, inclusive, except diff --git a/packages/jaeger-ui/src/types/trace.tsx b/packages/jaeger-ui/src/types/trace.tsx index dedb6a1b31..c06b1f4933 100644 --- a/packages/jaeger-ui/src/types/trace.tsx +++ b/packages/jaeger-ui/src/types/trace.tsx @@ -16,6 +16,7 @@ * All timestamps are in microseconds */ +// TODO: Everett Tech Debt: Fix KeyValuePair types export type KeyValuePair = { key: string; value: any;