mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 08:26:03 +08:00
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:
parent
da368ab5e8
commit
80c80b2180
@ -39,6 +39,7 @@ function Trace(props: TraceProps): JSX.Element {
|
|||||||
isExpandAll,
|
isExpandAll,
|
||||||
intervalUnit,
|
intervalUnit,
|
||||||
children,
|
children,
|
||||||
|
isMissing,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const { isDarkMode } = useThemeMode();
|
const { isDarkMode } = useThemeMode();
|
||||||
@ -125,7 +126,7 @@ function Trace(props: TraceProps): JSX.Element {
|
|||||||
isDarkMode={isDarkMode}
|
isDarkMode={isDarkMode}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CardContainer onClick={onClick}>
|
<CardContainer isMissing={isMissing} onClick={onClick}>
|
||||||
<StyledCol flex={`${panelWidth}px`} styledclass={[styles.overFlowHidden]}>
|
<StyledCol flex={`${panelWidth}px`} styledclass={[styles.overFlowHidden]}>
|
||||||
<StyledRow styledclass={[styles.flexNoWrap]}>
|
<StyledRow styledclass={[styles.flexNoWrap]}>
|
||||||
<Col>
|
<Col>
|
||||||
@ -174,6 +175,7 @@ function Trace(props: TraceProps): JSX.Element {
|
|||||||
activeSpanPath={activeSpanPath}
|
activeSpanPath={activeSpanPath}
|
||||||
isExpandAll={isExpandAll}
|
isExpandAll={isExpandAll}
|
||||||
intervalUnit={intervalUnit}
|
intervalUnit={intervalUnit}
|
||||||
|
isMissing={child.isMissing}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
@ -182,6 +184,10 @@ function Trace(props: TraceProps): JSX.Element {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Trace.defaultProps = {
|
||||||
|
isMissing: false,
|
||||||
|
};
|
||||||
|
|
||||||
interface ITraceGlobal {
|
interface ITraceGlobal {
|
||||||
globalSpread: ITraceMetaData['spread'];
|
globalSpread: ITraceMetaData['spread'];
|
||||||
globalStart: ITraceMetaData['globalStart'];
|
globalStart: ITraceMetaData['globalStart'];
|
||||||
@ -196,6 +202,7 @@ interface TraceProps extends ITraceTree, ITraceGlobal {
|
|||||||
activeSpanPath: string[];
|
activeSpanPath: string[];
|
||||||
isExpandAll: boolean;
|
isExpandAll: boolean;
|
||||||
intervalUnit: IIntervalUnit;
|
intervalUnit: IIntervalUnit;
|
||||||
|
isMissing?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Trace;
|
export default Trace;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { volcano } from '@ant-design/colors';
|
||||||
import styled, {
|
import styled, {
|
||||||
css,
|
css,
|
||||||
DefaultTheme,
|
DefaultTheme,
|
||||||
@ -15,7 +16,6 @@ export const Wrapper = styled.ul<Props>`
|
|||||||
padding-top: 0.5rem;
|
padding-top: 0.5rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
border-left: ${({ isOnlyChild }): StyledCSS =>
|
border-left: ${({ isOnlyChild }): StyledCSS =>
|
||||||
isOnlyChild && 'none'} !important;
|
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;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
${({ isMissing }): string =>
|
||||||
|
isMissing ? `border: 1px dashed ${volcano[6]};` : ''}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -3,7 +3,7 @@ import { IIntervalUnit } from 'container/TraceDetail/utils';
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { ITraceTree } from 'types/api/trace/getTraceItem';
|
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 Trace from './Trace';
|
||||||
import { getSpanPath } from './utils';
|
import { getSpanPath } from './utils';
|
||||||
|
|
||||||
@ -36,35 +36,33 @@ function GanttChart(props: GanttChartProps): JSX.Element {
|
|||||||
setIsExpandAll((prev) => !prev);
|
setIsExpandAll((prev) => !prev);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<CardContainer>
|
||||||
<CardContainer>
|
<CollapseButton
|
||||||
<CollapseButton
|
onClick={handleCollapse}
|
||||||
onClick={handleCollapse}
|
title={isExpandAll ? 'Collapse All' : 'Expand All'}
|
||||||
title={isExpandAll ? 'Collapse All' : 'Expand All'}
|
>
|
||||||
>
|
{isExpandAll ? <MinusSquareOutlined /> : <PlusSquareOutlined />}
|
||||||
{isExpandAll ? <MinusSquareOutlined /> : <PlusSquareOutlined />}
|
</CollapseButton>
|
||||||
</CollapseButton>
|
<CardWrapper>
|
||||||
<CardWrapper>
|
<Trace
|
||||||
<Trace
|
activeHoverId={activeHoverId}
|
||||||
activeHoverId={activeHoverId}
|
activeSpanPath={activeSpanPath}
|
||||||
activeSpanPath={activeSpanPath}
|
setActiveHoverId={setActiveHoverId}
|
||||||
setActiveHoverId={setActiveHoverId}
|
key={data.id}
|
||||||
key={data.id}
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
{...{
|
||||||
{...{
|
...data,
|
||||||
...data,
|
globalSpread,
|
||||||
globalSpread,
|
globalStart,
|
||||||
globalStart,
|
setActiveSelectedId,
|
||||||
setActiveSelectedId,
|
activeSelectedId,
|
||||||
activeSelectedId,
|
}}
|
||||||
}}
|
level={0}
|
||||||
level={0}
|
isExpandAll={isExpandAll}
|
||||||
isExpandAll={isExpandAll}
|
intervalUnit={intervalUnit}
|
||||||
intervalUnit={intervalUnit}
|
/>
|
||||||
/>
|
</CardWrapper>
|
||||||
</CardWrapper>
|
</CardContainer>
|
||||||
</CardContainer>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ export const CardWrapper = styled.div`
|
|||||||
export const CardContainer = styled.li`
|
export const CardContainer = styled.li`
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const CollapseButton = styled.div`
|
export const CollapseButton = styled.div`
|
||||||
|
@ -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 {
|
interface GetTraceMetaData {
|
||||||
globalStart: number;
|
globalStart: number;
|
||||||
@ -65,25 +66,48 @@ export function getTopLeftFromBody(
|
|||||||
|
|
||||||
export const getNodeById = (
|
export const getNodeById = (
|
||||||
searchingId: string,
|
searchingId: string,
|
||||||
treeData: ITraceTree,
|
treesData: ITraceForest | undefined,
|
||||||
): ITraceTree | undefined => {
|
): ITraceForest => {
|
||||||
let foundNode: ITraceTree | undefined;
|
const newtreeData: ITraceForest = {} as ITraceForest;
|
||||||
const traverse = (treeNode: ITraceTree, level = 0): void => {
|
|
||||||
|
const traverse = (
|
||||||
|
treeNode: ITraceTree,
|
||||||
|
setCallBack: (arg0: ITraceTree) => void,
|
||||||
|
level = 0,
|
||||||
|
): void => {
|
||||||
if (!treeNode) {
|
if (!treeNode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchingId === treeNode.id) {
|
if (searchingId === treeNode.id) {
|
||||||
foundNode = treeNode;
|
setCallBack(treeNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
treeNode.children.forEach((childNode) => {
|
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 = (
|
const getSpanWithoutChildren = (
|
||||||
|
42
frontend/src/container/TraceDetail/Missingtrace.tsx
Normal file
42
frontend/src/container/TraceDetail/Missingtrace.tsx
Normal 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;
|
@ -18,6 +18,7 @@ const { TabPane } = Tabs;
|
|||||||
|
|
||||||
function SelectedSpanDetails(props: SelectedSpanDetailsProps): JSX.Element {
|
function SelectedSpanDetails(props: SelectedSpanDetailsProps): JSX.Element {
|
||||||
const { tree } = props;
|
const { tree } = props;
|
||||||
|
|
||||||
const { isDarkMode } = useThemeMode();
|
const { isDarkMode } = useThemeMode();
|
||||||
|
|
||||||
const OverLayComponentName = useMemo(() => tree?.name, [tree?.name]);
|
const OverLayComponentName = useMemo(() => tree?.name, [tree?.name]);
|
||||||
|
@ -17,15 +17,23 @@ import dayjs from 'dayjs';
|
|||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import { spanServiceNameToColorMapping } from 'lib/getRandomColor';
|
import { spanServiceNameToColorMapping } from 'lib/getRandomColor';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
|
import { map } from 'lodash-es';
|
||||||
import { SPAN_DETAILS_LEFT_COL_WIDTH } from 'pages/TraceDetail/constants';
|
import { SPAN_DETAILS_LEFT_COL_WIDTH } from 'pages/TraceDetail/constants';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
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 { getSpanTreeMetadata } from 'utils/getSpanTreeMetadata';
|
||||||
import { spanToTreeUtil } from 'utils/spanToTree';
|
import { spanToTreeUtil } from 'utils/spanToTree';
|
||||||
|
|
||||||
|
import MissingSpansMessage from './Missingtrace';
|
||||||
import SelectedSpanDetails from './SelectedSpanDetails';
|
import SelectedSpanDetails from './SelectedSpanDetails';
|
||||||
import * as styles from './styles';
|
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 {
|
function TraceDetail({ response }: TraceDetailProps): JSX.Element {
|
||||||
const spanServiceColors = useMemo(
|
const spanServiceColors = useMemo(
|
||||||
@ -43,17 +51,23 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
|
|||||||
const [activeHoverId, setActiveHoverId] = useState<string>('');
|
const [activeHoverId, setActiveHoverId] = useState<string>('');
|
||||||
const [activeSelectedId, setActiveSelectedId] = useState<string>(spanId || '');
|
const [activeSelectedId, setActiveSelectedId] = useState<string>(spanId || '');
|
||||||
|
|
||||||
const [treeData, setTreeData] = useState<ITraceTree>(
|
const [treesData, setTreesData] = useState<ITraceForest>(
|
||||||
spanToTreeUtil(response[0].events),
|
spanToTreeUtil(response[0].events),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { treeData: tree, ...traceMetaData } = useMemo(() => {
|
const { treesData: tree, ...traceMetaData } = useMemo(() => {
|
||||||
const tree = getSortedData(treeData);
|
const sortedTreesData: ITraceForest = {
|
||||||
|
spanTree: map(treesData.spanTree, (tree) => getSortedData(tree)),
|
||||||
|
missingSpanTree: map(
|
||||||
|
treesData.missingSpanTree,
|
||||||
|
(tree) => getSortedData(tree) || [],
|
||||||
|
),
|
||||||
|
};
|
||||||
// Note: Handle undefined
|
// Note: Handle undefined
|
||||||
/*eslint-disable */
|
/*eslint-disable */
|
||||||
return getSpanTreeMetadata(tree as ITraceTree, spanServiceColors);
|
return getSpanTreeMetadata(sortedTreesData, spanServiceColors);
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
}, [treeData, spanServiceColors]);
|
}, [treesData, spanServiceColors]);
|
||||||
|
|
||||||
const [globalTraceMetadata] = useState<ITraceMetaData>({
|
const [globalTraceMetadata] = useState<ITraceMetaData>({
|
||||||
...traceMetaData,
|
...traceMetaData,
|
||||||
@ -69,24 +83,34 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
|
|||||||
}, [activeSelectedId]);
|
}, [activeSelectedId]);
|
||||||
|
|
||||||
const getSelectedNode = useMemo(() => {
|
const getSelectedNode = useMemo(() => {
|
||||||
return getNodeById(activeSelectedId, treeData);
|
return getNodeById(activeSelectedId, treesData);
|
||||||
}, [activeSelectedId, treeData]);
|
}, [activeSelectedId, treesData]);
|
||||||
|
|
||||||
// const onSearchHandler = (value: string) => {
|
// const onSearchHandler = (value: string) => {
|
||||||
// setSearchSpanString(value);
|
// setSearchSpanString(value);
|
||||||
// setTreeData(spanToTreeUtil(response[0].events));
|
// setTreeData(spanToTreeUtil(response[0].events));
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const onFocusSelectedSpanHandler = (): void => {
|
const onFocusSelectedSpanHandler = (): void => {
|
||||||
const treeNode = getNodeById(activeSelectedId, tree);
|
const treeNode = getNodeById(activeSelectedId, tree);
|
||||||
|
|
||||||
if (treeNode) {
|
if (treeNode) {
|
||||||
setTreeData(treeNode);
|
setTreesData(treeNode);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onResetHandler = (): void => {
|
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 (
|
return (
|
||||||
<StyledRow styledclass={[StyledStyles.Flex({ flex: 1 })]}>
|
<StyledRow styledclass={[StyledStyles.Flex({ flex: 1 })]}>
|
||||||
<StyledCol flex="auto" styledclass={styles.leftContainer}>
|
<StyledCol flex="auto" styledclass={styles.leftContainer}>
|
||||||
@ -101,16 +125,45 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
|
|||||||
<StyledTypography.Text styledclass={[styles.removeMargin]}>
|
<StyledTypography.Text styledclass={[styles.removeMargin]}>
|
||||||
{traceMetaData.totalSpans} Span
|
{traceMetaData.totalSpans} Span
|
||||||
</StyledTypography.Text>
|
</StyledTypography.Text>
|
||||||
|
{hasMissingSpans && <MissingSpansMessage />}
|
||||||
</StyledCol>
|
</StyledCol>
|
||||||
<Col flex="auto">
|
<Col flex="auto">
|
||||||
<TraceFlameGraph
|
{map(tree.spanTree, (tree) => {
|
||||||
treeData={tree}
|
return (
|
||||||
traceMetaData={traceMetaData}
|
<TraceFlameGraph
|
||||||
hoveredSpanId={activeHoverId}
|
key={tree as never}
|
||||||
selectedSpanId={activeSelectedId}
|
treeData={tree}
|
||||||
onSpanHover={setActiveHoverId}
|
traceMetaData={traceMetaData}
|
||||||
onSpanSelect={setActiveSelectedId}
|
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>
|
</Col>
|
||||||
</StyledRow>
|
</StyledRow>
|
||||||
<StyledRow styledclass={[styles.traceDateAndTimelineContainer]}>
|
<StyledRow styledclass={[styles.traceDateAndTimelineContainer]}>
|
||||||
@ -122,7 +175,9 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
|
|||||||
justifyContent: 'center',
|
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>
|
||||||
<StyledCol flex="auto" styledclass={[styles.timelineContainer]}>
|
<StyledCol flex="auto" styledclass={[styles.timelineContainer]}>
|
||||||
<Timeline
|
<Timeline
|
||||||
@ -141,14 +196,7 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
|
|||||||
}),
|
}),
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Col flex={`${SPAN_DETAILS_LEFT_COL_WIDTH}px`}>
|
<Col flex={`${SPAN_DETAILS_LEFT_COL_WIDTH}px`} />
|
||||||
{/* <Search
|
|
||||||
placeholder="Type to filter.."
|
|
||||||
allowClear
|
|
||||||
onSearch={onSearchHandler}
|
|
||||||
style={{ width: 200 }}
|
|
||||||
/> */}
|
|
||||||
</Col>
|
|
||||||
<Col flex="auto">
|
<Col flex="auto">
|
||||||
<StyledSpace styledclass={[styles.floatRight]}>
|
<StyledSpace styledclass={[styles.floatRight]}>
|
||||||
<Button onClick={onFocusSelectedSpanHandler} icon={<FilterOutlined />}>
|
<Button onClick={onFocusSelectedSpanHandler} icon={<FilterOutlined />}>
|
||||||
@ -161,23 +209,50 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
|
|||||||
</Col>
|
</Col>
|
||||||
</StyledRow>
|
</StyledRow>
|
||||||
<StyledDiv styledclass={[styles.ganttChartContainer]}>
|
<StyledDiv styledclass={[styles.ganttChartContainer]}>
|
||||||
<GanttChart
|
<GanttChartWrapper>
|
||||||
traceMetaData={traceMetaData}
|
{map([...tree.spanTree, ...tree.missingSpanTree], (tree) => (
|
||||||
data={tree}
|
<GanttChart
|
||||||
activeSelectedId={activeSelectedId}
|
key={tree as never}
|
||||||
activeHoverId={activeHoverId}
|
traceMetaData={traceMetaData}
|
||||||
setActiveHoverId={setActiveHoverId}
|
data={tree}
|
||||||
setActiveSelectedId={setActiveSelectedId}
|
activeSelectedId={activeSelectedId}
|
||||||
spanId={spanId || ''}
|
activeHoverId={activeHoverId}
|
||||||
intervalUnit={intervalUnit}
|
setActiveHoverId={setActiveHoverId}
|
||||||
/>
|
setActiveSelectedId={setActiveSelectedId}
|
||||||
|
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>
|
</StyledDiv>
|
||||||
</StyledCol>
|
</StyledCol>
|
||||||
<Col>
|
<Col>
|
||||||
<StyledDivider styledclass={[styles.verticalSeparator]} type="vertical" />
|
<StyledDivider styledclass={[styles.verticalSeparator]} type="vertical" />
|
||||||
</Col>
|
</Col>
|
||||||
<StyledCol md={5} sm={5} styledclass={[styles.selectedSpanDetailContainer]}>
|
<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>
|
</StyledCol>
|
||||||
</StyledRow>
|
</StyledRow>
|
||||||
);
|
);
|
||||||
|
@ -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
|
* Styles for the left container. Containers flamegraph, timeline and gantt chart
|
||||||
@ -76,3 +77,38 @@ export const floatRight = css`
|
|||||||
export const removeMargin = css`
|
export const removeMargin = css`
|
||||||
margin: 0;
|
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;
|
||||||
|
`;
|
||||||
|
@ -62,7 +62,7 @@ export const convertTimeToRelevantUnit = (
|
|||||||
return relevantTime;
|
return relevantTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSortedData = (treeData: ITraceTree): undefined | ITraceTree => {
|
export const getSortedData = (treeData: ITraceTree): ITraceTree => {
|
||||||
const traverse = (treeNode: ITraceTree, level = 0): void => {
|
const traverse = (treeNode: ITraceTree, level = 0): void => {
|
||||||
if (!treeNode) {
|
if (!treeNode) {
|
||||||
return;
|
return;
|
||||||
@ -80,3 +80,21 @@ export const getSortedData = (treeData: ITraceTree): undefined | ITraceTree => {
|
|||||||
|
|
||||||
return treeData;
|
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;
|
||||||
|
};
|
||||||
|
@ -28,6 +28,7 @@ test('loads and displays greeting', () => {
|
|||||||
spread: 0,
|
spread: 0,
|
||||||
totalSpans: 0,
|
totalSpans: 0,
|
||||||
},
|
},
|
||||||
|
missingSpanTree: false,
|
||||||
treeData: {
|
treeData: {
|
||||||
children: [],
|
children: [],
|
||||||
id: '',
|
id: '',
|
||||||
|
@ -93,8 +93,9 @@ function TraceFlameGraph(props: {
|
|||||||
onSpanSelect: SpanItemProps['onSpanSelect'];
|
onSpanSelect: SpanItemProps['onSpanSelect'];
|
||||||
hoveredSpanId: string;
|
hoveredSpanId: string;
|
||||||
selectedSpanId: string;
|
selectedSpanId: string;
|
||||||
|
missingSpanTree: boolean;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { treeData, traceMetaData, onSpanHover } = props;
|
const { treeData, traceMetaData, onSpanHover, missingSpanTree } = props;
|
||||||
|
|
||||||
if (!treeData || treeData.id === 'empty' || !traceMetaData) {
|
if (!treeData || treeData.id === 'empty' || !traceMetaData) {
|
||||||
return <div />;
|
return <div />;
|
||||||
@ -140,6 +141,7 @@ function TraceFlameGraph(props: {
|
|||||||
hoveredSpanId={hoveredSpanId}
|
hoveredSpanId={hoveredSpanId}
|
||||||
selectedSpanId={selectedSpanId}
|
selectedSpanId={selectedSpanId}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{spanData.children.map((childData) => (
|
{spanData.children.map((childData) => (
|
||||||
<RenderSpanRecursive
|
<RenderSpanRecursive
|
||||||
level={level + 1}
|
level={level + 1}
|
||||||
@ -164,7 +166,7 @@ function TraceFlameGraph(props: {
|
|||||||
onSpanSelect={onSpanSelect}
|
onSpanSelect={onSpanSelect}
|
||||||
hoveredSpanId={hoveredSpanId}
|
hoveredSpanId={hoveredSpanId}
|
||||||
selectedSpanId={selectedSpanId}
|
selectedSpanId={selectedSpanId}
|
||||||
level={0}
|
level={missingSpanTree ? -1 : 0}
|
||||||
parentLeftOffset={0}
|
parentLeftOffset={0}
|
||||||
/>
|
/>
|
||||||
</TraceFlameGraphContainer>
|
</TraceFlameGraphContainer>
|
||||||
|
@ -21,7 +21,8 @@ export type Span = [
|
|||||||
string | string[],
|
string | string[],
|
||||||
string | string[],
|
string | string[],
|
||||||
string | string[],
|
string | string[],
|
||||||
ITraceTree[],
|
Record<string, unknown>[],
|
||||||
|
boolean,
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface ITraceTree {
|
export interface ITraceTree {
|
||||||
@ -37,6 +38,10 @@ export interface ITraceTree {
|
|||||||
serviceColour: string;
|
serviceColour: string;
|
||||||
hasError?: boolean;
|
hasError?: boolean;
|
||||||
event?: ITraceEvents[];
|
event?: ITraceEvents[];
|
||||||
|
isMissing?: boolean;
|
||||||
|
// For internal use
|
||||||
|
isProcessed?: boolean;
|
||||||
|
references?: Record<string, string>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITraceTag {
|
export interface ITraceTag {
|
||||||
@ -48,3 +53,8 @@ interface ITraceEvents {
|
|||||||
attributeMap: { event: string; [key: string]: string };
|
attributeMap: { event: string; [key: string]: string };
|
||||||
name?: string;
|
name?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ITraceForest {
|
||||||
|
spanTree: ITraceTree[];
|
||||||
|
missingSpanTree: ITraceTree[];
|
||||||
|
}
|
||||||
|
@ -1,28 +1,31 @@
|
|||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
import { errorColor } from 'lib/getRandomColor';
|
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.
|
* Traverses the Span Tree data and returns the relevant meta data.
|
||||||
* Metadata includes globalStart, globalEnd,
|
* Metadata includes globalStart, globalEnd,
|
||||||
*/
|
*/
|
||||||
export const getSpanTreeMetadata = (
|
export const getSpanTreeMetadata = (
|
||||||
treeData: ITraceTree,
|
treesData: ITraceForest,
|
||||||
spanServiceColours: { [key: string]: string },
|
spanServiceColours: { [key: string]: string },
|
||||||
): GetSpanTreeMetaData => {
|
): GetSpanTreeMetaData => {
|
||||||
let globalStart = Number.POSITIVE_INFINITY;
|
let globalStart = Number.POSITIVE_INFINITY;
|
||||||
let globalEnd = Number.NEGATIVE_INFINITY;
|
let globalEnd = Number.NEGATIVE_INFINITY;
|
||||||
let totalSpans = 0;
|
let totalSpans = 0;
|
||||||
let levels = 1;
|
let levels = 1;
|
||||||
|
|
||||||
const traverse = (treeNode: ITraceTree, level = 0): void => {
|
const traverse = (treeNode: ITraceTree, level = 0): void => {
|
||||||
if (!treeNode) {
|
if (!treeNode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
totalSpans += 1;
|
totalSpans += 1;
|
||||||
levels = Math.max(levels, level);
|
levels = Math.max(levels, level);
|
||||||
const { startTime } = treeNode;
|
const { startTime, value } = treeNode;
|
||||||
const endTime = startTime + treeNode.value / 1e6;
|
if (startTime !== null && value !== null) {
|
||||||
globalStart = Math.min(globalStart, startTime);
|
const endTime = startTime + value / 1e6;
|
||||||
globalEnd = Math.max(globalEnd, endTime);
|
globalStart = Math.min(globalStart, startTime);
|
||||||
|
globalEnd = Math.max(globalEnd, endTime);
|
||||||
|
}
|
||||||
if (treeNode.hasError) {
|
if (treeNode.hasError) {
|
||||||
treeNode.serviceColour = errorColor;
|
treeNode.serviceColour = errorColor;
|
||||||
} else treeNode.serviceColour = spanServiceColours[treeNode.serviceName];
|
} else treeNode.serviceColour = spanServiceColours[treeNode.serviceName];
|
||||||
@ -30,7 +33,12 @@ export const getSpanTreeMetadata = (
|
|||||||
traverse(childNode, level + 1);
|
traverse(childNode, level + 1);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
traverse(treeData, 1);
|
treesData.spanTree.forEach((treeData) => {
|
||||||
|
traverse(treeData, 1);
|
||||||
|
});
|
||||||
|
treesData.missingSpanTree.forEach((treeData) => {
|
||||||
|
traverse(treeData, 1);
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
globalStart,
|
globalStart,
|
||||||
@ -38,7 +46,7 @@ export const getSpanTreeMetadata = (
|
|||||||
spread: globalEnd - globalStart,
|
spread: globalEnd - globalStart,
|
||||||
totalSpans,
|
totalSpans,
|
||||||
levels,
|
levels,
|
||||||
treeData,
|
treesData,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -48,5 +56,5 @@ interface GetSpanTreeMetaData {
|
|||||||
spread: number;
|
spread: number;
|
||||||
totalSpans: number;
|
totalSpans: number;
|
||||||
levels: number;
|
levels: number;
|
||||||
treeData: ITraceTree;
|
treesData: ITraceForest;
|
||||||
}
|
}
|
||||||
|
@ -1,137 +1,125 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable no-restricted-syntax */
|
||||||
// @ts-nocheck
|
|
||||||
|
|
||||||
import { cloneDeep } from 'lodash-es';
|
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 => {
|
const getSpanReferences = (
|
||||||
// Initializing tree. What should be returned is trace is empty? We should have better error handling
|
rawReferences: string[] = [],
|
||||||
let tree: ITraceTree = {
|
): Record<string, string>[] => {
|
||||||
id: 'empty',
|
return rawReferences.map((rawRef) => {
|
||||||
name: 'default',
|
const refObject: Record<string, string> = {};
|
||||||
value: 0,
|
rawRef
|
||||||
time: 0,
|
.replaceAll('{', '')
|
||||||
startTime: 0,
|
.replaceAll('}', '')
|
||||||
tags: [],
|
.replaceAll(' ', '')
|
||||||
children: [],
|
.split(',')
|
||||||
serviceColour: '',
|
.forEach((rawRefKeyPair) => {
|
||||||
serviceName: '',
|
const [key, value] = rawRefKeyPair.split('=');
|
||||||
};
|
refObject[key] = value;
|
||||||
|
|
||||||
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],
|
|
||||||
serviceColour: '',
|
|
||||||
event: originalListArray[id][10].map((e) => {
|
|
||||||
return JSON.parse(decodeURIComponent(e || '{}')) || {};
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const referencesArr = mapped_array[id][9];
|
|
||||||
let refArray = [];
|
|
||||||
if (typeof referencesArr === 'string') {
|
|
||||||
refArray.push(referencesArr);
|
|
||||||
} else {
|
|
||||||
refArray = referencesArr;
|
|
||||||
}
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
references.push(refItem);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (references.length !== 0 && references[0].spanID.length !== 0) {
|
return refObject;
|
||||||
if (references[0].refType === 'CHILD_OF') {
|
});
|
||||||
const parentID = references[0].spanID;
|
};
|
||||||
// console.log(`In SpanTreeUtil: mapped_array[parentID] is ${mapped_array[parentID]}`);
|
|
||||||
|
// This getSpanTags is migrated from the previous implementation.
|
||||||
if (typeof mapped_array[parentID] !== 'undefined') {
|
const getSpanTags = (spanData: Span): { key: string; value: string }[] => {
|
||||||
// checking for undefined [10] issue
|
const tags = [];
|
||||||
mapped_array[parentID][10].push(push_object);
|
if (spanData[7] !== null && spanData[8] !== null) {
|
||||||
} else {
|
if (typeof spanData[7] === 'string' && typeof spanData[8] === 'string') {
|
||||||
// console.log(
|
tags.push({ key: spanData[7], value: spanData[8] });
|
||||||
// `In SpanTreeUtil: mapped_array[parentID] is undefined, parentID is ${parentID}`,
|
} else if (spanData[7].length > 0 && spanData[8].length > 0) {
|
||||||
// );
|
for (let j = 0; j < spanData[7].length; j += 1) {
|
||||||
// console.log(
|
tags.push({ key: spanData[7][j], value: spanData[8][j] });
|
||||||
// `In SpanTreeUtil: mapped_array[parentID] is undefined, mapped_array[parentID] is ${mapped_array[parentID]}`,
|
}
|
||||||
// );
|
}
|
||||||
}
|
}
|
||||||
}
|
return tags;
|
||||||
} else {
|
};
|
||||||
tree = push_object;
|
|
||||||
}
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
} // end of for loop
|
export const spanToTreeUtil = (inputSpanList: Span[]): ITraceForest => {
|
||||||
} // end of if(spans)
|
const spanList = cloneDeep(inputSpanList);
|
||||||
|
const traceIdSet: Set<string> = new Set();
|
||||||
return { ...tree };
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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: span[10].map((e) => {
|
||||||
|
return (
|
||||||
|
JSON.parse(decodeURIComponent((e as never) || ('{}' as never))) ||
|
||||||
|
({} as Record<string, unknown>)
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
references: spanReferences,
|
||||||
|
};
|
||||||
|
spanMap[span[1]] = spanObject;
|
||||||
|
});
|
||||||
|
|
||||||
|
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 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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
spanTree,
|
||||||
|
missingSpanTree,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user