feat: missing spans handling by returning a forest of trees (#1365)

* feat: spanToTree 2.0

* feat: spanToTree EPIFI data

* feat: missing spans multiple trees

* chore: migrated to popoverss

Co-authored-by: Palash <palashgdev@gmail.com>
This commit is contained in:
Pranshu Chittora 2022-07-08 16:18:08 +05:30 committed by GitHub
parent da368ab5e8
commit 80c80b2180
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 441 additions and 227 deletions

View File

@ -39,6 +39,7 @@ function Trace(props: TraceProps): JSX.Element {
isExpandAll,
intervalUnit,
children,
isMissing,
} = props;
const { isDarkMode } = useThemeMode();
@ -125,7 +126,7 @@ function Trace(props: TraceProps): JSX.Element {
isDarkMode={isDarkMode}
/>
<CardContainer onClick={onClick}>
<CardContainer isMissing={isMissing} onClick={onClick}>
<StyledCol flex={`${panelWidth}px`} styledclass={[styles.overFlowHidden]}>
<StyledRow styledclass={[styles.flexNoWrap]}>
<Col>
@ -174,6 +175,7 @@ function Trace(props: TraceProps): JSX.Element {
activeSpanPath={activeSpanPath}
isExpandAll={isExpandAll}
intervalUnit={intervalUnit}
isMissing={child.isMissing}
/>
))}
</>
@ -182,6 +184,10 @@ function Trace(props: TraceProps): JSX.Element {
);
}
Trace.defaultProps = {
isMissing: false,
};
interface ITraceGlobal {
globalSpread: ITraceMetaData['spread'];
globalStart: ITraceMetaData['globalStart'];
@ -196,6 +202,7 @@ interface TraceProps extends ITraceTree, ITraceGlobal {
activeSpanPath: string[];
isExpandAll: boolean;
intervalUnit: IIntervalUnit;
isMissing?: boolean;
}
export default Trace;

View File

@ -1,3 +1,4 @@
import { volcano } from '@ant-design/colors';
import styled, {
css,
DefaultTheme,
@ -15,7 +16,6 @@ export const Wrapper = styled.ul<Props>`
padding-top: 0.5rem;
position: relative;
z-index: 1;
ul {
border-left: ${({ isOnlyChild }): StyledCSS =>
isOnlyChild && 'none'} !important;
@ -36,10 +36,13 @@ export const Wrapper = styled.ul<Props>`
}
`;
export const CardContainer = styled.li`
export const CardContainer = styled.li<{ isMissing?: boolean }>`
display: flex;
width: 100%;
cursor: pointer;
border-radius: 0.25rem;
${({ isMissing }): string =>
isMissing ? `border: 1px dashed ${volcano[6]};` : ''}
`;
interface Props {

View File

@ -3,7 +3,7 @@ import { IIntervalUnit } from 'container/TraceDetail/utils';
import React, { useEffect, useState } from 'react';
import { ITraceTree } from 'types/api/trace/getTraceItem';
import { CardContainer, CardWrapper, CollapseButton, Wrapper } from './styles';
import { CardContainer, CardWrapper, CollapseButton } from './styles';
import Trace from './Trace';
import { getSpanPath } from './utils';
@ -36,7 +36,6 @@ function GanttChart(props: GanttChartProps): JSX.Element {
setIsExpandAll((prev) => !prev);
};
return (
<Wrapper>
<CardContainer>
<CollapseButton
onClick={handleCollapse}
@ -64,7 +63,6 @@ function GanttChart(props: GanttChartProps): JSX.Element {
/>
</CardWrapper>
</CardContainer>
</Wrapper>
);
}

View File

@ -38,6 +38,7 @@ export const CardWrapper = styled.div`
export const CardContainer = styled.li`
display: flex;
width: 100%;
position: relative;
`;
export const CollapseButton = styled.div`

View File

@ -1,4 +1,5 @@
import { ITraceTree } from 'types/api/trace/getTraceItem';
import { set } from 'lodash-es';
import { ITraceForest, ITraceTree } from 'types/api/trace/getTraceItem';
interface GetTraceMetaData {
globalStart: number;
@ -65,25 +66,48 @@ export function getTopLeftFromBody(
export const getNodeById = (
searchingId: string,
treeData: ITraceTree,
): ITraceTree | undefined => {
let foundNode: ITraceTree | undefined;
const traverse = (treeNode: ITraceTree, level = 0): void => {
treesData: ITraceForest | undefined,
): ITraceForest => {
const newtreeData: ITraceForest = {} as ITraceForest;
const traverse = (
treeNode: ITraceTree,
setCallBack: (arg0: ITraceTree) => void,
level = 0,
): void => {
if (!treeNode) {
return;
}
if (searchingId === treeNode.id) {
foundNode = treeNode;
setCallBack(treeNode);
}
treeNode.children.forEach((childNode) => {
traverse(childNode, level + 1);
traverse(childNode, setCallBack, level + 1);
});
};
traverse(treeData, 1);
return foundNode;
const spanTreeSetCallback = (
path: (keyof ITraceForest)[],
value: ITraceTree,
): ITraceForest => set(newtreeData, path, [value]);
if (treesData?.spanTree)
treesData.spanTree.forEach((tree) => {
traverse(tree, (value) => spanTreeSetCallback(['spanTree'], value), 1);
});
if (treesData?.missingSpanTree)
treesData.missingSpanTree.forEach((tree) => {
traverse(
tree,
(value) => spanTreeSetCallback(['missingSpanTree'], value),
1,
);
});
return newtreeData;
};
const getSpanWithoutChildren = (

View File

@ -0,0 +1,42 @@
import { volcano } from '@ant-design/colors';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Popover } from 'antd';
import React from 'react';
function PopOverContent(): JSX.Element {
return (
<div>
More details on missing spans{' '}
<a
href="https://signoz.io/docs/userguide/traces/#missing-spans"
rel="noopener noreferrer"
target="_blank"
>
here
</a>
</div>
);
}
function MissingSpansMessage(): JSX.Element {
return (
<Popover content={PopOverContent} trigger="hover" placement="bottom">
<div
style={{
textAlign: 'center',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
margin: '1rem 0',
}}
>
<InfoCircleOutlined
style={{ color: volcano[6], fontSize: '1.4rem', marginRight: '0.3rem' }}
/>{' '}
This trace has missing spans
</div>
</Popover>
);
}
export default MissingSpansMessage;

View File

@ -18,6 +18,7 @@ const { TabPane } = Tabs;
function SelectedSpanDetails(props: SelectedSpanDetailsProps): JSX.Element {
const { tree } = props;
const { isDarkMode } = useThemeMode();
const OverLayComponentName = useMemo(() => tree?.name, [tree?.name]);

View File

@ -17,15 +17,23 @@ import dayjs from 'dayjs';
import useUrlQuery from 'hooks/useUrlQuery';
import { spanServiceNameToColorMapping } from 'lib/getRandomColor';
import history from 'lib/history';
import { map } from 'lodash-es';
import { SPAN_DETAILS_LEFT_COL_WIDTH } from 'pages/TraceDetail/constants';
import React, { useEffect, useMemo, useState } from 'react';
import { ITraceTree, PayloadProps } from 'types/api/trace/getTraceItem';
import { ITraceForest, PayloadProps } from 'types/api/trace/getTraceItem';
import { getSpanTreeMetadata } from 'utils/getSpanTreeMetadata';
import { spanToTreeUtil } from 'utils/spanToTree';
import MissingSpansMessage from './Missingtrace';
import SelectedSpanDetails from './SelectedSpanDetails';
import * as styles from './styles';
import { getSortedData, IIntervalUnit, INTERVAL_UNITS } from './utils';
import { FlameGraphMissingSpansContainer, GanttChartWrapper } from './styles';
import {
getSortedData,
getTreeLevelsCount,
IIntervalUnit,
INTERVAL_UNITS,
} from './utils';
function TraceDetail({ response }: TraceDetailProps): JSX.Element {
const spanServiceColors = useMemo(
@ -43,17 +51,23 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
const [activeHoverId, setActiveHoverId] = useState<string>('');
const [activeSelectedId, setActiveSelectedId] = useState<string>(spanId || '');
const [treeData, setTreeData] = useState<ITraceTree>(
const [treesData, setTreesData] = useState<ITraceForest>(
spanToTreeUtil(response[0].events),
);
const { treeData: tree, ...traceMetaData } = useMemo(() => {
const tree = getSortedData(treeData);
const { treesData: tree, ...traceMetaData } = useMemo(() => {
const sortedTreesData: ITraceForest = {
spanTree: map(treesData.spanTree, (tree) => getSortedData(tree)),
missingSpanTree: map(
treesData.missingSpanTree,
(tree) => getSortedData(tree) || [],
),
};
// Note: Handle undefined
/*eslint-disable */
return getSpanTreeMetadata(tree as ITraceTree, spanServiceColors);
return getSpanTreeMetadata(sortedTreesData, spanServiceColors);
/* eslint-enable */
}, [treeData, spanServiceColors]);
}, [treesData, spanServiceColors]);
const [globalTraceMetadata] = useState<ITraceMetaData>({
...traceMetaData,
@ -69,24 +83,34 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
}, [activeSelectedId]);
const getSelectedNode = useMemo(() => {
return getNodeById(activeSelectedId, treeData);
}, [activeSelectedId, treeData]);
return getNodeById(activeSelectedId, treesData);
}, [activeSelectedId, treesData]);
// const onSearchHandler = (value: string) => {
// setSearchSpanString(value);
// setTreeData(spanToTreeUtil(response[0].events));
// };
const onFocusSelectedSpanHandler = (): void => {
const treeNode = getNodeById(activeSelectedId, tree);
if (treeNode) {
setTreeData(treeNode);
setTreesData(treeNode);
}
};
const onResetHandler = (): void => {
setTreeData(spanToTreeUtil(response[0].events));
setTreesData(spanToTreeUtil(response[0].events));
};
const hasMissingSpans = useMemo(
(): boolean =>
tree.missingSpanTree &&
Array.isArray(tree.missingSpanTree) &&
tree.missingSpanTree.length > 0,
[tree],
);
return (
<StyledRow styledclass={[StyledStyles.Flex({ flex: 1 })]}>
<StyledCol flex="auto" styledclass={styles.leftContainer}>
@ -101,16 +125,45 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
<StyledTypography.Text styledclass={[styles.removeMargin]}>
{traceMetaData.totalSpans} Span
</StyledTypography.Text>
{hasMissingSpans && <MissingSpansMessage />}
</StyledCol>
<Col flex="auto">
{map(tree.spanTree, (tree) => {
return (
<TraceFlameGraph
key={tree as never}
treeData={tree}
traceMetaData={traceMetaData}
hoveredSpanId={activeHoverId}
selectedSpanId={activeSelectedId}
onSpanHover={setActiveHoverId}
onSpanSelect={setActiveSelectedId}
missingSpanTree={false}
/>
);
})}
{hasMissingSpans && (
<FlameGraphMissingSpansContainer>
{map(tree.missingSpanTree, (tree) => {
return (
<TraceFlameGraph
key={tree as never}
treeData={tree}
traceMetaData={{
...traceMetaData,
levels: getTreeLevelsCount(tree),
}}
hoveredSpanId={activeHoverId}
selectedSpanId={activeSelectedId}
onSpanHover={setActiveHoverId}
onSpanSelect={setActiveSelectedId}
missingSpanTree
/>
);
})}
</FlameGraphMissingSpansContainer>
)}
</Col>
</StyledRow>
<StyledRow styledclass={[styles.traceDateAndTimelineContainer]}>
@ -122,7 +175,9 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
justifyContent: 'center',
}}
>
{tree && dayjs(tree.startTime).format('hh:mm:ss a MM/DD')}
{tree &&
traceMetaData.globalStart &&
dayjs(traceMetaData.globalStart).format('hh:mm:ss a MM/DD')}
</StyledCol>
<StyledCol flex="auto" styledclass={[styles.timelineContainer]}>
<Timeline
@ -141,14 +196,7 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
}),
]}
>
<Col flex={`${SPAN_DETAILS_LEFT_COL_WIDTH}px`}>
{/* <Search
placeholder="Type to filter.."
allowClear
onSearch={onSearchHandler}
style={{ width: 200 }}
/> */}
</Col>
<Col flex={`${SPAN_DETAILS_LEFT_COL_WIDTH}px`} />
<Col flex="auto">
<StyledSpace styledclass={[styles.floatRight]}>
<Button onClick={onFocusSelectedSpanHandler} icon={<FilterOutlined />}>
@ -161,7 +209,10 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
</Col>
</StyledRow>
<StyledDiv styledclass={[styles.ganttChartContainer]}>
<GanttChartWrapper>
{map([...tree.spanTree, ...tree.missingSpanTree], (tree) => (
<GanttChart
key={tree as never}
traceMetaData={traceMetaData}
data={tree}
activeSelectedId={activeSelectedId}
@ -171,13 +222,37 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
spanId={spanId || ''}
intervalUnit={intervalUnit}
/>
))}
{/* {map(tree.missingSpanTree, (tree) => (
<GanttChart
key={tree as never}
traceMetaData={traceMetaData}
data={tree}
activeSelectedId={activeSelectedId}
activeHoverId={activeHoverId}
setActiveHoverId={setActiveHoverId}
setActiveSelectedId={setActiveSelectedId}
spanId={spanId || ''}
intervalUnit={intervalUnit}
/>
))} */}
</GanttChartWrapper>
</StyledDiv>
</StyledCol>
<Col>
<StyledDivider styledclass={[styles.verticalSeparator]} type="vertical" />
</Col>
<StyledCol md={5} sm={5} styledclass={[styles.selectedSpanDetailContainer]}>
<SelectedSpanDetails tree={getSelectedNode} />
<SelectedSpanDetails
tree={[
...(getSelectedNode.spanTree ? getSelectedNode.spanTree : []),
...(getSelectedNode.missingSpanTree
? getSelectedNode.missingSpanTree
: []),
]
.filter(Boolean)
.find((tree) => tree)}
/>
</StyledCol>
</StyledRow>
);

View File

@ -1,4 +1,5 @@
import { css } from 'styled-components';
import { volcano } from '@ant-design/colors';
import styled, { css } from 'styled-components';
/**
* Styles for the left container. Containers flamegraph, timeline and gantt chart
@ -76,3 +77,38 @@ export const floatRight = css`
export const removeMargin = css`
margin: 0;
`;
export const GanttChartWrapper = styled.ul`
padding-left: 0;
position: absolute;
width: 100%;
height: 100%;
ul {
list-style: none;
border-left: 1px solid #434343;
padding-left: 1rem;
width: 100%;
}
ul li {
position: relative;
&:before {
position: absolute;
left: -1rem;
top: 10px;
content: '';
height: 1px;
width: 1rem;
background-color: #434343;
}
}
`;
export const FlameGraphMissingSpansContainer = styled.div`
border: 1px dashed ${volcano[6]};
padding: 0.5rem 0;
margin-top: 1rem;
border-radius: 0.25rem;
`;

View File

@ -62,7 +62,7 @@ export const convertTimeToRelevantUnit = (
return relevantTime;
};
export const getSortedData = (treeData: ITraceTree): undefined | ITraceTree => {
export const getSortedData = (treeData: ITraceTree): ITraceTree => {
const traverse = (treeNode: ITraceTree, level = 0): void => {
if (!treeNode) {
return;
@ -80,3 +80,21 @@ export const getSortedData = (treeData: ITraceTree): undefined | ITraceTree => {
return treeData;
};
export const getTreeLevelsCount = (tree: ITraceTree): number => {
let levels = 0;
const traverse = (treeNode: ITraceTree, level: number): void => {
if (!treeNode) {
return;
}
levels = Math.max(level, levels);
treeNode.children.forEach((childNode) => {
traverse(childNode, level + 1);
});
};
traverse(tree, levels);
return levels;
};

View File

@ -28,6 +28,7 @@ test('loads and displays greeting', () => {
spread: 0,
totalSpans: 0,
},
missingSpanTree: false,
treeData: {
children: [],
id: '',

View File

@ -93,8 +93,9 @@ function TraceFlameGraph(props: {
onSpanSelect: SpanItemProps['onSpanSelect'];
hoveredSpanId: string;
selectedSpanId: string;
missingSpanTree: boolean;
}): JSX.Element {
const { treeData, traceMetaData, onSpanHover } = props;
const { treeData, traceMetaData, onSpanHover, missingSpanTree } = props;
if (!treeData || treeData.id === 'empty' || !traceMetaData) {
return <div />;
@ -140,6 +141,7 @@ function TraceFlameGraph(props: {
hoveredSpanId={hoveredSpanId}
selectedSpanId={selectedSpanId}
/>
{spanData.children.map((childData) => (
<RenderSpanRecursive
level={level + 1}
@ -164,7 +166,7 @@ function TraceFlameGraph(props: {
onSpanSelect={onSpanSelect}
hoveredSpanId={hoveredSpanId}
selectedSpanId={selectedSpanId}
level={0}
level={missingSpanTree ? -1 : 0}
parentLeftOffset={0}
/>
</TraceFlameGraphContainer>

View File

@ -21,7 +21,8 @@ export type Span = [
string | string[],
string | string[],
string | string[],
ITraceTree[],
Record<string, unknown>[],
boolean,
];
export interface ITraceTree {
@ -37,6 +38,10 @@ export interface ITraceTree {
serviceColour: string;
hasError?: boolean;
event?: ITraceEvents[];
isMissing?: boolean;
// For internal use
isProcessed?: boolean;
references?: Record<string, string>[];
}
export interface ITraceTag {
@ -48,3 +53,8 @@ interface ITraceEvents {
attributeMap: { event: string; [key: string]: string };
name?: string;
}
export interface ITraceForest {
spanTree: ITraceTree[];
missingSpanTree: ITraceTree[];
}

View File

@ -1,28 +1,31 @@
/* eslint-disable no-param-reassign */
import { errorColor } from 'lib/getRandomColor';
import { ITraceTree } from 'types/api/trace/getTraceItem';
import { ITraceForest, ITraceTree } from 'types/api/trace/getTraceItem';
/**
* Traverses the Span Tree data and returns the relevant meta data.
* Metadata includes globalStart, globalEnd,
*/
export const getSpanTreeMetadata = (
treeData: ITraceTree,
treesData: ITraceForest,
spanServiceColours: { [key: string]: string },
): GetSpanTreeMetaData => {
let globalStart = Number.POSITIVE_INFINITY;
let globalEnd = Number.NEGATIVE_INFINITY;
let totalSpans = 0;
let levels = 1;
const traverse = (treeNode: ITraceTree, level = 0): void => {
if (!treeNode) {
return;
}
totalSpans += 1;
levels = Math.max(levels, level);
const { startTime } = treeNode;
const endTime = startTime + treeNode.value / 1e6;
const { startTime, value } = treeNode;
if (startTime !== null && value !== null) {
const endTime = startTime + value / 1e6;
globalStart = Math.min(globalStart, startTime);
globalEnd = Math.max(globalEnd, endTime);
}
if (treeNode.hasError) {
treeNode.serviceColour = errorColor;
} else treeNode.serviceColour = spanServiceColours[treeNode.serviceName];
@ -30,7 +33,12 @@ export const getSpanTreeMetadata = (
traverse(childNode, level + 1);
});
};
treesData.spanTree.forEach((treeData) => {
traverse(treeData, 1);
});
treesData.missingSpanTree.forEach((treeData) => {
traverse(treeData, 1);
});
return {
globalStart,
@ -38,7 +46,7 @@ export const getSpanTreeMetadata = (
spread: globalEnd - globalStart,
totalSpans,
levels,
treeData,
treesData,
};
};
@ -48,5 +56,5 @@ interface GetSpanTreeMetaData {
spread: number;
totalSpans: number;
levels: number;
treeData: ITraceTree;
treesData: ITraceForest;
}

View File

@ -1,137 +1,125 @@
/* eslint-disable */
// @ts-nocheck
/* eslint-disable no-restricted-syntax */
import { cloneDeep } from 'lodash-es';
import { ITraceTree, Span } from 'types/api/trace/getTraceItem';
import { ITraceForest, ITraceTree, Span } from 'types/api/trace/getTraceItem';
export const spanToTreeUtil = (originalList: Span[]): ITraceTree => {
// Initializing tree. What should be returned is trace is empty? We should have better error handling
let tree: ITraceTree = {
id: 'empty',
name: 'default',
value: 0,
time: 0,
startTime: 0,
tags: [],
const getSpanReferences = (
rawReferences: string[] = [],
): Record<string, string>[] => {
return rawReferences.map((rawRef) => {
const refObject: Record<string, string> = {};
rawRef
.replaceAll('{', '')
.replaceAll('}', '')
.replaceAll(' ', '')
.split(',')
.forEach((rawRefKeyPair) => {
const [key, value] = rawRefKeyPair.split('=');
refObject[key] = value;
});
return refObject;
});
};
// This getSpanTags is migrated from the previous implementation.
const getSpanTags = (spanData: Span): { key: string; value: string }[] => {
const tags = [];
if (spanData[7] !== null && spanData[8] !== null) {
if (typeof spanData[7] === 'string' && typeof spanData[8] === 'string') {
tags.push({ key: spanData[7], value: spanData[8] });
} else if (spanData[7].length > 0 && spanData[8].length > 0) {
for (let j = 0; j < spanData[7].length; j += 1) {
tags.push({ key: spanData[7][j], value: spanData[8][j] });
}
}
}
return tags;
};
// eslint-disable-next-line sonarjs/cognitive-complexity
export const spanToTreeUtil = (inputSpanList: Span[]): ITraceForest => {
const spanList = cloneDeep(inputSpanList);
const traceIdSet: Set<string> = new Set();
const spanMap: Record<string, ITraceTree> = {};
const createTarceRootSpan = (
spanReferences: Record<string, string>[],
): void => {
spanReferences.forEach(({ SpanId, TraceId }) => {
traceIdSet.add(TraceId);
if (SpanId && !spanMap[SpanId]) {
spanMap[SpanId] = {
id: SpanId,
name: `Missing Span (${SpanId})`,
children: [],
serviceColour: '',
serviceName: '',
startTime: null as never,
tags: [],
time: null as never,
value: null as never,
isMissing: true,
};
}
});
};
const spanlist = cloneDeep(originalList);
// let spans :spanItem[]= trace.spans;
if (spanlist) {
// Create a dict with spanIDs as keys
// PNOTE
// Can we now assign different strings as id - Yes
// https://stackoverflow.com/questions/15877362/declare-and-initialize-a-dictionary-in-typescript
// May1
// https://stackoverflow.com/questions/13315131/enforcing-the-type-of-the-indexed-members-of-a-typescript-object
const mapped_array: { [id: string]: Span } = {};
const originalListArray: { [id: string]: Span } = {};
for (let i = 0; i < spanlist.length; i++) {
originalListArray[spanlist[i][1]] = originalList[i];
mapped_array[spanlist[i][1]] = spanlist[i];
mapped_array[spanlist[i][1]][10] = []; // initialising the 10th element in the Span data structure which is array
// of type ITraceTree
// console.log('IDs while creating mapped array')
// console.log(`SpanID is ${spanlist[i][1]}\n`);
}
// console.log(`In SpanTreeUtil: mapped_arrayis ${mapped_array}`);
for (const id in mapped_array) {
const child_span = mapped_array[id];
// mapping tags to new structure
const tags_temp = [];
if (child_span[7] !== null && child_span[8] !== null) {
if (
typeof child_span[7] === 'string' &&
typeof child_span[8] === 'string'
) {
tags_temp.push({ key: child_span[7], value: child_span[8] });
} else if (child_span[7].length > 0 && child_span[8].length > 0) {
for (let j = 0; j < child_span[7].length; j++) {
tags_temp.push({ key: child_span[7][j], value: child_span[8][j] });
}
}
}
const push_object: ITraceTree = {
id: child_span[1],
name: child_span[4],
value: parseInt(child_span[6]),
time: parseInt(child_span[6]),
startTime: child_span[0],
tags: tags_temp,
children: mapped_array[id][10],
serviceName: child_span[3],
hasError: !!child_span[11],
spanList.forEach((span) => {
const spanReferences = getSpanReferences(span[9] as string[]);
const spanObject = {
id: span[1],
name: span[4],
value: parseInt(span[6], 10),
time: parseInt(span[6], 10),
startTime: span[0],
tags: getSpanTags(span),
children: [],
serviceName: span[3],
hasError: !!span[11],
serviceColour: '',
event: originalListArray[id][10].map((e) => {
return JSON.parse(decodeURIComponent(e || '{}')) || {};
event: span[10].map((e) => {
return (
JSON.parse(decodeURIComponent((e as never) || ('{}' as never))) ||
({} as Record<string, unknown>)
);
}),
references: spanReferences,
};
spanMap[span[1]] = spanObject;
});
const referencesArr = mapped_array[id][9];
let refArray = [];
if (typeof referencesArr === 'string') {
refArray.push(referencesArr);
} else {
refArray = referencesArr;
for (const [, spanData] of Object.entries(spanMap)) {
if (spanData.references) {
createTarceRootSpan(spanData.references);
spanData.references.forEach(({ SpanId: parentSpanId }) => {
if (spanMap[parentSpanId]) {
spanData.isProcessed = true;
spanMap[parentSpanId].children.push(spanData);
}
});
}
}
for (const [spanId, spanData] of Object.entries(spanMap)) {
if (spanData.isProcessed) {
delete spanMap[spanId];
}
}
const references = [];
refArray.forEach((element) => {
element = element
.replaceAll('{', '')
.replaceAll('}', '')
.replaceAll(' ', '');
const arr = element.split(',');
const refItem = { traceID: '', spanID: '', refType: '' };
arr.forEach((obj) => {
const arr2 = obj.split('=');
if (arr2[0] === 'TraceId') {
refItem.traceID = arr2[1];
} else if (arr2[0] === 'SpanId') {
refItem.spanID = arr2[1];
} else if (arr2[0] === 'RefType') {
refItem.refType = arr2[1];
const spanTree: ITraceTree[] = [];
const missingSpanTree: ITraceTree[] = [];
const referencedTraceIds: string[] = Array.from(traceIdSet);
Object.keys(spanMap).forEach((spanId) => {
for (const traceId of referencedTraceIds) {
if (traceId.includes(spanId)) {
spanTree.push(spanMap[spanId]);
} else {
missingSpanTree.push(spanMap[spanId]);
}
}
});
references.push(refItem);
});
if (references.length !== 0 && references[0].spanID.length !== 0) {
if (references[0].refType === 'CHILD_OF') {
const parentID = references[0].spanID;
// console.log(`In SpanTreeUtil: mapped_array[parentID] is ${mapped_array[parentID]}`);
if (typeof mapped_array[parentID] !== 'undefined') {
// checking for undefined [10] issue
mapped_array[parentID][10].push(push_object);
} else {
// console.log(
// `In SpanTreeUtil: mapped_array[parentID] is undefined, parentID is ${parentID}`,
// );
// console.log(
// `In SpanTreeUtil: mapped_array[parentID] is undefined, mapped_array[parentID] is ${mapped_array[parentID]}`,
// );
}
}
} else {
tree = push_object;
}
} // end of for loop
} // end of if(spans)
return { ...tree };
return {
spanTree,
missingSpanTree,
};
};