feat: gantt charts for spans (#184)

* feat: create a base component for trace gantt chart

* fix: max and min calc

* fix: focus on selected paths

* fix: build issue

* fix: convert duration to ms

* fix: gantt chart cells margin left

* feat: sorted data by startTime

* feat: update layout and add select functionality to table

* feat: add UI and functionality

* feat: make row clickable in traces, show tags on gant chart click and some fixes

* feat: sort flamegraph and show tags on row click on gantt chart

* feat: change table type to radio and disable parent selection

* fix: left padding of gantt chart lines

* fix: line chart duration

* fix: sorting flame graph

* fix: reset zoom on flame graph

* fix: expand children on row click, show tags on page load, default expand on page load

* style(gantt-chart): make gantt chart buttons & tags sticky

* style(gant-chart): margin bottom in table & padding of gant

* feat: update content on trace list
This commit is contained in:
NIDHI TANDON 2021-06-24 22:28:32 +05:30 committed by GitHub
parent 59749d0576
commit bf2002d6a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 637 additions and 87 deletions

View File

@ -135,6 +135,7 @@
"@babel/preset-env": "^7.12.17",
"@babel/preset-react": "^7.12.13",
"@babel/preset-typescript": "^7.12.17",
"@types/lodash-es": "^4.17.4",
"autoprefixer": "^9.0.0",
"babel-plugin-styled-components": "^1.12.0",
"compression-webpack-plugin": "^8.0.0",
@ -147,6 +148,7 @@
"husky": "4.3.8",
"less-plugin-npm-import": "^2.1.0",
"lint-staged": "10.5.3",
"lodash-es": "^4.17.21",
"prettier": "2.2.1",
"react-hot-loader": "^4.13.0",
"react-is": "^17.0.1",

View File

@ -1,47 +1,94 @@
import React from "react";
import { Card, Tabs } from "antd";
import { Card, Space, Tabs, Typography } from "antd";
import styled from "styled-components";
import { pushDStree } from "../../store/actions";
const { TabPane } = Tabs;
interface spanTagItem {
key: string;
type: string;
value: string;
}
const { Text } = Typography;
interface SelectedSpanDetailsProps {
clickedSpanTags: spanTagItem[];
data: pushDStree
}
const Title = styled(Text)`
color: "#2D9CDB",
fontSize: '12px',
`;
const SelectedSpanDetails = (props: SelectedSpanDetailsProps) => {
const callback = (key: any) => {};
let spanTags = props.data.tags;
let service = props.data?.name?.split(":")[0];
let operation = props.data?.name?.split(":")[1];
return (
<Card style={{ height: 320 }}>
<Tabs defaultActiveKey="1" onChange={callback}>
<Card style={{ border: "none", background: "transparent", padding: 0 }} bodyStyle={{ padding: 0 }}>
<Space direction="vertical">
<strong> Details for selected Span </strong>
<Space direction="vertical" size={2}>
<Text style={{ marginTop: "18px" }}>
Service
</Text>
<Title style={{ color: "#2D9CDB", fontSize: "12px" }}>
{service}
</Title>
</Space>
<Space direction="vertical" size={2}>
<Text>
Operation
</Text>
<Text style={{ color: "#2D9CDB", fontSize: "12px" }}>
{operation}
</Text>
</Space>
</Space>
<Tabs defaultActiveKey="1">
<TabPane tab="Tags" key="1">
<strong> Details for selected Span </strong>
{props.clickedSpanTags.map((tags, index) => (
<li
key={index}
style={{ color: "grey", fontSize: "13px", listStyle: "none" }}
>
<span className="mr-1">{tags.key}</span>:
<span className="ml-1">
{tags.key === "error" ? "true" : tags.value}
</span>
</li>
))}{" "}
{spanTags && spanTags.map((tags, index) => {
return (
<>
{tags.value && (
<>
<Text style={{ color: "#BDBDBD", fontSize: "12px", marginBottom: "8px" }}>
{tags.key}
</Text>
<div style={{
background: "#4F4F4F",
color: "#2D9CDB",
fontSize: "12px",
padding: "6px 8px",
wordBreak: "break-all",
marginBottom: "16px",
}}>
{tags.key === "error" ? "true" : tags.value}
</div>
</>
)}
</>
);
})}
</TabPane>
<TabPane tab="Errors" key="2">
{props.clickedSpanTags
{spanTags && spanTags
.filter((tags) => tags.key === "error")
.map((error) => (
<div className="ml-5">
<p style={{ color: "grey", fontSize: "10px" }}>
<span className="mr-1">{error.key}</span>:
<span className="ml-1">true</span>
</p>
</div>
<>
<Text style={{ color: "#BDBDBD", fontSize: "12px", marginBottom: "8px" }}>
{error.key}
</Text>
<div style={{
background: "#4F4F4F",
color: "#2D9CDB",
fontSize: "12px",
padding: "6px 8px",
wordBreak: "break-all",
marginBottom: "16px",
}}>
true
</div>
</>
))}
</TabPane>
</Tabs>

View File

@ -0,0 +1,41 @@
// Doing DFS traversal on the tree
// resultCount : how many entries you want. where -1 means all possible entries.
// func(obj) : takes one element of the data structure and returns true if need to select or not
// program to implement stack data structure
import { isEmpty } from "lodash-es";
const getTreeData = (tree, callback, resultCount = -1) => {
if (resultCount === 0 || isEmpty(tree) || tree.id === "empty") return null;
let data = tree;
let result = [];
let stk = [];
stk.push(data);
while (!isEmpty(stk)) {
let x = stk[stk.length - 1];
// marked means seeing the node for the second time.
if (x.marked) {
delete x.marked;
stk.pop();
x.map((item) => {
if (callback(item) === true) {
result.push(item);
if (resultCount !== -1 && result.length === resultCount) return result;
}
});
} else {
x.marked = true;
x.map((item) => {
if (item.children.length > 0) {
stk.push(item.children);
}
});
}
}
return result;
};
export default getTreeData;

View File

@ -0,0 +1,13 @@
.row-styles{
cursor: pointer
}
.hide{
display: none;
}
.ant-tabs-nav-list{
justify-content: space-between;
width: 100%;
}
.ant-table-body table {
margin-bottom: 64px;
}

View File

@ -0,0 +1,335 @@
import React, { useEffect, useRef, useState } from "react";
import { Table, Progress, Tabs, Button, Row, Col } from "antd";
import "./TraceGanttChart.css";
import { max, isEmpty, has } from "lodash-es";
import styled from "styled-components";
import getTreeData from "Src/modules/Traces/TraceGantChartHelpers";
import { pushDStree } from "../../store/actions";
const { TabPane } = Tabs;
const StyledButton = styled(Button)`
border: 1px solid #e0e0e0;
border-radius: 4px;
color: #f2f2f2;
font-size: 14px;
line-height: 20px;
`;
interface TraceGanttChartProps {
treeData: pushDStree;
clickedSpan: pushDStree;
selectedSpan: pushDStree;
resetZoom: () => {};
setSpanTagsInfo: () => {};
}
const TraceGanttChart = ({
treeData,
clickedSpan,
selectedSpan,
resetZoom,
setSpanTagsInfo,
}: TraceGanttChartProps) => {
let checkStrictly = true;
const [selectedRows, setSelectedRows] = useState([]);
const [clickedSpanData, setClickedSpanData] = useState(clickedSpan);
const [defaultExpandedRows, setDefaultExpandedRows] = useState([]);
const [sortedTreeData, setSortedTreeData] = useState(treeData);
const [isReset, setIsReset] = useState(false);
const [rowId, setRowId] = useState(0);
const [tabsContainerWidth, setTabsContainerWidth] = useState(0);
const tableRef = useRef("");
let tabsContainer = document.querySelector(
"#collapsable .ant-tabs-nav-list",
);
let tabs = document.querySelectorAll("#collapsable .ant-tabs-tab");
const { id } = treeData || "id";
let maxGlobal = 0;
let minGlobal = 0;
let medianGlobal = 0;
let endTimeArray: [] = [];
useEffect(() => {
if (id !== "empty") {
setSortedTreeData(treeData);
if (clickedSpan) {
setClickedSpanData(clickedSpan);
}
setTabsContainerWidth(tabsContainer?.offsetWidth)
}
// handleScroll(selectedSpan?.id);
}, [sortedTreeData, treeData, clickedSpan]);
useEffect(() => {
if (
!isEmpty(clickedSpanData) &&
clickedSpan &&
!selectedRows.includes(clickedSpan.id)
&& !isReset
) {
setSelectedRows([clickedSpan.id]);
getParentKeys(clickedSpan);
let keys = [clickedSpan?.id, ...parentKeys];
// setDefaultExpandedRows(keys)
handleFocusOnSelectedPath("", [clickedSpan.id], clickedSpan);
}
}, [clickedSpan, selectedRows, isReset, clickedSpanData]);
let parentKeys = [];
let childrenKeys = [];
const getParentKeys = (obj) => {
if (has(obj, "parent")) {
parentKeys.push(obj.parent.id);
getParentKeys(obj.parent);
}
};
const getChildrenKeys = (obj) =>{
if (has(obj, "children")) {
childrenKeys.push(obj.id);
if(!isEmpty(obj.children)){
obj.children.map((item)=>{
getChildrenKeys(item);
})
}
}
}
useEffect(() => {
if (!isEmpty(selectedSpan) && isEmpty(clickedSpan)) {
getParentKeys(selectedSpan);
let keys = [selectedSpan?.id, ...parentKeys];
setDefaultExpandedRows(keys);
setSelectedRows([selectedSpan.id, clickedSpan]);
// setSpanTagsInfo({data: selectedSpan})
} else {
setSelectedRows([treeData?.[0]?.id]);
setDefaultExpandedRows([treeData?.[0]?.id]);
// /.setSpanTagsInfo({data: treeData?.[0]})
}
}, [selectedSpan, treeData]);
const getMaxEndTime = (treeData) => {
if (treeData.length > 0) {
if (treeData?.id !== "empty") {
return Array.from(treeData).map((item, key) => {
if (!isEmpty(item.children)) {
endTimeArray.push(item.time / 1000000 + item.startTime);
getMaxEndTime(item.children);
} else {
endTimeArray.push(item.time / 1000000 + item.startTime);
}
});
}
}
};
if (id !== "empty") {
getMaxEndTime(treeData);
maxGlobal = max(endTimeArray);
minGlobal = treeData?.[0]?.startTime;
medianGlobal = (minGlobal + maxGlobal) / 2;
}
/*
timeDiff = maxGlobal - startTime
totalTime = maxGlobal - minGlobal
totalWidth = width of container
*/
const getPaddingLeft = (timeDiff, totalTime, totalWidth) => {
return ((timeDiff / totalTime) * totalWidth ).toFixed(0);
};
let tabMinVal = 0;
let tabMedianVal = (medianGlobal - minGlobal).toFixed(0);
let tabMaxVal = (maxGlobal - minGlobal).toFixed(0);
const columns = [
{
title: "",
dataIndex: "name",
key: "name",
},
{
title: (
<Tabs>
<TabPane tab={tabMinVal + "ms"} key="1" />
<TabPane tab={tabMedianVal + "ms"} key="2" />
<TabPane tab={tabMaxVal + "ms"} key="3" />
</Tabs>
),
dataIndex: "trace",
name: "trace",
render: (_, record: pushDStree) => {
let widths = [];
let length;
if (widths.length < tabs.length) {
Array.from(tabs).map((tab) => {
widths.push(tab.offsetWidth);
});
}
let paddingLeft = 0;
let startTime = parseInt(record.startTime);
let duration = parseInt((record.time / 1000000).toFixed(2));
paddingLeft = parseInt(getPaddingLeft(startTime - minGlobal, maxGlobal - minGlobal, tabsContainerWidth));
let textPadding = paddingLeft;
if(paddingLeft === tabsContainerWidth - 20){
textPadding = tabsContainerWidth - 40
}
length = ((duration / (maxGlobal - startTime)) * 100).toFixed(
2,
);
return (
<>
<div style={{ paddingLeft: textPadding + "px" }}>{duration}ms</div>
<Progress
percent={length}
showInfo={false}
style={{ paddingLeft: paddingLeft + "px" }}
/>
</>
);
},
},
];
const handleFocusOnSelectedPath = (event, selectedRowsList = selectedRows) => {
if (!isEmpty(selectedRowsList)) {
let node: pushDStree = getTreeData(
treeData,
(item: pushDStree) => item.id === selectedRowsList[0],
1,
);
setSpanTagsInfo({ data: node[0] });
getParentKeys(node[0]);
getChildrenKeys(node[0]);
let rows = document.querySelectorAll("#collapsable table tbody tr");
Array.from(rows).map((row) => {
let attribKey = row.getAttribute("data-row-key");
if (!selectedRowsList.includes(attribKey)) {
row.classList.add("hide");
}
});
setDefaultExpandedRows([...parentKeys, ...childrenKeys]);
}
};
const handleResetFocus = () => {
let rows = document.querySelectorAll("#collapsable table tbody tr");
Array.from(rows).map((row) => {
row.classList.remove("hide");
});
resetZoom(true);
};
const handleScroll = (id) => {
let rows = document.querySelectorAll("#collapsable table tbody tr");
const table = document.querySelectorAll("#collapsable table");
Array.from(rows).map((row) => {
let attribKey = row.getAttribute("data-row-key");
if (id === attribKey) {
let scrollValue = row.offsetTop;
table[1].scrollTop = scrollValue;
}
});
};
const rowSelection = {
onChange: (selectedRowKeys: []) => {
setSelectedRows(selectedRowKeys);
setClickedSpanData({});
if (isEmpty(selectedRowKeys)) {
setIsReset(true);
} else {
setIsReset(false);
}
},
onSelect:(record)=>{
handleRowOnClick(record)
},
selectedRowKeys: selectedRows,
};
const handleRowOnClick = (record) => {
setRowId(record.id);
let node: pushDStree = getTreeData(
treeData,
(item: pushDStree) => item.id === record.id,
1,
);
setSpanTagsInfo({ data: node[0] });
const selectedRowKeys = selectedRows;
if (selectedRowKeys.indexOf(record.id) >= 0) {
selectedRowKeys.splice(selectedRowKeys.indexOf(record.key), 1);
} else {
selectedRowKeys.push(record.id);
}
setSelectedRows([record.id]);
};
const handleOnExpandedRowsChange = (item) => {
setDefaultExpandedRows(item);
};
return (
<>
{id !== "empty" && (
<>
<Row
justify="end"
gutter={32}
style={{
marginBottom: "24px",
}}
>
<Col>
<StyledButton onClick={handleFocusOnSelectedPath}>
{" "}
Focus on selected path{" "}
</StyledButton>
</Col>
<Col>
<StyledButton onClick={handleResetFocus}> Reset Focus </StyledButton>
</Col>
</Row>
<Table
refs={tableRef}
hideSelectAll={true}
columns={columns}
rowSelection={{ ...rowSelection, checkStrictly, type:'radio' }}
dataSource={sortedTreeData}
rowKey="id"
sticky={true}
onRow={(record, rowIndex) => {
return {
onClick: () => handleRowOnClick(record, rowIndex), // click row
};
}}
expandedRowKeys={defaultExpandedRows}
onExpandedRowsChange={handleOnExpandedRowsChange}
pagination={false}
scroll={{ y: 540}}
rowClassName="row-styles"
filterMultiple={false}
/>
</>
)}
</>
);
};
export default TraceGanttChart;

View File

@ -1,29 +1,65 @@
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { useParams, useLocation } from "react-router-dom";
import { flamegraph } from "d3-flame-graph";
import { connect } from "react-redux";
import { Card, Button, Row, Col, Space } from "antd";
import { Card, Row, Col, Space, Affix } from "antd";
import * as d3 from "d3";
import * as d3Tip from "d3-tip";
//import * as d3Tip from 'd3-tip';
// PNOTE - uninstall @types/d3-tip. issues with importing d3-tip https://github.com/Caged/d3-tip/issues/181
import "./TraceGraph.css";
import { spanToTreeUtil } from "../../utils/spanToTree";
import { fetchTraceItem, spansWSameTraceIDResponse } from "../../store/actions";
import {
fetchTraceItem,
pushDStree,
spansWSameTraceIDResponse,
} from "../../store/actions";
import { StoreState } from "../../store/reducers";
import SelectedSpanDetails from "./SelectedSpanDetails";
import TraceGanttChart from "./TraceGanttChart";
import styled from "styled-components";
import { isEmpty, sortBy } from "lodash-es";
interface TraceGraphProps {
traceItem: spansWSameTraceIDResponse;
fetchTraceItem: Function;
}
const TraceGanttChartContainer = styled(Card)`
background: #333333;
border-radius: 5px;
`;
const _TraceGraph = (props: TraceGraphProps) => {
let location = useLocation();
const spanId = location?.state?.spanId;
const params = useParams<{ id?: string }>();
const [clickedSpanTags, setClickedSpanTags] = useState([]);
const [clickedSpanTags, setClickedSpanTags] = useState<pushDStree>([]);
const [selectedSpan, setSelectedSpan] = useState({});
const [clickedSpan, setClickedSpan] = useState(null);
const [resetZoom, setResetZoom] = useState(false);
const [sortedTreeData, setSortedTreeData] = useState<pushDStree>([]);
let sortedData = {};
const getSortedData = (treeData: [pushDStree], parent = {}) => {
if (!isEmpty(treeData)) {
if (treeData[0].id !== "empty") {
return Array.from(treeData).map((item, key) => {
if (!isEmpty(item.children)) {
getSortedData(item.children, item);
sortedData = sortBy(item.children, (i) => i.startTime);
treeData[key].children = sortedData;
}
if (!isEmpty(parent)) {
treeData[key].parent = parent;
}
return treeData;
});
}
return treeData;
}
};
const tree = spanToTreeUtil(props.traceItem[0].events);
useEffect(() => {
//sets span width based on value - which is mapped to duration
@ -31,16 +67,31 @@ const _TraceGraph = (props: TraceGraphProps) => {
}, []);
useEffect(() => {
if (props.traceItem || resetZoom) {
const tree = spanToTreeUtil(props.traceItem[0].events);
if (props.traceItem) {
let sortedData = getSortedData([tree]);
setSortedTreeData(sortedData?.[0]);
getSpanInfo(sortedData?.[0], spanId);
// This is causing element to change ref. Can use both useRef or this approach.
d3.select("#chart").datum(tree).call(chart);
setResetZoom(false);
d3.select("#chart").datum(tree).call(chart).sort(item=>item.startTime);
}
}, [props.traceItem, resetZoom]);
}, [props.traceItem]);
// if this monitoring of props.traceItem.data is removed then zoom on click doesn't work
// Doesn't work if only do initial check, works if monitor an element - as it may get updated in sometime
useEffect(() => {
if(!isEmpty(sortedTreeData) && sortedTreeData?.id !== "empty" && isEmpty(clickedSpanTags)) {
setClickedSpanTags(sortedTreeData?.[0]);
}
}, [sortedTreeData]);
useEffect(() => {
if (resetZoom) {
// This is causing element to change ref. Can use both useRef or this approach.
d3.select("#chart").datum(tree).call(chart).sort(item=>item.startTime);
setResetZoom(false);
}
}, [resetZoom]);
const tip = d3Tip
.default()
.attr("class", "d3-tip")
@ -49,30 +100,59 @@ const _TraceGraph = (props: TraceGraphProps) => {
});
const onClick = (z: any) => {
setClickedSpanTags(z.data.tags);
setClickedSpanTags(z.data);
setClickedSpan(z.data);
setSelectedSpan([]);
console.log(`Clicked on ${z.data.name}, id: "${z.id}"`);
};
const setSpanTagsInfo = (z: any) => {
setClickedSpanTags(z.data);
};
const getSpanInfo = (data: [pushDStree], spanId: string): void => {
if (resetZoom) {
setSelectedSpan({});
return;
}
if (data?.[0]?.id !== "empty") {
Array.from(data).map((item) => {
if (item.id === spanId) {
setSelectedSpan(item);
setClickedSpanTags(item);
return item;
} else if (!isEmpty(item.children)) {
getSpanInfo(item.children, spanId);
}
});
}
};
const chart = flamegraph()
.cellHeight(18)
.transitionDuration(500)
.inverted(true)
.tooltip(tip)
.minFrameSize(10)
.minFrameSize(4)
.elided(false)
.differential(false)
.sort(true)
.sort((item) => item.startTime)
//Use self value=true when we're using not using aggregated option, Which is not our case.
// In that case it's doing step function sort of stuff thru computation.
// Source flamegraph.js line 557 and 573.
// .selfValue(true)
.onClick(onClick);
.onClick(onClick)
.width(800);
const handleResetZoom = (value) => {
setResetZoom(value);
};
return (
<Row gutter={{ xs: 8, sm: 16, md: 24, lg: 32 }}>
<Col md={24} sm={24}>
<Col md={18} sm={18}>
<Space direction="vertical" size="middle" style={{ width: "100%" }}>
<Card bodyStyle={{ padding: 80 }} style={{ height: 320 }}>
<Card bodyStyle={{ padding: 24 }} style={{ height: 320 }}>
<div
style={{
display: "flex",
@ -84,20 +164,27 @@ const _TraceGraph = (props: TraceGraphProps) => {
<div style={{ textAlign: "center" }}>
Trace Graph component ID is {params.id}{" "}
</div>
<Button
type="primary"
onClick={setResetZoom.bind(this, true)}
style={{ width: 160 }}
>
Reset Zoom
</Button>
<div id="chart" style={{ fontSize: 12, marginTop: 20 }}></div>
</div>
</Card>
<SelectedSpanDetails clickedSpanTags={clickedSpanTags} />
<Affix offsetTop={24}>
<TraceGanttChartContainer id={"collapsable"}>
<TraceGanttChart
treeData={sortedTreeData}
clickedSpan={clickedSpan}
selectedSpan={selectedSpan}
resetZoom={handleResetZoom}
setSpanTagsInfo={setSpanTagsInfo}
/>
</TraceGanttChartContainer>
</Affix>
</Space>
</Col>
<Col md={6} sm={6}>
<Affix offsetTop={24}>
<SelectedSpanDetails data={clickedSpanTags} />
</Affix>
</Col>
</Row>
);
};

View File

@ -1,6 +1,6 @@
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { NavLink } from "react-router-dom";
import { useHistory} from "react-router-dom";
import { Space, Table } from "antd";
import ROUTES from "Src/constants/routes";
@ -10,9 +10,14 @@ import { isOnboardingSkipped } from "../../utils/app";
import moment from "moment";
import styled from "styled-components";
const StyledTable = styled(Table)`
cursor: pointer;
`
const TraceHeader = styled.div`
margin: 16px 0;
`;
interface TraceListProps {
traces: traceResponseNew;
fetchTraces: Function;
@ -25,26 +30,17 @@ interface TableDataSourceItem {
operationName: string;
startTime: number;
duration: number;
service: string;
}
const _TraceList = (props: TraceListProps) => {
// PNOTE (TO DO) - Currently this use of useEffect gives warning. May need to memoise fetchtraces - https://stackoverflow.com/questions/55840294/how-to-fix-missing-dependency-warning-when-using-useeffect-react-hook
let history = useHistory();
useEffect(() => {
props.fetchTraces();
}, []);
// PNOTE - code snippet -
// renderList(): JSX.Element[] {
// return this.props.todos.map((todo: Todo) => {
// return (
// <div onClick={() => this.onTodoClick(todo.id)} key={todo.id}>
// {todo.title}
// </div>
// );
// });
// }
const columns: any = [
{
title: "Start Time",
@ -57,12 +53,9 @@ const _TraceList = (props: TraceListProps) => {
// new Date() assumes input in milliseconds. Start Time stamp returned by druid api for span list is in ms
},
{
title: "Duration (in ms)",
dataIndex: "duration",
key: "duration",
sorter: (a: any, b: any) => a.duration - b.duration,
sortDirections: ["descend", "ascend"],
render: (value: number) => (value / 1000000).toFixed(2),
title: "Service",
dataIndex: "service",
key: "service",
},
{
title: "Operation",
@ -70,13 +63,12 @@ const _TraceList = (props: TraceListProps) => {
key: "operationName",
},
{
title: "TraceID",
dataIndex: "traceid",
key: "traceid",
render: (text: string) => (
<NavLink to={ROUTES.TRACES + "/" + text}>{text.slice(-16)}</NavLink>
),
//only last 16 chars have traceID, druid makes it 32 by adding zeros
title: "Duration (in ms)",
dataIndex: "duration",
key: "duration",
sorter: (a: any, b: any) => a.duration - b.duration,
sortDirections: ["descend", "ascend"],
render: (value: number) => (value / 1000000).toFixed(2),
},
];
@ -87,8 +79,6 @@ const _TraceList = (props: TraceListProps) => {
typeof props.traces[0] !== "undefined" &&
props.traces[0].events.length > 0
) {
//PNOTE - Template literal should be wrapped in curly braces for it to be evaluated
props.traces[0].events.map(
(item: (number | string | string[] | pushDStree[])[], index) => {
if (
@ -96,7 +86,8 @@ const _TraceList = (props: TraceListProps) => {
typeof item[4] === "string" &&
typeof item[6] === "string" &&
typeof item[1] === "string" &&
typeof item[2] === "string"
typeof item[2] === "string" &&
typeof item[3] === "string"
)
dataSource.push({
startTime: item[0],
@ -105,13 +96,30 @@ const _TraceList = (props: TraceListProps) => {
spanid: item[1],
traceid: item[2],
key: index.toString(),
service: item[3],
});
},
);
//antd table in typescript - https://codesandbox.io/s/react-typescript-669cv
return <Table dataSource={dataSource} columns={columns} size="middle" />;
return <StyledTable
dataSource={dataSource}
columns={columns}
size="middle"
rowClassName=""
onRow={(record) => ({
onClick: () => {
history.push({
pathname: ROUTES.TRACES + "/" + record.traceid,
state: {
spanId: record.spanid,
},
});
}
})}
/>
;
} else {
if (isOnboardingSkipped()) {
return (
@ -136,7 +144,7 @@ const _TraceList = (props: TraceListProps) => {
return (
<div>
<TraceHeader>List of traces with spanID</TraceHeader>
<TraceHeader>List of filtered spans</TraceHeader>
<div>{renderTraces()}</div>
</div>
);

View File

@ -2136,6 +2136,18 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
"@types/lodash-es@^4.17.4":
version "4.17.4"
resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.4.tgz#b2e440d2bf8a93584a9fd798452ec497986c9b97"
integrity sha512-BBz79DCJbD2CVYZH67MBeHZRX++HF+5p8Mo5MzjZi64Wac39S3diedJYHZtScbRVf4DjZyN6LzA0SB0zy+HSSQ==
dependencies:
"@types/lodash" "*"
"@types/lodash@*":
version "4.14.170"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.170.tgz#0d67711d4bf7f4ca5147e9091b847479b87925d6"
integrity sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q==
"@types/minimatch@*":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
@ -8738,6 +8750,11 @@ locate-path@^6.0.0:
dependencies:
p-locate "^5.0.0"
lodash-es@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
lodash._reinterpolate@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"