Chore: Refactor span.js, trace.js & trace.fixture.js to TypeScript (#58006)

This commit is contained in:
Hamas Shafiq 2022-11-30 18:09:08 +00:00 committed by GitHub
parent ce630b2dc5
commit 5073839f8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 175 additions and 139 deletions

View File

@ -12,11 +12,14 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { TraceResponse } from 'src/types';
import { TraceSpan, TraceSpanData } from 'src/types/trace';
import traceGenerator from '../demo/trace-generators'; import traceGenerator from '../demo/trace-generators';
import * as spanSelectors from './span'; import * as spanSelectors from './span';
const generatedTrace = traceGenerator.trace({ numberOfSpans: 45 }); const generatedTrace: TraceResponse = traceGenerator.trace({ numberOfSpans: 45 });
it('getSpanId() should return the name of the span', () => { it('getSpanId() should return the name of the span', () => {
const span = generatedTrace.spans[0]; const span = generatedTrace.spans[0];
@ -46,8 +49,10 @@ it('getSpanReferences() should return the span reference array', () => {
expect(spanSelectors.getSpanReferences(generatedTrace.spans[0])).toEqual(generatedTrace.spans[0].references); expect(spanSelectors.getSpanReferences(generatedTrace.spans[0])).toEqual(generatedTrace.spans[0].references);
}); });
it('getSpanReferences() should return empty array for null references', () => { it('getSpanReferences() should return an empty array when references is undefined', () => {
expect(spanSelectors.getSpanReferences({ references: null })).toEqual([]); const span = generatedTrace.spans[0];
span.references = undefined;
expect(spanSelectors.getSpanReferences(span)).toEqual([]);
}); });
it('getSpanReferenceByType() should return the span reference requested', () => { it('getSpanReferenceByType() should return the span reference requested', () => {
@ -55,7 +60,7 @@ it('getSpanReferenceByType() should return the span reference requested', () =>
spanSelectors.getSpanReferenceByType({ spanSelectors.getSpanReferenceByType({
span: generatedTrace.spans[1], span: generatedTrace.spans[1],
type: 'CHILD_OF', type: 'CHILD_OF',
}).refType })?.refType
).toBe('CHILD_OF'); ).toBe('CHILD_OF');
}); });
@ -70,7 +75,7 @@ it('getSpanReferenceByType() should return undefined if one does not exist', ()
it('getSpanParentId() should return the spanID of the parent span', () => { it('getSpanParentId() should return the spanID of the parent span', () => {
expect(spanSelectors.getSpanParentId(generatedTrace.spans[1])).toBe( expect(spanSelectors.getSpanParentId(generatedTrace.spans[1])).toBe(
generatedTrace.spans[1].references.find(({ refType }) => refType === 'CHILD_OF').spanID generatedTrace.spans[1].references!.find(({ refType }: { refType: string }) => refType === 'CHILD_OF')!.spanID
); );
}); });
@ -85,16 +90,17 @@ it('getSpanProcessId() should return the processID of the span', () => {
}); });
it('getSpanProcess() should return the process of the span', () => { it('getSpanProcess() should return the process of the span', () => {
const serviceName = 'bagel';
const span = { const span = {
...generatedTrace.spans[0], ...generatedTrace.spans[0],
process: {}, process: { serviceName },
}; } as TraceSpan;
expect(spanSelectors.getSpanProcess(span)).toBe(span.process); expect(spanSelectors.getSpanProcess(span)).toBe(span.process);
}); });
it('getSpanProcess() should throw if no process exists', () => { it('getSpanProcess() should throw if no process exists', () => {
expect(() => spanSelectors.getSpanProcess(generatedTrace.spans[0])).toThrow(); expect(() => spanSelectors.getSpanProcess(generatedTrace.spans[0] as TraceSpan)).toThrow();
}); });
it('getSpanServiceName() should return the service name of the span', () => { it('getSpanServiceName() should return the service name of the span', () => {
@ -102,7 +108,7 @@ it('getSpanServiceName() should return the service name of the span', () => {
const span = { const span = {
...generatedTrace.spans[0], ...generatedTrace.spans[0],
process: { serviceName }, process: { serviceName },
}; } as TraceSpan;
expect(spanSelectors.getSpanServiceName(span)).toBe(serviceName); expect(spanSelectors.getSpanServiceName(span)).toBe(serviceName);
}); });
@ -112,17 +118,17 @@ it('filterSpansForTimestamps() should return a filtered list of spans between th
const spans = [ const spans = [
{ {
startTime: now - 1000, startTime: now - 1000,
id: 'start-time-1', spanID: 'start-time-1',
}, },
{ {
startTime: now, startTime: now,
id: 'start-time-2', spanID: 'start-time-2',
}, },
{ {
startTime: now + 1000, startTime: now + 1000,
id: 'start-time-3', spanID: 'start-time-3',
}, },
]; ] as TraceSpanData[];
expect( expect(
spanSelectors.filterSpansForTimestamps({ spanSelectors.filterSpansForTimestamps({
@ -164,23 +170,23 @@ it('filterSpansForText() should return a filtered list of spans between the time
process: { process: {
serviceName: 'alpha', serviceName: 'alpha',
}, },
id: 'start-time-1', spanID: 'start-time-1',
}, },
{ {
operationName: 'GET /another', operationName: 'GET /another',
process: { process: {
serviceName: 'beta', serviceName: 'beta',
}, },
id: 'start-time-1', spanID: 'start-time-1',
}, },
{ {
operationName: 'POST /mything', operationName: 'POST /mything',
process: { process: {
serviceName: 'alpha', serviceName: 'alpha',
}, },
id: 'start-time-1', spanID: 'start-time-1',
}, },
]; ] as TraceSpan[];
expect( expect(
spanSelectors.filterSpansForText({ spanSelectors.filterSpansForText({

View File

@ -15,25 +15,27 @@
import fuzzy from 'fuzzy'; import fuzzy from 'fuzzy';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { TraceSpan, TraceSpanData, TraceSpanReference } from '../types/trace';
import { getProcessServiceName } from './process'; import { getProcessServiceName } from './process';
export const getSpanId = (span) => span.spanID; export const getSpanId = (span: TraceSpanData) => span.spanID;
export const getSpanName = (span) => span.operationName; export const getSpanName = (span: TraceSpanData) => span.operationName;
export const getSpanDuration = (span) => span.duration; export const getSpanDuration = (span: TraceSpanData) => span.duration;
export const getSpanTimestamp = (span) => span.startTime; export const getSpanTimestamp = (span: TraceSpanData) => span.startTime;
export const getSpanProcessId = (span) => span.processID; export const getSpanProcessId = (span: TraceSpanData) => span.processID;
export const getSpanReferences = (span) => span.references || []; export const getSpanReferences = (span: TraceSpanData) => span.references || [];
export const getSpanReferenceByType = createSelector( export const getSpanReferenceByType = createSelector(
createSelector(({ span }) => span, getSpanReferences), createSelector(({ span }: { span: TraceSpanData }) => span, getSpanReferences),
({ type }) => type, ({ type }: { type: string }) => type,
(references, type) => references.find((ref) => ref.refType === type) (references, type) => references.find((ref: TraceSpanReference) => ref.refType === type)
); );
export const getSpanParentId = createSelector( export const getSpanParentId = createSelector(
(span) => getSpanReferenceByType({ span, type: 'CHILD_OF' }), (span: TraceSpanData) => getSpanReferenceByType({ span, type: 'CHILD_OF' }),
(childOfRef) => (childOfRef ? childOfRef.spanID : null) (childOfRef) => (childOfRef ? childOfRef.spanID : null)
); );
export const getSpanProcess = (span) => { export const getSpanProcess = (span: TraceSpan) => {
if (!span.process) { if (!span.process) {
throw new Error( throw new Error(
` `
@ -42,23 +44,22 @@ export const getSpanProcess = (span) => {
` `
); );
} }
return span.process; return span.process;
}; };
export const getSpanServiceName = createSelector(getSpanProcess, getProcessServiceName); export const getSpanServiceName = createSelector(getSpanProcess, getProcessServiceName);
export const filterSpansForTimestamps = createSelector( export const filterSpansForTimestamps = createSelector(
({ spans }) => spans, ({ spans }: { spans: TraceSpanData[] }) => spans,
({ leftBound }) => leftBound, ({ leftBound }: { leftBound: number }) => leftBound,
({ rightBound }) => rightBound, ({ rightBound }: { rightBound: number }) => rightBound,
(spans, leftBound, rightBound) => (spans, leftBound, rightBound) =>
spans.filter((span) => getSpanTimestamp(span) >= leftBound && getSpanTimestamp(span) <= rightBound) spans.filter((span) => getSpanTimestamp(span) >= leftBound && getSpanTimestamp(span) <= rightBound)
); );
export const filterSpansForText = createSelector( export const filterSpansForText = createSelector(
({ spans }) => spans, ({ spans }: { spans: TraceSpan[] }) => spans,
({ text }) => text, ({ text }: { text: string }) => text,
(spans, text) => (spans, text) =>
fuzzy fuzzy
.filter(text, spans, { .filter(text, spans, {
@ -67,7 +68,7 @@ export const filterSpansForText = createSelector(
.map(({ original }) => original) .map(({ original }) => original)
); );
const getTextFilterdSpansAsMap = createSelector(filterSpansForText, (matchingSpans) => const getTextFilteredSpansAsMap = createSelector(filterSpansForText, (matchingSpans) =>
matchingSpans.reduce( matchingSpans.reduce(
(obj, span) => ({ (obj, span) => ({
...obj, ...obj,
@ -77,11 +78,12 @@ const getTextFilterdSpansAsMap = createSelector(filterSpansForText, (matchingSpa
) )
); );
// TODO: delete this function as it is not used?
export const highlightSpansForTextFilter = createSelector( export const highlightSpansForTextFilter = createSelector(
({ spans }) => spans, ({ spans }: { spans: TraceSpanData[] }) => spans,
getTextFilterdSpansAsMap, getTextFilteredSpansAsMap,
(spans, textFilteredSpansMap) => (spans, textFilteredSpansMap: { [key: string]: TraceSpanData }) =>
spans.map((span) => ({ spans.map((span: TraceSpanData) => ({
...span, ...span,
muted: !textFilteredSpansMap[getSpanId(span)], muted: !textFilteredSpansMap[getSpanId(span)],
})) }))

View File

@ -14,6 +14,16 @@
// See https://github.com/jaegertracing/jaeger-ui/issues/115 for details. // See https://github.com/jaegertracing/jaeger-ui/issues/115 for details.
import { TraceSpanReference } from '../types/trace';
const references: TraceSpanReference[] = [
{
refType: 'FOLLOWS_FROM',
spanID: 'ea7cfaca83f0724b',
traceID: '2992f2a5b5d037a8aabffd08ef384237',
},
];
export const followsFromRef = { export const followsFromRef = {
processes: { processes: {
p1: { p1: {
@ -28,13 +38,7 @@ export const followsFromRef = {
logs: [], logs: [],
operationName: 'thread', operationName: 'thread',
processID: 'p1', processID: 'p1',
references: [ references: references,
{
refType: 'FOLLOWS_FROM',
spanID: 'ea7cfaca83f0724b',
traceID: '2992f2a5b5d037a8aabffd08ef384237',
},
],
spanID: '1bdf4201221bb2ac', spanID: '1bdf4201221bb2ac',
startTime: 1509533706521220, startTime: 1509533706521220,
tags: [], tags: [],

View File

@ -13,8 +13,10 @@
// limitations under the License. // limitations under the License.
import { values as _values } from 'lodash'; import { values as _values } from 'lodash';
import TreeNode from 'src/utils/TreeNode';
import traceGenerator from '../demo/trace-generators'; import traceGenerator from '../demo/trace-generators';
import { TraceResponse, TraceSpan, TraceSpanData } from '../types/trace';
import { numberSortComparator } from '../utils/sort'; import { numberSortComparator } from '../utils/sort';
import { import {
@ -29,7 +31,7 @@ import {
import * as traceSelectors from './trace'; import * as traceSelectors from './trace';
import { followsFromRef } from './trace.fixture'; import { followsFromRef } from './trace.fixture';
const generatedTrace = traceGenerator.trace({ numberOfSpans: 45 }); const generatedTrace: TraceResponse = traceGenerator.trace({ numberOfSpans: 45 });
it('getTraceId() should return the traceID', () => { it('getTraceId() should return the traceID', () => {
expect(traceSelectors.getTraceId(generatedTrace)).toBe(generatedTrace.traceID); expect(traceSelectors.getTraceId(generatedTrace)).toBe(generatedTrace.traceID);
@ -39,7 +41,7 @@ it('hydrateSpansWithProcesses() should return the trace with processes on each s
const hydratedTrace = traceSelectors.hydrateSpansWithProcesses(generatedTrace); const hydratedTrace = traceSelectors.hydrateSpansWithProcesses(generatedTrace);
hydratedTrace.spans.forEach((span) => hydratedTrace.spans.forEach((span) =>
expect(getSpanProcess(span)).toBe(generatedTrace.processes[getSpanProcessId(span)]) expect(getSpanProcess(span as TraceSpan)).toBe(generatedTrace.processes[getSpanProcessId(span)])
); );
}); });
@ -55,7 +57,7 @@ describe('getTraceSpanIdsAsTree()', () => {
const tree = traceSelectors.getTraceSpanIdsAsTree(generatedTrace); const tree = traceSelectors.getTraceSpanIdsAsTree(generatedTrace);
const spanMap = traceSelectors.getTraceSpansAsMap(generatedTrace); const spanMap = traceSelectors.getTraceSpansAsMap(generatedTrace);
tree.walk((value, node) => { tree.walk((value: string | number | undefined, node: TreeNode) => {
const expectedParentValue = value === traceSelectors.TREE_ROOT_ID ? null : value; const expectedParentValue = value === traceSelectors.TREE_ROOT_ID ? null : value;
node.children.forEach((childNode) => { node.children.forEach((childNode) => {
expect(getSpanParentId(spanMap.get(childNode.value))).toBe(expectedParentValue); expect(getSpanParentId(spanMap.get(childNode.value))).toBe(expectedParentValue);
@ -98,7 +100,7 @@ it('getParentSpan() should return the first span if there are multiple parents',
references: [], references: [],
}, },
], ],
}; } as unknown as TraceResponse;
expect(traceSelectors.getParentSpan(trace)).toBe(firstSpan); expect(traceSelectors.getParentSpan(trace)).toBe(firstSpan);
}); });
@ -131,14 +133,14 @@ it('getTraceDepth() should determine the total depth of the trace tree', () => {
}); });
it('getSpanDepthForTrace() should determine the depth of a given span in the parent', () => { it('getSpanDepthForTrace() should determine the depth of a given span in the parent', () => {
function testDepthCalc(span) { function testDepthCalc(span: TraceSpanData) {
let depth = 2; let depth = 2;
let currentId = getSpanParentId(span); let currentId = getSpanParentId(span);
const findCurrentSpanById = (item) => getSpanId(item) === currentId; const findCurrentSpanById = (item: TraceSpanData) => getSpanId(item) === currentId;
while (currentId !== getSpanId(generatedTrace.spans[0])) { while (currentId !== getSpanId(generatedTrace.spans[0])) {
depth++; depth++;
currentId = getSpanParentId(generatedTrace.spans.find(findCurrentSpanById)); currentId = getSpanParentId(generatedTrace.spans.find(findCurrentSpanById)!);
} }
// console.log('hypothetical depth', depth); // console.log('hypothetical depth', depth);
@ -183,10 +185,13 @@ it('formatDurationForUnit() should use the formatters to return the proper value
}); });
it('formatDurationForTrace() should return a ms value for traces shorter than a second', () => { it('formatDurationForTrace() should return a ms value for traces shorter than a second', () => {
const firstSpan = generatedTrace.spans[0];
firstSpan.duration = 600000;
expect( expect(
traceSelectors.formatDurationForTrace({ traceSelectors.formatDurationForTrace({
trace: { trace: {
spans: [{ duration: 600000 }], ...generatedTrace,
spans: [firstSpan],
}, },
duration: 302000, duration: 302000,
}) })
@ -266,14 +271,17 @@ it('getTreeSizeForTraceSpan() should return the size for a child span', () => {
trace: generatedTrace, trace: generatedTrace,
span: generatedTrace.spans[1], span: generatedTrace.spans[1],
}) })
).toBe(traceSelectors.getTraceSpanIdsAsTree(generatedTrace).find(generatedTrace.spans[1].spanID).size - 1); ).toBe(traceSelectors.getTraceSpanIdsAsTree(generatedTrace).find(generatedTrace.spans[1].spanID)!.size - 1);
}); });
it('getTreeSizeForTraceSpan() should return -1 for an absent span', () => { it('getTreeSizeForTraceSpan() should return -1 for an absent span', () => {
const absentSpan = generatedTrace.spans[0];
absentSpan.spanID = 'whatever';
expect( expect(
traceSelectors.getTreeSizeForTraceSpan({ traceSelectors.getTreeSizeForTraceSpan({
trace: generatedTrace, trace: generatedTrace,
span: { spanID: 'whatever' }, span: absentSpan,
}) })
).toBe(-1); ).toBe(-1);
}); });
@ -287,7 +295,7 @@ it('getTraceName() should return the trace name based on the parentSpan', () =>
it('omitCollapsedSpans() should filter out collapsed spans', () => { it('omitCollapsedSpans() should filter out collapsed spans', () => {
const span = generatedTrace.spans[1]; const span = generatedTrace.spans[1];
const size = traceSelectors.getTraceSpanIdsAsTree(generatedTrace).find(span.spanID).size - 1; const size = traceSelectors.getTraceSpanIdsAsTree(generatedTrace).find(span.spanID)!.size - 1;
expect( expect(
traceSelectors.omitCollapsedSpans({ traceSelectors.omitCollapsedSpans({
@ -299,15 +307,13 @@ it('omitCollapsedSpans() should filter out collapsed spans', () => {
}); });
it('getTicksForTrace() should return a list of ticks given interval parameters', () => { it('getTicksForTrace() should return a list of ticks given interval parameters', () => {
const trace = generatedTrace;
const timestamp = new Date().getTime() * 1000; const timestamp = new Date().getTime() * 1000;
const trace = {
spans: [ trace.spans.forEach((span) => {
{ span.duration = 3000000;
startTime: timestamp, span.startTime = timestamp;
duration: 3000000, });
},
],
};
expect( expect(
traceSelectors.getTicksForTrace({ traceSelectors.getTicksForTrace({
@ -325,14 +331,20 @@ it('getTicksForTrace() should return a list of ticks given interval parameters',
it('getTicksForTrace() should use defaults', () => { it('getTicksForTrace() should use defaults', () => {
const timestamp = new Date().getTime() * 1000; const timestamp = new Date().getTime() * 1000;
const trace = { const trace = traceGenerator.trace({ numberOfSpans: 1 });
spans: [
{ trace.spans = [
startTime: timestamp, {
duration: 4000000, traceID: '5031233a-d0b5-5d41-9b4b-4c072bcf5020',
}, processID: 'b5f4e0ff-7318-5017-a3f3-9c7b423a82aa',
], spanID: 'e871771f-f1b4-54af-9e9d-826259c2915e',
}; flags: 0,
logs: [],
operationName: 'POST',
startTime: timestamp,
duration: 4000000,
},
];
expect(traceSelectors.getTicksForTrace({ trace })).toEqual([ expect(traceSelectors.getTicksForTrace({ trace })).toEqual([
{ timestamp, width: traceSelectors.DEFAULT_TICK_WIDTH }, { timestamp, width: traceSelectors.DEFAULT_TICK_WIDTH },

View File

@ -14,6 +14,7 @@
import { createSelector, createStructuredSelector } from 'reselect'; import { createSelector, createStructuredSelector } from 'reselect';
import { Trace, TraceData, TraceProcess, TraceResponse, TraceSpan, TraceSpanData } from '../types/trace';
import TreeNode from '../utils/TreeNode'; import TreeNode from '../utils/TreeNode';
import { formatMillisecondTime, formatSecondTime, ONE_SECOND } from '../utils/date'; import { formatMillisecondTime, formatSecondTime, ONE_SECOND } from '../utils/date';
import { numberSortComparator } from '../utils/sort'; import { numberSortComparator } from '../utils/sort';
@ -28,15 +29,13 @@ import {
getSpanProcessId, getSpanProcessId,
} from './span'; } from './span';
export const getTraceId = (trace) => trace.traceID; export const getTraceId = (trace: TraceData) => trace.traceID;
export const getTraceSpans = (trace: TraceResponse) => trace.spans;
export const getTraceSpans = (trace) => trace.spans; const getTraceProcesses = (trace: TraceData | Trace) => trace.processes;
const getTraceProcesses = (trace) => trace.processes;
const getSpanWithProcess = createSelector( const getSpanWithProcess = createSelector(
(state) => state.span, (state: { span: TraceSpanData; processes: Record<string, TraceProcess> }) => state.span,
(state) => state.processes, (state: { span: TraceSpanData; processes: Record<string, TraceProcess> }) => state.processes,
(span, processes) => ({ (span, processes) => ({
...span, ...span,
process: processes[getSpanProcessId(span)], process: processes[getSpanProcessId(span)],
@ -44,7 +43,7 @@ const getSpanWithProcess = createSelector(
); );
export const getTraceSpansAsMap = createSelector(getTraceSpans, (spans) => export const getTraceSpansAsMap = createSelector(getTraceSpans, (spans) =>
spans.reduce((map, span) => map.set(getSpanId(span), span), new Map()) spans.reduce((map, span: TraceSpanData) => map.set(getSpanId(span), span), new Map())
); );
export const TREE_ROOT_ID = '__root__'; export const TREE_ROOT_ID = '__root__';
@ -63,17 +62,17 @@ export const TREE_ROOT_ID = '__root__';
* @return {TreeNode} A tree of spanIDs derived from the relationships * @return {TreeNode} A tree of spanIDs derived from the relationships
* between spans in the trace. * between spans in the trace.
*/ */
export function getTraceSpanIdsAsTree(trace) { export function getTraceSpanIdsAsTree(trace: TraceResponse) {
const nodesById = new Map(trace.spans.map((span) => [span.spanID, new TreeNode(span.spanID)])); const nodesById = new Map(trace.spans.map((span: TraceSpanData) => [span.spanID, new TreeNode(span.spanID)]));
const spansById = new Map(trace.spans.map((span) => [span.spanID, span])); const spansById = new Map(trace.spans.map((span: TraceSpanData) => [span.spanID, span]));
const root = new TreeNode(TREE_ROOT_ID); const root = new TreeNode(TREE_ROOT_ID);
trace.spans.forEach((span) => { trace.spans.forEach((span: TraceSpanData) => {
const node = nodesById.get(span.spanID); const node = nodesById.get(span.spanID)!;
if (Array.isArray(span.references) && span.references.length) { if (Array.isArray(span.references) && span.references.length) {
const { refType, spanID: parentID } = span.references[0]; const { refType, spanID: parentID } = span.references[0];
if (refType === 'CHILD_OF' || refType === 'FOLLOWS_FROM') { if (refType === 'CHILD_OF' || refType === 'FOLLOWS_FROM') {
const parent = nodesById.get(parentID) || root; const parent = nodesById.get(parentID) || root;
parent.children.push(node); parent.children?.push(node);
} else { } else {
throw new Error(`Unrecognized ref type: ${refType}`); throw new Error(`Unrecognized ref type: ${refType}`);
} }
@ -81,15 +80,15 @@ export function getTraceSpanIdsAsTree(trace) {
root.children.push(node); root.children.push(node);
} }
}); });
const comparator = (nodeA, nodeB) => { const comparator = (nodeA: TreeNode | undefined, nodeB: TreeNode | undefined) => {
const a = spansById.get(nodeA.value); const a: TraceSpanData | undefined = nodeA?.value ? spansById.get(nodeA.value.toString()) : undefined;
const b = spansById.get(nodeB.value); const b: TraceSpanData | undefined = nodeB?.value ? spansById.get(nodeB.value.toString()) : undefined;
return +(a.startTime > b.startTime) || +(a.startTime === b.startTime) - 1; return +(a?.startTime! > b?.startTime!) || +(a?.startTime === b?.startTime) - 1;
}; };
trace.spans.forEach((span) => { trace.spans.forEach((span: TraceSpanData) => {
const node = nodesById.get(span.spanID); const node: TreeNode | undefined = nodesById.get(span.spanID);
if (node.children.length > 1) { if (node!.children.length > 1) {
node.children.sort(comparator); node?.children.sort(comparator);
} }
}); });
root.children.sort(comparator); root.children.sort(comparator);
@ -97,13 +96,13 @@ export function getTraceSpanIdsAsTree(trace) {
} }
// attach "process" as an object to each span. // attach "process" as an object to each span.
export const hydrateSpansWithProcesses = (trace) => { export const hydrateSpansWithProcesses = (trace: TraceResponse) => {
const spans = getTraceSpans(trace); const spans = getTraceSpans(trace);
const processes = getTraceProcesses(trace); const processes = getTraceProcesses(trace);
return { return {
...trace, ...trace,
spans: spans.map((span) => getSpanWithProcess({ span, processes })), spans: spans.map((span: TraceSpanData) => getSpanWithProcess({ span, processes })),
}; };
}; };
@ -111,25 +110,26 @@ export const getTraceSpanCount = createSelector(getTraceSpans, (spans) => spans.
export const getTraceTimestamp = createSelector(getTraceSpans, (spans) => export const getTraceTimestamp = createSelector(getTraceSpans, (spans) =>
spans.reduce( spans.reduce(
(prevTimestamp, span) => (prevTimestamp ? Math.min(prevTimestamp, getSpanTimestamp(span)) : getSpanTimestamp(span)), (prevTimestamp: number, span: TraceSpanData) =>
null prevTimestamp ? Math.min(prevTimestamp, getSpanTimestamp(span)) : getSpanTimestamp(span),
0
) )
); );
export const getTraceDuration = createSelector(getTraceSpans, getTraceTimestamp, (spans, timestamp) => export const getTraceDuration = createSelector(getTraceSpans, getTraceTimestamp, (spans, timestamp) =>
spans.reduce( spans.reduce(
(prevDuration, span) => (prevDuration: number, span: TraceSpanData) =>
prevDuration prevDuration
? Math.max(getSpanTimestamp(span) - timestamp + getSpanDuration(span), prevDuration) ? Math.max(getSpanTimestamp(span) - timestamp! + getSpanDuration(span), prevDuration)
: getSpanDuration(span), : getSpanDuration(span),
null 0
) )
); );
export const getTraceEndTimestamp = createSelector( export const getTraceEndTimestamp = createSelector(
getTraceTimestamp, getTraceTimestamp,
getTraceDuration, getTraceDuration,
(timestamp, duration) => timestamp + duration (timestamp: number, duration: number) => timestamp! + duration
); );
export const getParentSpan = createSelector( export const getParentSpan = createSelector(
@ -137,16 +137,18 @@ export const getParentSpan = createSelector(
getTraceSpansAsMap, getTraceSpansAsMap,
(tree, spanMap) => (tree, spanMap) =>
tree.children tree.children
.map((node) => spanMap.get(node.value)) .map((node: TreeNode) => spanMap.get(node.value))
.sort((spanA, spanB) => numberSortComparator(getSpanTimestamp(spanA), getSpanTimestamp(spanB)))[0] .sort((spanA: TraceSpanData, spanB: TraceSpanData) =>
numberSortComparator(getSpanTimestamp(spanA), getSpanTimestamp(spanB))
)[0]
); );
export const getTraceDepth = createSelector(getTraceSpanIdsAsTree, (spanTree) => spanTree.depth - 1); export const getTraceDepth = createSelector(getTraceSpanIdsAsTree, (spanTree) => spanTree.depth - 1);
export const getSpanDepthForTrace = createSelector( export const getSpanDepthForTrace = createSelector(
createSelector((state) => state.trace, getTraceSpanIdsAsTree), createSelector((state: { trace: TraceResponse }) => state.trace, getTraceSpanIdsAsTree),
createSelector((state) => state.span, getSpanId), createSelector((state: { span: TraceSpanData }) => state.span, getSpanId),
(node, spanID) => node.getPath(spanID).length - 1 (node, spanID) => node.getPath(spanID)!.length - 1
); );
export const getTraceServices = createSelector(getTraceProcesses, (processes) => export const getTraceServices = createSelector(getTraceProcesses, (processes) =>
@ -165,26 +167,34 @@ export const DURATION_FORMATTERS = {
s: formatSecondTime, s: formatSecondTime,
}; };
const getDurationFormatterForTrace = createSelector(getTraceDuration, (totalDuration) => const getDurationFormatterForTrace = createSelector(getTraceDuration, (totalDuration: number) =>
totalDuration >= ONE_SECOND ? DURATION_FORMATTERS.s : DURATION_FORMATTERS.ms totalDuration >= ONE_SECOND ? DURATION_FORMATTERS.s : DURATION_FORMATTERS.ms
); );
export const formatDurationForUnit = createSelector( export const formatDurationForUnit = createSelector(
({ duration }) => duration, ({ duration }: { duration: number }) => duration,
({ unit }) => DURATION_FORMATTERS[unit], ({ unit }: { unit: 'ms' | 's' }) => DURATION_FORMATTERS[unit],
(duration, formatter) => formatter(duration) (duration, formatter) => formatter(duration)
); );
export const formatDurationForTrace = createSelector( export const formatDurationForTrace = createSelector(
({ duration }) => duration, ({ duration }: { duration: number }) => duration,
createSelector(({ trace }) => trace, getDurationFormatterForTrace), createSelector(({ trace }: { trace: TraceResponse }) => trace, getDurationFormatterForTrace),
(duration, formatter) => formatter(duration) (duration, formatter) => formatter(duration)
); );
export const getSortedSpans = createSelector( export const getSortedSpans = createSelector(
({ trace }) => trace, ({ trace }: { trace: TraceResponse }) => trace,
({ spans }) => spans, ({ spans }: { spans: TraceSpanData[] }) => spans,
({ sort }) => sort, ({
sort,
}: {
sort: {
dir: number;
comparator: (itemA: number, itemB: number) => number;
selector: (itemA: TraceSpanData, itemB: TraceResponse) => number;
};
}) => sort,
(trace, spans, { dir, comparator, selector }) => (trace, spans, { dir, comparator, selector }) =>
[...spans].sort((spanA, spanB) => dir * comparator(selector(spanA, trace), selector(spanB, trace))) [...spans].sort((spanA, spanB) => dir * comparator(selector(spanA, trace), selector(spanB, trace)))
); );
@ -192,13 +202,13 @@ export const getSortedSpans = createSelector(
const getTraceSpansByHierarchyPosition = createSelector(getTraceSpanIdsAsTree, (tree) => { const getTraceSpansByHierarchyPosition = createSelector(getTraceSpanIdsAsTree, (tree) => {
const hierarchyPositionMap = new Map(); const hierarchyPositionMap = new Map();
let i = 0; let i = 0;
tree.walk((spanID) => hierarchyPositionMap.set(spanID, i++)); tree.walk((spanID: string | number | undefined) => hierarchyPositionMap.set(spanID, i++));
return hierarchyPositionMap; return hierarchyPositionMap;
}); });
export const getTreeSizeForTraceSpan = createSelector( export const getTreeSizeForTraceSpan = createSelector(
createSelector((state) => state.trace, getTraceSpanIdsAsTree), createSelector((state: { trace: TraceResponse }) => state.trace, getTraceSpanIdsAsTree),
createSelector((state) => state.span, getSpanId), createSelector((state: { span: TraceSpanData }) => state.span, getSpanId),
(tree, spanID) => { (tree, spanID) => {
const node = tree.find(spanID); const node = tree.find(spanID);
if (!node) { if (!node) {
@ -209,8 +219,8 @@ export const getTreeSizeForTraceSpan = createSelector(
); );
export const getSpanHierarchySortPositionForTrace = createSelector( export const getSpanHierarchySortPositionForTrace = createSelector(
createSelector(({ trace }) => trace, getTraceSpansByHierarchyPosition), createSelector(({ trace }: { trace: Trace }) => trace, getTraceSpansByHierarchyPosition),
({ span }) => span, ({ span }: { span: TraceSpan }) => span,
(hierarchyPositionMap, span) => hierarchyPositionMap.get(getSpanId(span)) (hierarchyPositionMap, span) => hierarchyPositionMap.get(getSpanId(span))
); );
@ -222,16 +232,16 @@ export const getTraceName = createSelector(
serviceName: getSpanServiceName, serviceName: getSpanServiceName,
}) })
), ),
({ name, serviceName }) => `${serviceName}: ${name}` ({ name, serviceName }: { name: string; serviceName: string }) => `${serviceName}: ${name}`
); );
export const omitCollapsedSpans = createSelector( export const omitCollapsedSpans = createSelector(
({ spans }) => spans, ({ spans }: { spans: TraceSpanData[] }) => spans,
createSelector(({ trace }) => trace, getTraceSpanIdsAsTree), createSelector(({ trace }: { trace: TraceResponse }) => trace, getTraceSpanIdsAsTree),
({ collapsed }) => collapsed, ({ collapsed }: { collapsed: string[] }) => collapsed,
(spans, tree, collapse) => { (spans, tree, collapse) => {
const hiddenSpanIds = collapse.reduce((result, collapsedSpanId) => { const hiddenSpanIds = collapse.reduce((result, collapsedSpanId) => {
tree.find(collapsedSpanId).walk((id) => id !== collapsedSpanId && result.add(id)); tree.find(collapsedSpanId)!.walk((id: string | number | undefined) => id !== collapsedSpanId && result.add(id));
return result; return result;
}, new Set()); }, new Set());
@ -242,13 +252,13 @@ export const omitCollapsedSpans = createSelector(
export const DEFAULT_TICK_INTERVAL = 4; export const DEFAULT_TICK_INTERVAL = 4;
export const DEFAULT_TICK_WIDTH = 3; export const DEFAULT_TICK_WIDTH = 3;
export const getTicksForTrace = createSelector( export const getTicksForTrace = createSelector(
({ trace }) => trace, ({ trace }: { trace: TraceResponse }) => trace,
({ interval = DEFAULT_TICK_INTERVAL }) => interval, ({ interval = DEFAULT_TICK_INTERVAL }: { interval?: number }) => interval,
({ width = DEFAULT_TICK_WIDTH }) => width, ({ width = DEFAULT_TICK_WIDTH }: { width?: number }) => width,
( (
trace, trace,
interval, interval: number,
width width: number
// timestamps will be spaced over the interval, starting from the initial timestamp // timestamps will be spaced over the interval, starting from the initial timestamp
) => ) =>
[...Array(interval + 1).keys()].map((num) => ({ [...Array(interval + 1).keys()].map((num) => ({
@ -260,14 +270,16 @@ export const getTicksForTrace = createSelector(
// TODO: delete this when the backend can ensure uniqueness // TODO: delete this when the backend can ensure uniqueness
/* istanbul ignore next */ /* istanbul ignore next */
export const enforceUniqueSpanIds = createSelector( export const enforceUniqueSpanIds = createSelector(
/* istanbul ignore next */ (trace) => trace, /* istanbul ignore next */ (trace: Trace) => trace,
getTraceSpans, getTraceSpans,
/* istanbul ignore next */ (trace, spans) => { /* istanbul ignore next */ (trace, spans) => {
const map = new Map(); const map = new Map();
const spanArray: TraceSpanData[] = [];
return { return {
...trace, ...trace,
spans: spans.reduce((result, span) => { spans: spans.reduce((result: TraceSpanData[], span: TraceSpanData) => {
const spanID = map.has(getSpanId(span)) ? `${getSpanId(span)}_${map.get(getSpanId(span))}` : getSpanId(span); const spanID = map.has(getSpanId(span)) ? `${getSpanId(span)}_${map.get(getSpanId(span))}` : getSpanId(span);
const updatedSpan = { ...span, spanID }; const updatedSpan = { ...span, spanID };
@ -280,17 +292,17 @@ export const enforceUniqueSpanIds = createSelector(
map.set(getSpanId(span), (map.get(getSpanId(span)) || 0) + 1); map.set(getSpanId(span), (map.get(getSpanId(span)) || 0) + 1);
return result.concat([updatedSpan]); return result.concat([updatedSpan]);
}, []), }, spanArray),
}; };
} }
); );
// TODO: delete this when the backend can ensure uniqueness // TODO: delete this when the backend can ensure uniqueness
export const dropEmptyStartTimeSpans = createSelector( export const dropEmptyStartTimeSpans = createSelector(
/* istanbul ignore next */ (trace) => trace, /* istanbul ignore next */ (trace: Trace) => trace,
getTraceSpans, getTraceSpans,
/* istanbul ignore next */ (trace, spans) => ({ /* istanbul ignore next */ (trace, spans) => ({
...trace, ...trace,
spans: spans.filter((span) => !!getSpanTimestamp(span)), spans: spans.filter((span: TraceSpanData) => !!getSpanTimestamp(span)),
}) })
); );

View File

@ -22,7 +22,7 @@ export default class TreeNode {
return (node: TreeNode) => fn(node.value, node, depth); return (node: TreeNode) => fn(node.value, node, depth);
} }
static searchFunction(search: TreeNode | number | SearchFn) { static searchFunction(search: TreeNode | number | SearchFn | string) {
if (typeof search === 'function') { if (typeof search === 'function') {
return search; return search;
} }
@ -51,7 +51,7 @@ export default class TreeNode {
return this; return this;
} }
find(search: TreeNode | number | SearchFn): TreeNode | null { find(search: TreeNode | number | SearchFn | string): TreeNode | null {
const searchFn = TreeNode.iterFunction(TreeNode.searchFunction(search)); const searchFn = TreeNode.iterFunction(TreeNode.searchFunction(search));
if (searchFn(this)) { if (searchFn(this)) {
return this; return this;
@ -65,7 +65,7 @@ export default class TreeNode {
return null; return null;
} }
getPath(search: TreeNode) { getPath(search: TreeNode | string) {
const searchFn = TreeNode.iterFunction(TreeNode.searchFunction(search)); const searchFn = TreeNode.iterFunction(TreeNode.searchFunction(search));
const findPath = (currentNode: TreeNode, currentPath: TreeNode[]): TreeNode[] | null => { const findPath = (currentNode: TreeNode, currentPath: TreeNode[]): TreeNode[] | null => {