mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-07-25 08:14:24 +08:00
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 <ankit@signoz.io>
This commit is contained in:
parent
5637188e72
commit
a0c320e47e
@ -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<AppState, ILogsReducer>((store) => store.logs);
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
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(() => <span>Add to query: {fieldKey}</span>, [
|
||||
fieldKey,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Button size="small" type="text" onClick={handleQueryAdd}>
|
||||
<ButtonContainer size="small" type="text" onClick={handleQueryAdd}>
|
||||
<Popover placement="top" content={popOverContent}>
|
||||
{children}
|
||||
</Popover>
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
);
|
||||
}
|
||||
|
||||
|
8
frontend/src/components/Logs/styles.ts
Normal file
8
frontend/src/components/Logs/styles.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Button } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const ButtonContainer = styled(Button)`
|
||||
&&& {
|
||||
padding-left: 0;
|
||||
}
|
||||
`;
|
@ -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<string>('');
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const flattenLogData: Record<string, never> | 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 = <span style={{ color: blue[4] }}>{field}</span>;
|
||||
|
||||
if (record.field === 'trace_id') {
|
||||
const traceId = flattenLogData[record.field];
|
||||
|
||||
return (
|
||||
<Space size="middle">
|
||||
{renderedField}
|
||||
|
||||
{traceId && (
|
||||
<Tooltip title="Inspect in Trace">
|
||||
<div
|
||||
style={{ cursor: 'pointer' }}
|
||||
role="presentation"
|
||||
onClick={onTraceHandler(record)}
|
||||
>
|
||||
<LinkOutlined
|
||||
style={{
|
||||
width: '15px',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
if (!RESTRICTED_FIELDS.includes(fieldKey[0])) {
|
||||
return (
|
||||
<AddToQueryHOC fieldKey={fieldKey[0]} fieldValue={flattenLogData[field]}>
|
||||
|
@ -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<AppState, ILogsReducer>((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,
|
||||
|
@ -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<Params>();
|
||||
|
||||
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 (
|
||||
<CardContainer>
|
||||
<StyledSpace
|
||||
@ -78,6 +89,7 @@ function SelectedSpanDetails(props: SelectedSpanDetailsProps): JSX.Element {
|
||||
<Typography.Text strong> Details for selected Span </Typography.Text>
|
||||
|
||||
<CustomTitle>Service</CustomTitle>
|
||||
|
||||
<Tooltip overlay={OverLayComponentServiceName}>
|
||||
<CustomText ellipsis>{tree.serviceName}</CustomText>
|
||||
</Tooltip>
|
||||
@ -86,6 +98,8 @@ function SelectedSpanDetails(props: SelectedSpanDetailsProps): JSX.Element {
|
||||
<Tooltip overlay={OverLayComponentName}>
|
||||
<CustomText ellipsis>{tree.name}</CustomText>
|
||||
</Tooltip>
|
||||
|
||||
<Button onClick={onLogsHandler}>Go to Related logs</Button>
|
||||
</StyledSpace>
|
||||
|
||||
<Modal
|
||||
@ -125,4 +139,8 @@ export interface ModalText {
|
||||
subText: string;
|
||||
}
|
||||
|
||||
interface Params {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export default SelectedSpanDetails;
|
||||
|
Loading…
x
Reference in New Issue
Block a user