mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 06:15:57 +08:00
fix: exporer log details action buttons (#3126)
* fix: exporer log details action buttons * chore: magic strings is removed --------- Co-authored-by: Palash Gupta <palashgdev@gmail.com>
This commit is contained in:
parent
60c0836d3e
commit
d26022efb1
@ -1,7 +1,9 @@
|
|||||||
import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
|
import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
|
||||||
|
import { ActionItemProps } from 'container/LogDetailedView/ActionItem';
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
|
||||||
export type LogDetailProps = {
|
export type LogDetailProps = {
|
||||||
log: ILog | null;
|
log: ILog | null;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
} & Pick<AddToQueryHOCProps, 'onAddToQuery'>;
|
} & Pick<AddToQueryHOCProps, 'onAddToQuery'> &
|
||||||
|
Pick<ActionItemProps, 'onClickActionItem'>;
|
||||||
|
@ -8,6 +8,7 @@ function LogDetail({
|
|||||||
log,
|
log,
|
||||||
onClose,
|
onClose,
|
||||||
onAddToQuery,
|
onAddToQuery,
|
||||||
|
onClickActionItem,
|
||||||
}: LogDetailProps): JSX.Element {
|
}: LogDetailProps): JSX.Element {
|
||||||
const onDrawerClose = (): void => {
|
const onDrawerClose = (): void => {
|
||||||
onClose();
|
onClose();
|
||||||
@ -17,7 +18,13 @@ function LogDetail({
|
|||||||
{
|
{
|
||||||
label: 'Table',
|
label: 'Table',
|
||||||
key: '1',
|
key: '1',
|
||||||
children: log && <TableView logData={log} onAddToQuery={onAddToQuery} />,
|
children: log && (
|
||||||
|
<TableView
|
||||||
|
logData={log}
|
||||||
|
onAddToQuery={onAddToQuery}
|
||||||
|
onClickActionItem={onClickActionItem}
|
||||||
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'JSON',
|
label: 'JSON',
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Popover } from 'antd';
|
import { Popover } from 'antd';
|
||||||
|
import { OPERATORS } from 'constants/queryBuilder';
|
||||||
import { memo, ReactNode, useCallback, useMemo } from 'react';
|
import { memo, ReactNode, useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { ButtonContainer } from './styles';
|
import { ButtonContainer } from './styles';
|
||||||
@ -10,7 +11,7 @@ function AddToQueryHOC({
|
|||||||
children,
|
children,
|
||||||
}: AddToQueryHOCProps): JSX.Element {
|
}: AddToQueryHOCProps): JSX.Element {
|
||||||
const handleQueryAdd = useCallback(() => {
|
const handleQueryAdd = useCallback(() => {
|
||||||
onAddToQuery(fieldKey, fieldValue);
|
onAddToQuery(fieldKey, fieldValue, OPERATORS.IN);
|
||||||
}, [fieldKey, fieldValue, onAddToQuery]);
|
}, [fieldKey, fieldValue, onAddToQuery]);
|
||||||
|
|
||||||
const popOverContent = useMemo(() => <span>Add to query: {fieldKey}</span>, [
|
const popOverContent = useMemo(() => <span>Add to query: {fieldKey}</span>, [
|
||||||
@ -29,7 +30,7 @@ function AddToQueryHOC({
|
|||||||
export interface AddToQueryHOCProps {
|
export interface AddToQueryHOCProps {
|
||||||
fieldKey: string;
|
fieldKey: string;
|
||||||
fieldValue: string;
|
fieldValue: string;
|
||||||
onAddToQuery: (fieldKey: string, fieldValue: string) => void;
|
onAddToQuery: (fieldKey: string, fieldValue: string, operator: string) => void;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,148 +1,43 @@
|
|||||||
import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons';
|
import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons';
|
||||||
import { Button, Col, Popover } from 'antd';
|
import { Button, Col, Popover } from 'antd';
|
||||||
import getStep from 'lib/getStep';
|
import { OPERATORS } from 'constants/queryBuilder';
|
||||||
import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
|
import { removeJSONStringifyQuotes } from 'lib/removeJSONStringifyQuotes';
|
||||||
import { getIdConditions } from 'pages/Logs/utils';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { memo, useMemo } from 'react';
|
|
||||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
|
||||||
import { bindActionCreators, Dispatch } from 'redux';
|
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
|
||||||
import { getLogs } from 'store/actions/logs/getLogs';
|
|
||||||
import { getLogsAggregate } from 'store/actions/logs/getLogsAggregate';
|
|
||||||
import { AppState } from 'store/reducers';
|
|
||||||
import AppActions from 'types/actions';
|
|
||||||
import { SET_SEARCH_QUERY_STRING, TOGGLE_LIVE_TAIL } from 'types/actions/logs';
|
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
|
||||||
import { ILogsReducer } from 'types/reducer/logs';
|
|
||||||
|
|
||||||
const removeJSONStringifyQuotes = (s: string): string => {
|
|
||||||
if (!s || !s.length) {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s[0] === '"' && s[s.length - 1] === '"') {
|
|
||||||
return s.slice(1, s.length - 1);
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ActionItemProps {
|
|
||||||
fieldKey: string;
|
|
||||||
fieldValue: string;
|
|
||||||
getLogs: (props: Parameters<typeof getLogs>[0]) => ReturnType<typeof getLogs>;
|
|
||||||
getLogsAggregate: (
|
|
||||||
props: Parameters<typeof getLogsAggregate>[0],
|
|
||||||
) => ReturnType<typeof getLogsAggregate>;
|
|
||||||
}
|
|
||||||
function ActionItem({
|
function ActionItem({
|
||||||
fieldKey,
|
fieldKey,
|
||||||
fieldValue,
|
fieldValue,
|
||||||
getLogs,
|
onClickActionItem,
|
||||||
getLogsAggregate,
|
}: ActionItemProps): JSX.Element {
|
||||||
}: ActionItemProps): JSX.Element | unknown {
|
const handleClick = useCallback(
|
||||||
const {
|
(operator: string) => {
|
||||||
searchFilter: { queryString },
|
const validatedFieldValue = removeJSONStringifyQuotes(fieldValue);
|
||||||
logLinesPerPage,
|
|
||||||
idStart,
|
|
||||||
liveTail,
|
|
||||||
idEnd,
|
|
||||||
order,
|
|
||||||
} = useSelector<AppState, ILogsReducer>((store) => store.logs);
|
|
||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
|
||||||
|
|
||||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
onClickActionItem(fieldKey, validatedFieldValue, operator);
|
||||||
(state) => state.globalTime,
|
},
|
||||||
|
[onClickActionItem, fieldKey, fieldValue],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleQueryAdd = (newQueryString: string): void => {
|
const onClickHandler = useCallback(
|
||||||
let updatedQueryString = queryString || '';
|
(operator: string) => (): void => {
|
||||||
|
handleClick(operator);
|
||||||
|
},
|
||||||
|
[handleClick],
|
||||||
|
);
|
||||||
|
|
||||||
if (updatedQueryString.length === 0) {
|
|
||||||
updatedQueryString += `${newQueryString}`;
|
|
||||||
} else {
|
|
||||||
updatedQueryString += ` AND ${newQueryString}`;
|
|
||||||
}
|
|
||||||
dispatch({
|
|
||||||
type: SET_SEARCH_QUERY_STRING,
|
|
||||||
payload: {
|
|
||||||
searchQueryString: updatedQueryString,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (liveTail === 'STOPPED') {
|
|
||||||
getLogs({
|
|
||||||
q: updatedQueryString,
|
|
||||||
limit: logLinesPerPage,
|
|
||||||
orderBy: 'timestamp',
|
|
||||||
order,
|
|
||||||
timestampStart: minTime,
|
|
||||||
timestampEnd: maxTime,
|
|
||||||
...getIdConditions(idStart, idEnd, order),
|
|
||||||
});
|
|
||||||
getLogsAggregate({
|
|
||||||
timestampStart: minTime,
|
|
||||||
timestampEnd: maxTime,
|
|
||||||
step: getStep({
|
|
||||||
start: minTime,
|
|
||||||
end: maxTime,
|
|
||||||
inputFormat: 'ns',
|
|
||||||
}),
|
|
||||||
q: updatedQueryString,
|
|
||||||
});
|
|
||||||
} else if (liveTail === 'PLAYING') {
|
|
||||||
dispatch({
|
|
||||||
type: TOGGLE_LIVE_TAIL,
|
|
||||||
payload: 'PAUSED',
|
|
||||||
});
|
|
||||||
setTimeout(
|
|
||||||
() =>
|
|
||||||
dispatch({
|
|
||||||
type: TOGGLE_LIVE_TAIL,
|
|
||||||
payload: liveTail,
|
|
||||||
}),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const validatedFieldValue = removeJSONStringifyQuotes(fieldValue);
|
|
||||||
const PopOverMenuContent = useMemo(
|
const PopOverMenuContent = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<Col>
|
<Col>
|
||||||
<Button
|
<Button type="text" size="small" onClick={onClickHandler(OPERATORS.IN)}>
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
onClick={(): void =>
|
|
||||||
handleQueryAdd(
|
|
||||||
generateFilterQuery({
|
|
||||||
fieldKey,
|
|
||||||
fieldValue: validatedFieldValue,
|
|
||||||
type: 'IN',
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<PlusCircleOutlined /> Filter for value
|
<PlusCircleOutlined /> Filter for value
|
||||||
</Button>
|
</Button>
|
||||||
<br />
|
<br />
|
||||||
<Button
|
<Button type="text" size="small" onClick={onClickHandler(OPERATORS.NIN)}>
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
onClick={(): void =>
|
|
||||||
handleQueryAdd(
|
|
||||||
generateFilterQuery({
|
|
||||||
fieldKey,
|
|
||||||
fieldValue: validatedFieldValue,
|
|
||||||
type: 'NIN',
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<MinusCircleOutlined /> Filter out value
|
<MinusCircleOutlined /> Filter out value
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
),
|
),
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
[onClickHandler],
|
||||||
[fieldKey, validatedFieldValue],
|
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Popover placement="bottomLeft" content={PopOverMenuContent} trigger="click">
|
<Popover placement="bottomLeft" content={PopOverMenuContent} trigger="click">
|
||||||
@ -152,19 +47,15 @@ function ActionItem({
|
|||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
interface DispatchProps {
|
|
||||||
getLogs: (props: Parameters<typeof getLogs>[0]) => (dispatch: never) => void;
|
export interface ActionItemProps {
|
||||||
getLogsAggregate: (
|
fieldKey: string;
|
||||||
props: Parameters<typeof getLogsAggregate>[0],
|
fieldValue: string;
|
||||||
) => (dispatch: never) => void;
|
onClickActionItem: (
|
||||||
|
fieldKey: string,
|
||||||
|
fieldValue: string,
|
||||||
|
operator: string,
|
||||||
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = (
|
export default memo(ActionItem);
|
||||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
|
||||||
): DispatchProps => ({
|
|
||||||
getLogs: bindActionCreators(getLogs, dispatch),
|
|
||||||
getLogsAggregate: bindActionCreators(getLogsAggregate, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
export default connect(null, mapDispatchToProps)(memo(ActionItem as any));
|
|
||||||
|
@ -20,7 +20,7 @@ import AppActions from 'types/actions';
|
|||||||
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
|
||||||
import ActionItem from './ActionItem';
|
import ActionItem, { ActionItemProps } from './ActionItem';
|
||||||
import { flattenObject, recursiveParseJSON } from './utils';
|
import { flattenObject, recursiveParseJSON } from './utils';
|
||||||
|
|
||||||
// Fields which should be restricted from adding it to query
|
// Fields which should be restricted from adding it to query
|
||||||
@ -30,9 +30,15 @@ interface TableViewProps {
|
|||||||
logData: ILog;
|
logData: ILog;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = TableViewProps & Pick<AddToQueryHOCProps, 'onAddToQuery'>;
|
type Props = TableViewProps &
|
||||||
|
Pick<AddToQueryHOCProps, 'onAddToQuery'> &
|
||||||
|
Pick<ActionItemProps, 'onClickActionItem'>;
|
||||||
|
|
||||||
function TableView({ logData, onAddToQuery }: Props): JSX.Element | null {
|
function TableView({
|
||||||
|
logData,
|
||||||
|
onAddToQuery,
|
||||||
|
onClickActionItem,
|
||||||
|
}: Props): JSX.Element | null {
|
||||||
const [fieldSearchInput, setFieldSearchInput] = useState<string>('');
|
const [fieldSearchInput, setFieldSearchInput] = useState<string>('');
|
||||||
|
|
||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
@ -89,7 +95,13 @@ function TableView({ logData, onAddToQuery }: Props): JSX.Element | null {
|
|||||||
render: (fieldData: Record<string, string>): JSX.Element | null => {
|
render: (fieldData: Record<string, string>): JSX.Element | null => {
|
||||||
const fieldKey = fieldData.field.split('.').slice(-1);
|
const fieldKey = fieldData.field.split('.').slice(-1);
|
||||||
if (!RESTRICTED_FIELDS.includes(fieldKey[0])) {
|
if (!RESTRICTED_FIELDS.includes(fieldKey[0])) {
|
||||||
return <ActionItem fieldKey={fieldKey} fieldValue={fieldData.value} />;
|
return (
|
||||||
|
<ActionItem
|
||||||
|
fieldKey={fieldKey[0]}
|
||||||
|
fieldValue={fieldData.value}
|
||||||
|
onClickActionItem={onClickActionItem}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
@ -1,21 +1,49 @@
|
|||||||
import LogDetail from 'components/LogDetail';
|
import LogDetail from 'components/LogDetail';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
|
import { getGeneratedFilterQueryString } from 'lib/getGeneratedFilterQueryString';
|
||||||
import { useCallback } from 'react';
|
import getStep from 'lib/getStep';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { getIdConditions } from 'pages/Logs/utils';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { Dispatch } from 'redux';
|
import { bindActionCreators, Dispatch } from 'redux';
|
||||||
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
|
import { getLogs } from 'store/actions/logs/getLogs';
|
||||||
|
import { getLogsAggregate } from 'store/actions/logs/getLogsAggregate';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
import {
|
||||||
|
SET_DETAILED_LOG_DATA,
|
||||||
|
SET_SEARCH_QUERY_STRING,
|
||||||
|
TOGGLE_LIVE_TAIL,
|
||||||
|
} from 'types/actions/logs';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
import { ILogsReducer } from 'types/reducer/logs';
|
import { ILogsReducer } from 'types/reducer/logs';
|
||||||
|
|
||||||
function LogDetailedView(): JSX.Element {
|
type LogDetailedViewProps = {
|
||||||
|
getLogs: (props: Parameters<typeof getLogs>[0]) => ReturnType<typeof getLogs>;
|
||||||
|
getLogsAggregate: (
|
||||||
|
props: Parameters<typeof getLogsAggregate>[0],
|
||||||
|
) => ReturnType<typeof getLogsAggregate>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function LogDetailedView({
|
||||||
|
getLogs,
|
||||||
|
getLogsAggregate,
|
||||||
|
}: LogDetailedViewProps): JSX.Element {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const {
|
const {
|
||||||
detailedLog,
|
detailedLog,
|
||||||
searchFilter: { queryString },
|
searchFilter: { queryString },
|
||||||
|
logLinesPerPage,
|
||||||
|
idStart,
|
||||||
|
liveTail,
|
||||||
|
idEnd,
|
||||||
|
order,
|
||||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||||
|
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||||
|
(state) => state.globalTime,
|
||||||
|
);
|
||||||
|
|
||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
|
|
||||||
@ -26,32 +54,109 @@ function LogDetailedView(): JSX.Element {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleQueryAdd = useCallback(
|
const handleAddToQuery = useCallback(
|
||||||
(fieldKey: string, fieldValue: string) => {
|
(fieldKey: string, fieldValue: string, operator: string) => {
|
||||||
const generatedQuery = generateFilterQuery({
|
const updatedQueryString = getGeneratedFilterQueryString(
|
||||||
fieldKey,
|
fieldKey,
|
||||||
fieldValue,
|
fieldValue,
|
||||||
type: 'IN',
|
operator,
|
||||||
});
|
queryString,
|
||||||
|
);
|
||||||
|
|
||||||
let updatedQueryString = queryString || '';
|
|
||||||
if (updatedQueryString.length === 0) {
|
|
||||||
updatedQueryString += `${generatedQuery}`;
|
|
||||||
} else {
|
|
||||||
updatedQueryString += ` AND ${generatedQuery}`;
|
|
||||||
}
|
|
||||||
history.replace(`${ROUTES.LOGS}?q=${updatedQueryString}`);
|
history.replace(`${ROUTES.LOGS}?q=${updatedQueryString}`);
|
||||||
},
|
},
|
||||||
[history, queryString],
|
[history, queryString],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleClickActionItem = useCallback(
|
||||||
|
(fieldKey: string, fieldValue: string, operator: string): void => {
|
||||||
|
const updatedQueryString = getGeneratedFilterQueryString(
|
||||||
|
fieldKey,
|
||||||
|
fieldValue,
|
||||||
|
operator,
|
||||||
|
queryString,
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: SET_SEARCH_QUERY_STRING,
|
||||||
|
payload: {
|
||||||
|
searchQueryString: updatedQueryString,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (liveTail === 'STOPPED') {
|
||||||
|
getLogs({
|
||||||
|
q: updatedQueryString,
|
||||||
|
limit: logLinesPerPage,
|
||||||
|
orderBy: 'timestamp',
|
||||||
|
order,
|
||||||
|
timestampStart: minTime,
|
||||||
|
timestampEnd: maxTime,
|
||||||
|
...getIdConditions(idStart, idEnd, order),
|
||||||
|
});
|
||||||
|
getLogsAggregate({
|
||||||
|
timestampStart: minTime,
|
||||||
|
timestampEnd: maxTime,
|
||||||
|
step: getStep({
|
||||||
|
start: minTime,
|
||||||
|
end: maxTime,
|
||||||
|
inputFormat: 'ns',
|
||||||
|
}),
|
||||||
|
q: updatedQueryString,
|
||||||
|
});
|
||||||
|
} else if (liveTail === 'PLAYING') {
|
||||||
|
dispatch({
|
||||||
|
type: TOGGLE_LIVE_TAIL,
|
||||||
|
payload: 'PAUSED',
|
||||||
|
});
|
||||||
|
setTimeout(
|
||||||
|
() =>
|
||||||
|
dispatch({
|
||||||
|
type: TOGGLE_LIVE_TAIL,
|
||||||
|
payload: liveTail,
|
||||||
|
}),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
dispatch,
|
||||||
|
getLogs,
|
||||||
|
getLogsAggregate,
|
||||||
|
idEnd,
|
||||||
|
idStart,
|
||||||
|
liveTail,
|
||||||
|
logLinesPerPage,
|
||||||
|
maxTime,
|
||||||
|
minTime,
|
||||||
|
order,
|
||||||
|
queryString,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LogDetail
|
<LogDetail
|
||||||
log={detailedLog}
|
log={detailedLog}
|
||||||
onClose={onDrawerClose}
|
onClose={onDrawerClose}
|
||||||
onAddToQuery={handleQueryAdd}
|
onAddToQuery={handleAddToQuery}
|
||||||
|
onClickActionItem={handleClickActionItem}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LogDetailedView;
|
interface DispatchProps {
|
||||||
|
getLogs: (props: Parameters<typeof getLogs>[0]) => (dispatch: never) => void;
|
||||||
|
getLogsAggregate: (
|
||||||
|
props: Parameters<typeof getLogsAggregate>[0],
|
||||||
|
) => (dispatch: never) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (
|
||||||
|
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||||
|
): DispatchProps => ({
|
||||||
|
getLogs: bindActionCreators(getLogs, dispatch),
|
||||||
|
getLogsAggregate: bindActionCreators(getLogsAggregate, dispatch),
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export default connect(null, mapDispatchToProps)(memo(LogDetailedView as any));
|
||||||
|
@ -5,6 +5,7 @@ import TabLabel from 'components/TabLabel';
|
|||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import {
|
import {
|
||||||
initialQueriesMap,
|
initialQueriesMap,
|
||||||
|
OPERATORS,
|
||||||
PANEL_TYPES,
|
PANEL_TYPES,
|
||||||
QueryBuilderKeys,
|
QueryBuilderKeys,
|
||||||
} from 'constants/queryBuilder';
|
} from 'constants/queryBuilder';
|
||||||
@ -226,8 +227,8 @@ function LogsExplorerViews(): JSX.Element {
|
|||||||
[currentStagedQueryData, orderByTimestamp],
|
[currentStagedQueryData, orderByTimestamp],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleAddQuery = useCallback(
|
const handleAddToQuery = useCallback(
|
||||||
(fieldKey: string, fieldValue: string): void => {
|
(fieldKey: string, fieldValue: string, operator: string): void => {
|
||||||
const keysAutocomplete: BaseAutocompleteData[] =
|
const keysAutocomplete: BaseAutocompleteData[] =
|
||||||
queryClient.getQueryData<SuccessResponse<IQueryAutocompleteResponse>>(
|
queryClient.getQueryData<SuccessResponse<IQueryAutocompleteResponse>>(
|
||||||
[QueryBuilderKeys.GET_AGGREGATE_KEYS],
|
[QueryBuilderKeys.GET_AGGREGATE_KEYS],
|
||||||
@ -239,6 +240,9 @@ function LogsExplorerViews(): JSX.Element {
|
|||||||
fieldKey,
|
fieldKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const currentOperator =
|
||||||
|
Object.keys(OPERATORS).find((op) => op === operator) || '';
|
||||||
|
|
||||||
const nextQuery: Query = {
|
const nextQuery: Query = {
|
||||||
...currentQuery,
|
...currentQuery,
|
||||||
builder: {
|
builder: {
|
||||||
@ -254,7 +258,7 @@ function LogsExplorerViews(): JSX.Element {
|
|||||||
{
|
{
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
key: existAutocompleteKey,
|
key: existAutocompleteKey,
|
||||||
op: '=',
|
op: currentOperator,
|
||||||
value: fieldValue,
|
value: fieldValue,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -422,7 +426,7 @@ function LogsExplorerViews(): JSX.Element {
|
|||||||
onOpenDetailedView={handleSetActiveLog}
|
onOpenDetailedView={handleSetActiveLog}
|
||||||
onEndReached={handleEndReached}
|
onEndReached={handleEndReached}
|
||||||
onExpand={handleSetActiveLog}
|
onExpand={handleSetActiveLog}
|
||||||
onAddToQuery={handleAddQuery}
|
onAddToQuery={handleAddToQuery}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -453,7 +457,7 @@ function LogsExplorerViews(): JSX.Element {
|
|||||||
logs,
|
logs,
|
||||||
handleSetActiveLog,
|
handleSetActiveLog,
|
||||||
handleEndReached,
|
handleEndReached,
|
||||||
handleAddQuery,
|
handleAddToQuery,
|
||||||
data,
|
data,
|
||||||
isError,
|
isError,
|
||||||
],
|
],
|
||||||
@ -506,7 +510,8 @@ function LogsExplorerViews(): JSX.Element {
|
|||||||
<LogDetail
|
<LogDetail
|
||||||
log={activeLog}
|
log={activeLog}
|
||||||
onClose={handleClearActiveLog}
|
onClose={handleClearActiveLog}
|
||||||
onAddToQuery={handleAddQuery}
|
onAddToQuery={handleAddToQuery}
|
||||||
|
onClickActionItem={handleAddToQuery}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -7,7 +7,7 @@ import Spinner from 'components/Spinner';
|
|||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { contentStyle } from 'container/Trace/Search/config';
|
import { contentStyle } from 'container/Trace/Search/config';
|
||||||
import useFontFaceObserver from 'hooks/useFontObserver';
|
import useFontFaceObserver from 'hooks/useFontObserver';
|
||||||
import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
|
import { getGeneratedFilterQueryString } from 'lib/getGeneratedFilterQueryString';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
@ -77,20 +77,15 @@ function LogsTable(props: LogsTableProps): JSX.Element {
|
|||||||
[dispatch],
|
[dispatch],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleQueryAdd = useCallback(
|
const handleAddToQuery = useCallback(
|
||||||
(fieldKey: string, fieldValue: string) => {
|
(fieldKey: string, fieldValue: string, operator: string) => {
|
||||||
const generatedQuery = generateFilterQuery({
|
const updatedQueryString = getGeneratedFilterQueryString(
|
||||||
fieldKey,
|
fieldKey,
|
||||||
fieldValue,
|
fieldValue,
|
||||||
type: 'IN',
|
operator,
|
||||||
});
|
queryString,
|
||||||
|
);
|
||||||
|
|
||||||
let updatedQueryString = queryString || '';
|
|
||||||
if (updatedQueryString.length === 0) {
|
|
||||||
updatedQueryString += `${generatedQuery}`;
|
|
||||||
} else {
|
|
||||||
updatedQueryString += ` AND ${generatedQuery}`;
|
|
||||||
}
|
|
||||||
history.replace(`${ROUTES.LOGS}?q=${updatedQueryString}`);
|
history.replace(`${ROUTES.LOGS}?q=${updatedQueryString}`);
|
||||||
},
|
},
|
||||||
[history, queryString],
|
[history, queryString],
|
||||||
@ -117,7 +112,7 @@ function LogsTable(props: LogsTableProps): JSX.Element {
|
|||||||
logData={log}
|
logData={log}
|
||||||
selectedFields={selected}
|
selectedFields={selected}
|
||||||
onOpenDetailedView={handleOpenDetailedView}
|
onOpenDetailedView={handleOpenDetailedView}
|
||||||
onAddToQuery={handleQueryAdd}
|
onAddToQuery={handleAddToQuery}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -128,7 +123,7 @@ function LogsTable(props: LogsTableProps): JSX.Element {
|
|||||||
linesPerRow,
|
linesPerRow,
|
||||||
onClickExpand,
|
onClickExpand,
|
||||||
handleOpenDetailedView,
|
handleOpenDetailedView,
|
||||||
handleQueryAdd,
|
handleAddToQuery,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -34,8 +34,12 @@ export const useTag = (
|
|||||||
() =>
|
() =>
|
||||||
(query?.filters?.items || []).map((ele) => {
|
(query?.filters?.items || []).map((ele) => {
|
||||||
if (isInNInOperator(getOperatorFromValue(ele.op))) {
|
if (isInNInOperator(getOperatorFromValue(ele.op))) {
|
||||||
const csvString = Papa.unparse([ele.value]);
|
try {
|
||||||
return `${ele.key?.key} ${getOperatorFromValue(ele.op)} ${csvString}`;
|
const csvString = Papa.unparse([ele.value]);
|
||||||
|
return `${ele.key?.key} ${getOperatorFromValue(ele.op)} ${csvString}`;
|
||||||
|
} catch {
|
||||||
|
return `${ele.key?.key} ${getOperatorFromValue(ele.op)} ${ele.value}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return `${ele.key?.key} ${getOperatorFromValue(ele.op)} ${ele.value}`;
|
return `${ele.key?.key} ${getOperatorFromValue(ele.op)} ${ele.value}`;
|
||||||
}),
|
}),
|
||||||
|
24
frontend/src/lib/getGeneratedFilterQueryString.ts
Normal file
24
frontend/src/lib/getGeneratedFilterQueryString.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { generateFilterQuery } from './logs/generateFilterQuery';
|
||||||
|
|
||||||
|
export const getGeneratedFilterQueryString = (
|
||||||
|
fieldKey: string,
|
||||||
|
fieldValue: string,
|
||||||
|
operator: string,
|
||||||
|
queryString: string,
|
||||||
|
): string => {
|
||||||
|
let updatedQueryString = queryString || '';
|
||||||
|
|
||||||
|
const generatedString = generateFilterQuery({
|
||||||
|
fieldKey,
|
||||||
|
fieldValue,
|
||||||
|
type: operator,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (updatedQueryString.length === 0) {
|
||||||
|
updatedQueryString += `${generatedString}`;
|
||||||
|
} else {
|
||||||
|
updatedQueryString += ` AND ${generatedString}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedQueryString;
|
||||||
|
};
|
10
frontend/src/lib/removeJSONStringifyQuotes.ts
Normal file
10
frontend/src/lib/removeJSONStringifyQuotes.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export const removeJSONStringifyQuotes = (s: string): string => {
|
||||||
|
if (!s || !s.length) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s[0] === '"' && s[s.length - 1] === '"') {
|
||||||
|
return s.slice(1, s.length - 1);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user