From bf2002d6a2c0d54c0c54076bc766a8d860b4738c Mon Sep 17 00:00:00 2001 From: NIDHI TANDON Date: Thu, 24 Jun 2021 22:28:32 +0530 Subject: [PATCH] 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 --- frontend/package.json | 2 + .../modules/Traces/SelectedSpanDetails.tsx | 105 ++++-- .../modules/Traces/TraceGantChartHelpers.js | 41 +++ .../src/modules/Traces/TraceGanttChart.css | 13 + .../src/modules/Traces/TraceGanttChart.tsx | 335 ++++++++++++++++++ .../modules/Traces/TraceGanttChartHelpers.js | 0 frontend/src/modules/Traces/TraceGraph.tsx | 143 ++++++-- frontend/src/modules/Traces/TraceList.tsx | 68 ++-- frontend/yarn.lock | 17 + 9 files changed, 637 insertions(+), 87 deletions(-) create mode 100644 frontend/src/modules/Traces/TraceGantChartHelpers.js create mode 100644 frontend/src/modules/Traces/TraceGanttChart.css create mode 100644 frontend/src/modules/Traces/TraceGanttChart.tsx create mode 100644 frontend/src/modules/Traces/TraceGanttChartHelpers.js diff --git a/frontend/package.json b/frontend/package.json index 6f0eb0f709c8..834f8355cdb1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/src/modules/Traces/SelectedSpanDetails.tsx b/frontend/src/modules/Traces/SelectedSpanDetails.tsx index dbd4d664cf0b..96a350e7d21e 100644 --- a/frontend/src/modules/Traces/SelectedSpanDetails.tsx +++ b/frontend/src/modules/Traces/SelectedSpanDetails.tsx @@ -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 ( - - + + + + Details for selected Span + + + Service + + + {service} + + + + + Operation + + + {operation} + + + + - Details for selected Span - {props.clickedSpanTags.map((tags, index) => ( -
  • - {tags.key}: - - {tags.key === "error" ? "true" : tags.value} - -
  • - ))}{" "} + {spanTags && spanTags.map((tags, index) => { + return ( + <> + {tags.value && ( + <> + + {tags.key} + +
    + {tags.key === "error" ? "true" : tags.value} +
    + + )} + + ); + })}
    - {props.clickedSpanTags + {spanTags && spanTags .filter((tags) => tags.key === "error") .map((error) => ( -
    -

    - {error.key}: - true -

    -
    + <> + + {error.key} + +
    + true +
    + ))}
    diff --git a/frontend/src/modules/Traces/TraceGantChartHelpers.js b/frontend/src/modules/Traces/TraceGantChartHelpers.js new file mode 100644 index 000000000000..fb610b6c5fce --- /dev/null +++ b/frontend/src/modules/Traces/TraceGantChartHelpers.js @@ -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; diff --git a/frontend/src/modules/Traces/TraceGanttChart.css b/frontend/src/modules/Traces/TraceGanttChart.css new file mode 100644 index 000000000000..037b52b6516e --- /dev/null +++ b/frontend/src/modules/Traces/TraceGanttChart.css @@ -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; +} \ No newline at end of file diff --git a/frontend/src/modules/Traces/TraceGanttChart.tsx b/frontend/src/modules/Traces/TraceGanttChart.tsx new file mode 100644 index 000000000000..5687ae406927 --- /dev/null +++ b/frontend/src/modules/Traces/TraceGanttChart.tsx @@ -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: ( + + + + + + ), + 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 ( + <> +
    {duration}ms
    + + + ); + }, + }, + ]; + + 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" && ( + <> + + + + {" "} + Focus on selected path{" "} + + + + Reset Focus + + + + { + return { + onClick: () => handleRowOnClick(record, rowIndex), // click row + }; + }} + expandedRowKeys={defaultExpandedRows} + onExpandedRowsChange={handleOnExpandedRowsChange} + pagination={false} + scroll={{ y: 540}} + rowClassName="row-styles" + filterMultiple={false} + /> + + )} + + ); +}; + +export default TraceGanttChart; diff --git a/frontend/src/modules/Traces/TraceGanttChartHelpers.js b/frontend/src/modules/Traces/TraceGanttChartHelpers.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/frontend/src/modules/Traces/TraceGraph.tsx b/frontend/src/modules/Traces/TraceGraph.tsx index 20e81b1c1be7..1e8dbd3a4410 100644 --- a/frontend/src/modules/Traces/TraceGraph.tsx +++ b/frontend/src/modules/Traces/TraceGraph.tsx @@ -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([]); + const [selectedSpan, setSelectedSpan] = useState({}); + const [clickedSpan, setClickedSpan] = useState(null); const [resetZoom, setResetZoom] = useState(false); + const [sortedTreeData, setSortedTreeData] = useState([]); + + 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 ( - + - +
    {
    Trace Graph component ID is {params.id}{" "}
    -
    - - + + + + +
    + + + + + ); }; diff --git a/frontend/src/modules/Traces/TraceList.tsx b/frontend/src/modules/Traces/TraceList.tsx index 78aa97e6dd15..d4cd375ef93f 100644 --- a/frontend/src/modules/Traces/TraceList.tsx +++ b/frontend/src/modules/Traces/TraceList.tsx @@ -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 ( - //
    this.onTodoClick(todo.id)} key={todo.id}> - // {todo.title} - //
    - // ); - // }); - // } - 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) => ( - {text.slice(-16)} - ), - //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
    ; + return ({ + 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 (
    - List of traces with spanID + List of filtered spans
    {renderTraces()}
    ); diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 18886a2ff6d4..edc1d1c91ffe 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -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"