feat: add span links support (#2415)

* feat: add span links support

* fix: handle an edge case

* chore: test is fixed

* chore: some of the refactoring is updated

---------

Co-authored-by: Palash Gupta <palashgdev@gmail.com>
This commit is contained in:
Vishal Sharma 2023-03-03 14:35:11 +05:30 committed by GitHub
parent b99d7009a1
commit 2a5cb78964
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 111 additions and 51 deletions

View File

@ -1,4 +1,6 @@
import { Input, Typography } from 'antd'; import { Input, List, Typography } from 'antd';
import ROUTES from 'constants/routes';
import { formUrlParams } from 'container/TraceDetail/utils';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ITraceTag } from 'types/api/trace/getTraceItem'; import { ITraceTag } from 'types/api/trace/getTraceItem';
@ -7,7 +9,12 @@ import { ModalText } from '..';
import { Container } from './styles'; import { Container } from './styles';
import Tag from './Tag'; import Tag from './Tag';
function Tags({ tags, onToggleHandler, setText }: TagsProps): JSX.Element { function Tags({
tags,
linkedSpans,
onToggleHandler,
setText,
}: TagsProps): JSX.Element {
const { t } = useTranslation(['traceDetails']); const { t } = useTranslation(['traceDetails']);
const [allRenderedTags, setAllRenderedTags] = useState(tags); const [allRenderedTags, setAllRenderedTags] = useState(tags);
const isSearchVisible = useMemo(() => tags.length > 5, [tags]); const isSearchVisible = useMemo(() => tags.length > 5, [tags]);
@ -16,6 +23,16 @@ function Tags({ tags, onToggleHandler, setText }: TagsProps): JSX.Element {
setAllRenderedTags(tags); setAllRenderedTags(tags);
}, [tags]); }, [tags]);
const getLink = useCallback(
(item: Record<string, string>) =>
`${ROUTES.TRACE}/${item.TraceId}${formUrlParams({
spanId: item.SpanId,
levelUp: 0,
levelDown: 0,
})}`,
[],
);
const onChangeHandler = useCallback( const onChangeHandler = useCallback(
(e: React.ChangeEvent<HTMLInputElement>): void => { (e: React.ChangeEvent<HTMLInputElement>): void => {
const { value } = e.target; const { value } = e.target;
@ -38,7 +55,6 @@ function Tags({ tags, onToggleHandler, setText }: TagsProps): JSX.Element {
onChange={onChangeHandler} onChange={onChangeHandler}
/> />
)} )}
{allRenderedTags.map((tag) => ( {allRenderedTags.map((tag) => (
<Tag <Tag
key={JSON.stringify(tag)} key={JSON.stringify(tag)}
@ -49,12 +65,24 @@ function Tags({ tags, onToggleHandler, setText }: TagsProps): JSX.Element {
}} }}
/> />
))} ))}
{linkedSpans && linkedSpans.length > 0 && (
<List
header={<Typography.Title level={5}>Linked Spans</Typography.Title>}
dataSource={linkedSpans}
renderItem={(item): JSX.Element => (
<List.Item>
<Typography.Link href={getLink(item)}>{item.SpanId}</Typography.Link>
</List.Item>
)}
/>
)}
</Container> </Container>
); );
} }
interface TagsProps extends CommonTagsProps { interface TagsProps extends CommonTagsProps {
tags: ITraceTag[]; tags: ITraceTag[];
linkedSpans?: Record<string, string>[];
} }
export interface CommonTagsProps { export interface CommonTagsProps {
@ -62,4 +90,8 @@ export interface CommonTagsProps {
setText: React.Dispatch<React.SetStateAction<ModalText>>; setText: React.Dispatch<React.SetStateAction<ModalText>>;
} }
Tags.defaultProps = {
linkedSpans: [],
};
export default Tags; export default Tags;

View File

@ -42,7 +42,7 @@ function SelectedSpanDetails(props: SelectedSpanDetailsProps): JSX.Element {
return <div />; return <div />;
} }
const { tags } = tree; const { tags, nonChildReferences } = tree;
return ( return (
<CardContainer> <CardContainer>
@ -83,7 +83,12 @@ function SelectedSpanDetails(props: SelectedSpanDetailsProps): JSX.Element {
<Tabs defaultActiveKey="1"> <Tabs defaultActiveKey="1">
<TabPane tab="Tags" key="1"> <TabPane tab="Tags" key="1">
<Tags onToggleHandler={onToggleHandler} setText={setText} tags={tags} /> <Tags
onToggleHandler={onToggleHandler}
setText={setText}
tags={tags}
linkedSpans={nonChildReferences}
/>
</TabPane> </TabPane>
<TabPane tab="Events" key="2"> <TabPane tab="Events" key="2">
<Events <Events

View File

@ -46,9 +46,10 @@ export interface ITraceTree {
hasError?: boolean; hasError?: boolean;
event?: ITraceEvents[]; event?: ITraceEvents[];
isMissing?: boolean; isMissing?: boolean;
childReferences?: Record<string, string>[];
nonChildReferences?: Record<string, string>[];
// For internal use // For internal use
isProcessed?: boolean; isProcessed?: boolean;
references?: Record<string, string>[];
} }
export interface ITraceTag { export interface ITraceTag {

View File

@ -5,10 +5,31 @@ Object {
"missingSpanTree": Array [], "missingSpanTree": Array [],
"spanTree": Array [ "spanTree": Array [
Object { Object {
"childReferences": Array [
Object {
"RefType": "CHILD_OF",
"SpanId": "",
"TraceId": "0000000000000000span_1",
},
],
"children": Array [ "children": Array [
Object { Object {
"childReferences": Array [
Object {
"RefType": "CHILD_OF",
"SpanId": "span_1",
"TraceId": "0000000000000000span_1",
},
],
"children": Array [ "children": Array [
Object { Object {
"childReferences": Array [
Object {
"RefType": "CHILD_OF",
"SpanId": "span_2",
"TraceId": "0000000000000000span_1",
},
],
"children": Array [], "children": Array [],
"event": Array [ "event": Array [
Object { Object {
@ -25,13 +46,7 @@ Object {
"id": "span_3", "id": "span_3",
"isProcessed": true, "isProcessed": true,
"name": "HTTP GET SPAN 3", "name": "HTTP GET SPAN 3",
"references": Array [ "nonChildReferences": Array [],
Object {
"RefType": "CHILD_OF",
"SpanId": "span_2",
"TraceId": "0000000000000000span_1",
},
],
"serviceColour": "", "serviceColour": "",
"serviceName": "frontend", "serviceName": "frontend",
"startTime": 1657275433246, "startTime": 1657275433246,
@ -60,13 +75,7 @@ Object {
"id": "span_2", "id": "span_2",
"isProcessed": true, "isProcessed": true,
"name": "HTTP GET SPAN 2", "name": "HTTP GET SPAN 2",
"references": Array [ "nonChildReferences": Array [],
Object {
"RefType": "CHILD_OF",
"SpanId": "span_1",
"TraceId": "0000000000000000span_1",
},
],
"serviceColour": "", "serviceColour": "",
"serviceName": "frontend", "serviceName": "frontend",
"startTime": 1657275433246, "startTime": 1657275433246,
@ -94,13 +103,7 @@ Object {
"hasError": false, "hasError": false,
"id": "span_1", "id": "span_1",
"name": "HTTP GET SPAN 1", "name": "HTTP GET SPAN 1",
"references": Array [ "nonChildReferences": Array [],
Object {
"RefType": "CHILD_OF",
"SpanId": "",
"TraceId": "0000000000000000span_1",
},
],
"serviceColour": "", "serviceColour": "",
"serviceName": "frontend", "serviceName": "frontend",
"startTime": 1657275433246, "startTime": 1657275433246,
@ -123,6 +126,13 @@ Object {
Object { Object {
"children": Array [ "children": Array [
Object { Object {
"childReferences": Array [
Object {
"RefType": "CHILD_OF",
"SpanId": "span_2",
"TraceId": "0000000000000000span_1",
},
],
"children": Array [], "children": Array [],
"event": Array [ "event": Array [
Object { Object {
@ -139,13 +149,7 @@ Object {
"id": "span_3", "id": "span_3",
"isProcessed": true, "isProcessed": true,
"name": "HTTP GET SPAN 3", "name": "HTTP GET SPAN 3",
"references": Array [ "nonChildReferences": Array [],
Object {
"RefType": "CHILD_OF",
"SpanId": "span_2",
"TraceId": "0000000000000000span_1",
},
],
"serviceColour": "", "serviceColour": "",
"serviceName": "frontend", "serviceName": "frontend",
"startTime": 1657275433246, "startTime": 1657275433246,
@ -172,6 +176,13 @@ Object {
], ],
"spanTree": Array [ "spanTree": Array [
Object { Object {
"childReferences": Array [
Object {
"RefType": "CHILD_OF",
"SpanId": "",
"TraceId": "0000000000000000span_1",
},
],
"children": Array [], "children": Array [],
"event": Array [ "event": Array [
Object { Object {
@ -187,13 +198,7 @@ Object {
"hasError": false, "hasError": false,
"id": "span_1", "id": "span_1",
"name": "HTTP GET SPAN 1", "name": "HTTP GET SPAN 1",
"references": Array [ "nonChildReferences": Array [],
Object {
"RefType": "CHILD_OF",
"SpanId": "",
"TraceId": "0000000000000000span_1",
},
],
"serviceColour": "", "serviceColour": "",
"serviceName": "frontend", "serviceName": "frontend",
"startTime": 1657275433246, "startTime": 1657275433246,

View File

@ -4,8 +4,20 @@ import { ITraceForest, ITraceTree, Span } from 'types/api/trace/getTraceItem';
const getSpanReferences = ( const getSpanReferences = (
rawReferences: string[] = [], rawReferences: string[] = [],
): Record<string, string>[] => isChildReference: boolean,
rawReferences.map((rawRef) => { ): Record<string, string>[] => {
let filteredReferences = [];
if (isChildReference) {
filteredReferences = rawReferences.filter((value) =>
value.includes('CHILD_OF'),
);
} else {
filteredReferences = rawReferences.filter(
(value) => !value.includes('CHILD_OF'),
);
}
return filteredReferences.map((rawRef) => {
const refObject: Record<string, string> = {}; const refObject: Record<string, string> = {};
rawRef rawRef
.replaceAll('{', '') .replaceAll('{', '')
@ -19,6 +31,7 @@ const getSpanReferences = (
return refObject; return refObject;
}); });
};
// This getSpanTags is migrated from the previous implementation. // This getSpanTags is migrated from the previous implementation.
const getSpanTags = (spanData: Span): { key: string; value: string }[] => { const getSpanTags = (spanData: Span): { key: string; value: string }[] => {
@ -41,7 +54,7 @@ export const spanToTreeUtil = (inputSpanList: Span[]): ITraceForest => {
const traceIdSet: Set<string> = new Set(); const traceIdSet: Set<string> = new Set();
const spanMap: Record<string, ITraceTree> = {}; const spanMap: Record<string, ITraceTree> = {};
const createTarceRootSpan = ( const createTraceRootSpan = (
spanReferences: Record<string, string>[], spanReferences: Record<string, string>[],
): void => { ): void => {
spanReferences.forEach(({ SpanId, TraceId }) => { spanReferences.forEach(({ SpanId, TraceId }) => {
@ -64,7 +77,8 @@ export const spanToTreeUtil = (inputSpanList: Span[]): ITraceForest => {
}; };
spanList.forEach((span) => { spanList.forEach((span) => {
const spanReferences = getSpanReferences(span[9] as string[]); const childReferences = getSpanReferences(span[9] as string[], true);
const nonChildReferences = getSpanReferences(span[9] as string[], false);
const spanObject = { const spanObject = {
id: span[1], id: span[1],
name: span[4], name: span[4],
@ -76,16 +90,17 @@ export const spanToTreeUtil = (inputSpanList: Span[]): ITraceForest => {
serviceName: span[3], serviceName: span[3],
hasError: !!span[11], hasError: !!span[11],
serviceColour: '', serviceColour: '',
event: span[10].map((e) => JSON.parse(e || '{}') || {}), event: span[10]?.map((e) => JSON.parse(e || '{}') || {}),
references: spanReferences, childReferences,
nonChildReferences,
}; };
spanMap[span[1]] = spanObject; spanMap[span[1]] = spanObject;
}); });
for (const [, spanData] of Object.entries(spanMap)) { for (const [, spanData] of Object.entries(spanMap)) {
if (spanData.references) { if (spanData.childReferences) {
createTarceRootSpan(spanData.references); createTraceRootSpan(spanData.childReferences);
spanData.references.forEach(({ SpanId: parentSpanId }) => { spanData.childReferences.forEach(({ SpanId: parentSpanId }) => {
if (spanMap[parentSpanId]) { if (spanMap[parentSpanId]) {
spanData.isProcessed = true; spanData.isProcessed = true;
spanMap[parentSpanId].children.push(spanData); spanMap[parentSpanId].children.push(spanData);
@ -103,7 +118,9 @@ export const spanToTreeUtil = (inputSpanList: Span[]): ITraceForest => {
const missingSpanTree: ITraceTree[] = []; const missingSpanTree: ITraceTree[] = [];
const referencedTraceIds: string[] = Array.from(traceIdSet); const referencedTraceIds: string[] = Array.from(traceIdSet);
Object.keys(spanMap).forEach((spanId) => { Object.keys(spanMap).forEach((spanId) => {
const isRoot = spanMap[spanId].references?.some((refs) => refs.SpanId === ''); const isRoot = spanMap[spanId].childReferences?.some(
(refs) => refs.SpanId === '',
);
if (isRoot) { if (isRoot) {
spanTree.push(spanMap[spanId]); spanTree.push(spanMap[spanId]);
return; return;