From a0c320e47eea72bc7a27202181028301b00bb160 Mon Sep 17 00:00:00 2001 From: Palash Gupta Date: Thu, 18 May 2023 12:36:02 +0530 Subject: [PATCH] feat: trace to logs and logs to trace is added (#2699) * feat: trace to logs and logs to trace is added * chore: icons and spanId is updated * chore: feedback changes are updated --------- Co-authored-by: Ankit Nayan --- .../src/components/Logs/AddToQueryHOC.tsx | 26 ++++---- frontend/src/components/Logs/styles.ts | 8 +++ .../container/LogDetailedView/TableView.tsx | 64 ++++++++++++++++++- .../LogsSearchFilter/useSearchParser.ts | 14 ++-- .../TraceDetail/SelectedSpanDetails/index.tsx | 20 +++++- 5 files changed, 105 insertions(+), 27 deletions(-) create mode 100644 frontend/src/components/Logs/styles.ts diff --git a/frontend/src/components/Logs/AddToQueryHOC.tsx b/frontend/src/components/Logs/AddToQueryHOC.tsx index d8aee19708..e0810250a8 100644 --- a/frontend/src/components/Logs/AddToQueryHOC.tsx +++ b/frontend/src/components/Logs/AddToQueryHOC.tsx @@ -1,13 +1,14 @@ -import { Button, Popover } from 'antd'; +import { Popover } from 'antd'; +import ROUTES from 'constants/routes'; +import history from 'lib/history'; import { generateFilterQuery } from 'lib/logs/generateFilterQuery'; import React, { memo, useCallback, useMemo } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { Dispatch } from 'redux'; +import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; -import AppActions from 'types/actions'; -import { SET_SEARCH_QUERY_STRING } from 'types/actions/logs'; import { ILogsReducer } from 'types/reducer/logs'; +import { ButtonContainer } from './styles'; + function AddToQueryHOC({ fieldKey, fieldValue, @@ -16,7 +17,6 @@ function AddToQueryHOC({ const { searchFilter: { queryString }, } = useSelector((store) => store.logs); - const dispatch = useDispatch>(); const generatedQuery = useMemo( () => generateFilterQuery({ fieldKey, fieldValue, type: 'IN' }), @@ -31,24 +31,20 @@ function AddToQueryHOC({ } else { updatedQueryString += ` AND ${generatedQuery}`; } - dispatch({ - type: SET_SEARCH_QUERY_STRING, - payload: { - searchQueryString: updatedQueryString, - }, - }); - }, [dispatch, generatedQuery, queryString]); + + history.replace(`${ROUTES.LOGS}?q=${updatedQueryString}`); + }, [generatedQuery, queryString]); const popOverContent = useMemo(() => Add to query: {fieldKey}, [ fieldKey, ]); return ( - + ); } diff --git a/frontend/src/components/Logs/styles.ts b/frontend/src/components/Logs/styles.ts new file mode 100644 index 0000000000..53f31192f9 --- /dev/null +++ b/frontend/src/components/Logs/styles.ts @@ -0,0 +1,8 @@ +import { Button } from 'antd'; +import styled from 'styled-components'; + +export const ButtonContainer = styled(Button)` + &&& { + padding-left: 0; + } +`; diff --git a/frontend/src/container/LogDetailedView/TableView.tsx b/frontend/src/container/LogDetailedView/TableView.tsx index ef30d1471d..003dac2e7c 100644 --- a/frontend/src/container/LogDetailedView/TableView.tsx +++ b/frontend/src/container/LogDetailedView/TableView.tsx @@ -1,14 +1,22 @@ import { blue, orange } from '@ant-design/colors'; -import { Input } from 'antd'; +import { LinkOutlined } from '@ant-design/icons'; +import { Input, Space, Tooltip } from 'antd'; import { ColumnsType } from 'antd/es/table'; import Editor from 'components/Editor'; import AddToQueryHOC from 'components/Logs/AddToQueryHOC'; import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC'; import { ResizeTable } from 'components/ResizeTable'; +import ROUTES from 'constants/routes'; import flatten from 'flat'; +import history from 'lib/history'; import { fieldSearchFilter } from 'lib/logs/fieldSearch'; import { isEmpty } from 'lodash-es'; import React, { useMemo, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { generatePath } from 'react-router-dom'; +import { Dispatch } from 'redux'; +import AppActions from 'types/actions'; +import { SET_DETAILED_LOG_DATA } from 'types/actions/logs'; import { ILog } from 'types/api/logs/log'; import ActionItem from './ActionItem'; @@ -23,6 +31,8 @@ interface TableViewProps { function TableView({ logData }: TableViewProps): JSX.Element | null { const [fieldSearchInput, setFieldSearchInput] = useState(''); + const dispatch = useDispatch>(); + const flattenLogData: Record | null = useMemo( () => (logData ? flatten(logData) : null), [logData], @@ -41,6 +51,29 @@ function TableView({ logData }: TableViewProps): JSX.Element | null { value: JSON.stringify(flattenLogData[key]), })); + const onTraceHandler = (record: DataType) => (): void => { + if (flattenLogData === null) return; + + const traceId = flattenLogData[record.field]; + + const spanId = flattenLogData?.span_id; + + if (traceId) { + dispatch({ + type: SET_DETAILED_LOG_DATA, + payload: null, + }); + + const basePath = generatePath(ROUTES.TRACE_DETAIL, { + id: traceId, + }); + + const route = spanId ? `${basePath}?spanId=${spanId}` : basePath; + + history.push(route); + } + }; + if (!dataSource) { return null; } @@ -62,11 +95,38 @@ function TableView({ logData }: TableViewProps): JSX.Element | null { dataIndex: 'field', key: 'field', width: 30, + align: 'left', ellipsis: true, - render: (field: string): JSX.Element => { + render: (field: string, record): JSX.Element => { const fieldKey = field.split('.').slice(-1); const renderedField = {field}; + if (record.field === 'trace_id') { + const traceId = flattenLogData[record.field]; + + return ( + + {renderedField} + + {traceId && ( + +
+ +
+
+ )} +
+ ); + } + if (!RESTRICTED_FIELDS.includes(fieldKey[0])) { return ( diff --git a/frontend/src/container/LogsSearchFilter/useSearchParser.ts b/frontend/src/container/LogsSearchFilter/useSearchParser.ts index a3dab8a75c..54b483f4e7 100644 --- a/frontend/src/container/LogsSearchFilter/useSearchParser.ts +++ b/frontend/src/container/LogsSearchFilter/useSearchParser.ts @@ -3,7 +3,7 @@ import useUrlQuery from 'hooks/useUrlQuery'; import history from 'lib/history'; import { parseQuery } from 'lib/logql'; import isEqual from 'lodash-es/isEqual'; -import { useCallback, useEffect, useMemo } from 'react'; +import { useCallback, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Dispatch } from 'redux'; import { AppState } from 'store/reducers'; @@ -28,7 +28,7 @@ export function useSearchParser(): { } = useSelector((store) => store.logs); const urlQuery = useUrlQuery(); - const parsedFilters = useMemo(() => urlQuery.get('q'), [urlQuery]); + const parsedFilters = urlQuery.get('q'); const { minTime, maxTime, selectedTime } = useSelector< AppState, @@ -62,16 +62,12 @@ export function useSearchParser(): { }, // need to hide this warning as we don't want to update the query string on every change // eslint-disable-next-line react-hooks/exhaustive-deps - [dispatch, parsedQuery, selectedTime], + [dispatch, parsedQuery, selectedTime, queryString], ); useEffect(() => { - if (!queryString && parsedFilters) { - updateQueryString(parsedFilters); - } else if (queryString) { - updateQueryString(queryString); - } - }, [queryString, updateQueryString, parsedFilters]); + updateQueryString(parsedFilters || ''); + }, [parsedFilters, updateQueryString]); return { queryString, diff --git a/frontend/src/container/TraceDetail/SelectedSpanDetails/index.tsx b/frontend/src/container/TraceDetail/SelectedSpanDetails/index.tsx index c7e0755a37..7c0bd1ad9f 100644 --- a/frontend/src/container/TraceDetail/SelectedSpanDetails/index.tsx +++ b/frontend/src/container/TraceDetail/SelectedSpanDetails/index.tsx @@ -1,8 +1,11 @@ -import { Modal, Tabs, Tooltip, Typography } from 'antd'; +import { Button, Modal, Tabs, Tooltip, Typography } from 'antd'; import Editor from 'components/Editor'; import { StyledSpace } from 'components/Styled'; +import ROUTES from 'constants/routes'; import { useIsDarkMode } from 'hooks/useDarkMode'; +import history from 'lib/history'; import React, { useMemo, useState } from 'react'; +import { useParams } from 'react-router-dom'; import { ITraceTree } from 'types/api/trace/getTraceItem'; import Events from './Events'; @@ -18,6 +21,8 @@ import Tags from './Tags'; function SelectedSpanDetails(props: SelectedSpanDetailsProps): JSX.Element { const { tree, firstSpanStartTime } = props; + const { id: traceId } = useParams(); + const isDarkMode = useIsDarkMode(); const OverLayComponentName = useMemo(() => tree?.name, [tree?.name]); @@ -69,6 +74,12 @@ function SelectedSpanDetails(props: SelectedSpanDetailsProps): JSX.Element { }, ]; + const onLogsHandler = (): void => { + const query = encodeURIComponent(`trace_id IN ('${traceId}')`); + + history.push(`${ROUTES.LOGS}?q=${query}`); + }; + return ( Details for selected Span Service + {tree.serviceName} @@ -86,6 +98,8 @@ function SelectedSpanDetails(props: SelectedSpanDetailsProps): JSX.Element { {tree.name} + +