mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-07-28 16:02:03 +08:00
Merge branch 'develop' into chore/hotrod-locust
This commit is contained in:
commit
d7f5e5d6ac
@ -1,3 +1,7 @@
|
||||
import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
export type LogDetailProps = { log: ILog | null; onClose: () => void };
|
||||
export type LogDetailProps = {
|
||||
log: ILog | null;
|
||||
onClose: () => void;
|
||||
} & Pick<AddToQueryHOCProps, 'onAddToQuery'>;
|
||||
|
@ -4,7 +4,11 @@ import TableView from 'container/LogDetailedView/TableView';
|
||||
|
||||
import { LogDetailProps } from './LogDetail.interfaces';
|
||||
|
||||
function LogDetail({ log, onClose }: LogDetailProps): JSX.Element {
|
||||
function LogDetail({
|
||||
log,
|
||||
onClose,
|
||||
onAddToQuery,
|
||||
}: LogDetailProps): JSX.Element {
|
||||
const onDrawerClose = (): void => {
|
||||
onClose();
|
||||
};
|
||||
@ -13,7 +17,7 @@ function LogDetail({ log, onClose }: LogDetailProps): JSX.Element {
|
||||
{
|
||||
label: 'Table',
|
||||
key: '1',
|
||||
children: log && <TableView logData={log} />,
|
||||
children: log && <TableView logData={log} onAddToQuery={onAddToQuery} />,
|
||||
},
|
||||
{
|
||||
label: 'JSON',
|
||||
|
@ -1,39 +1,17 @@
|
||||
import { Popover } from 'antd';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
|
||||
import { memo, ReactNode, useCallback, useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
import { ButtonContainer } from './styles';
|
||||
|
||||
function AddToQueryHOC({
|
||||
fieldKey,
|
||||
fieldValue,
|
||||
onAddToQuery,
|
||||
children,
|
||||
}: AddToQueryHOCProps): JSX.Element {
|
||||
const {
|
||||
searchFilter: { queryString },
|
||||
} = useSelector<AppState, ILogsReducer>((store) => store.logs);
|
||||
|
||||
const generatedQuery = useMemo(
|
||||
() => generateFilterQuery({ fieldKey, fieldValue, type: 'IN' }),
|
||||
[fieldKey, fieldValue],
|
||||
);
|
||||
|
||||
const handleQueryAdd = useCallback(() => {
|
||||
let updatedQueryString = queryString || '';
|
||||
|
||||
if (updatedQueryString.length === 0) {
|
||||
updatedQueryString += `${generatedQuery}`;
|
||||
} else {
|
||||
updatedQueryString += ` AND ${generatedQuery}`;
|
||||
}
|
||||
|
||||
history.replace(`${ROUTES.LOGS}?q=${updatedQueryString}`);
|
||||
}, [generatedQuery, queryString]);
|
||||
onAddToQuery(fieldKey, fieldValue);
|
||||
}, [fieldKey, fieldValue, onAddToQuery]);
|
||||
|
||||
const popOverContent = useMemo(() => <span>Add to query: {fieldKey}</span>, [
|
||||
fieldKey,
|
||||
@ -48,9 +26,10 @@ function AddToQueryHOC({
|
||||
);
|
||||
}
|
||||
|
||||
interface AddToQueryHOCProps {
|
||||
export interface AddToQueryHOCProps {
|
||||
fieldKey: string;
|
||||
fieldValue: string;
|
||||
onAddToQuery: (fieldKey: string, fieldValue: string) => void;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
|
@ -2,16 +2,22 @@ import { blue, grey, orange } from '@ant-design/colors';
|
||||
import { CopyFilled, ExpandAltOutlined } from '@ant-design/icons';
|
||||
import Convert from 'ansi-to-html';
|
||||
import { Button, Divider, Row, Typography } from 'antd';
|
||||
import ROUTES from 'constants/routes';
|
||||
import dayjs from 'dayjs';
|
||||
import dompurify from 'dompurify';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
// utils
|
||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||
import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { AppState } from 'store/reducers';
|
||||
// interfaces
|
||||
import { IField } from 'types/api/logs/fields';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
// components
|
||||
import AddToQueryHOC from '../AddToQueryHOC';
|
||||
@ -57,9 +63,37 @@ function LogSelectedField({
|
||||
fieldKey = '',
|
||||
fieldValue = '',
|
||||
}: LogFieldProps): JSX.Element {
|
||||
const history = useHistory();
|
||||
const {
|
||||
searchFilter: { queryString },
|
||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||
|
||||
const handleQueryAdd = useCallback(
|
||||
(fieldKey: string, fieldValue: string) => {
|
||||
const generatedQuery = generateFilterQuery({
|
||||
fieldKey,
|
||||
fieldValue,
|
||||
type: 'IN',
|
||||
});
|
||||
|
||||
let updatedQueryString = queryString || '';
|
||||
if (updatedQueryString.length === 0) {
|
||||
updatedQueryString += `${generatedQuery}`;
|
||||
} else {
|
||||
updatedQueryString += ` AND ${generatedQuery}`;
|
||||
}
|
||||
history.replace(`${ROUTES.LOGS}?q=${updatedQueryString}`);
|
||||
},
|
||||
[history, queryString],
|
||||
);
|
||||
|
||||
return (
|
||||
<SelectedLog>
|
||||
<AddToQueryHOC fieldKey={fieldKey} fieldValue={fieldValue}>
|
||||
<AddToQueryHOC
|
||||
fieldKey={fieldKey}
|
||||
fieldValue={fieldValue}
|
||||
onAddToQuery={handleQueryAdd}
|
||||
>
|
||||
<Typography.Text>
|
||||
<span style={{ color: blue[4] }}>{fieldKey}</span>
|
||||
</Typography.Text>
|
||||
|
@ -66,7 +66,6 @@ export const alphabet: string[] = alpha.map((str) => String.fromCharCode(str));
|
||||
export enum QueryBuilderKeys {
|
||||
GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE',
|
||||
GET_AGGREGATE_KEYS = 'GET_AGGREGATE_KEYS',
|
||||
GET_ATTRIBUTE_KEY = 'GET_ATTRIBUTE_KEY',
|
||||
}
|
||||
|
||||
export const mapOfOperators = {
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { Button, Typography } from 'antd';
|
||||
import createDashboard from 'api/dashboard/create';
|
||||
import axios from 'axios';
|
||||
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import useAxiosError from 'hooks/useAxiosError';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useMutation } from 'react-query';
|
||||
@ -15,10 +14,9 @@ import {
|
||||
Title,
|
||||
Wrapper,
|
||||
} from './styles';
|
||||
import { getSelectOptions } from './utils';
|
||||
import { filterOptions, getSelectOptions } from './utils';
|
||||
|
||||
function ExportPanel({ isLoading, onExport }: ExportPanelProps): JSX.Element {
|
||||
const { notifications } = useNotifications();
|
||||
const { t } = useTranslation(['dashboard']);
|
||||
|
||||
const [selectedDashboardId, setSelectedDashboardId] = useState<string | null>(
|
||||
@ -31,21 +29,19 @@ function ExportPanel({ isLoading, onExport }: ExportPanelProps): JSX.Element {
|
||||
refetch,
|
||||
} = useGetAllDashboard();
|
||||
|
||||
const handleError = useAxiosError();
|
||||
|
||||
const {
|
||||
mutate: createNewDashboard,
|
||||
isLoading: createDashboardLoading,
|
||||
} = useMutation(createDashboard, {
|
||||
onSuccess: (data) => {
|
||||
onExport(data?.payload || null);
|
||||
if (data.payload) {
|
||||
onExport(data?.payload);
|
||||
}
|
||||
refetch();
|
||||
},
|
||||
onError: (error) => {
|
||||
if (axios.isAxiosError(error)) {
|
||||
notifications.error({
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
},
|
||||
onError: handleError,
|
||||
});
|
||||
|
||||
const options = useMemo(() => getSelectOptions(data?.payload || []), [data]);
|
||||
@ -90,10 +86,12 @@ function ExportPanel({ isLoading, onExport }: ExportPanelProps): JSX.Element {
|
||||
<DashboardSelect
|
||||
placeholder="Select Dashboard"
|
||||
options={options}
|
||||
showSearch
|
||||
loading={isDashboardLoading}
|
||||
disabled={isDashboardLoading}
|
||||
value={selectedDashboardId}
|
||||
onSelect={handleSelect}
|
||||
filterOption={filterOptions}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
|
@ -1,9 +0,0 @@
|
||||
export const MENU_KEY = {
|
||||
EXPORT: 'export',
|
||||
CREATE_ALERTS: 'create-alerts',
|
||||
};
|
||||
|
||||
export const MENU_LABEL = {
|
||||
EXPORT: 'Export Panel',
|
||||
CREATE_ALERTS: 'Create Alerts',
|
||||
};
|
@ -1,12 +1,12 @@
|
||||
import { Button, Dropdown, MenuProps, Modal } from 'antd';
|
||||
import { AlertOutlined, AreaChartOutlined } from '@ant-design/icons';
|
||||
import { Button, Modal, Space } from 'antd';
|
||||
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { MENU_KEY, MENU_LABEL } from './config';
|
||||
import ExportPanelContainer from './ExportPanel';
|
||||
|
||||
function ExportPanel({
|
||||
@ -28,51 +28,37 @@ function ExportPanel({
|
||||
);
|
||||
}, [query]);
|
||||
|
||||
const onMenuClickHandler: MenuProps['onClick'] = useCallback(
|
||||
(e: OnClickProps) => {
|
||||
if (e.key === MENU_KEY.EXPORT) {
|
||||
onModalToggle(true);
|
||||
}
|
||||
|
||||
if (e.key === MENU_KEY.CREATE_ALERTS) {
|
||||
onCreateAlertsHandler();
|
||||
}
|
||||
},
|
||||
[onModalToggle, onCreateAlertsHandler],
|
||||
);
|
||||
|
||||
const menu: MenuProps = useMemo(
|
||||
() => ({
|
||||
items: [
|
||||
{
|
||||
key: MENU_KEY.EXPORT,
|
||||
label: MENU_LABEL.EXPORT,
|
||||
},
|
||||
{
|
||||
key: MENU_KEY.CREATE_ALERTS,
|
||||
label: MENU_LABEL.CREATE_ALERTS,
|
||||
},
|
||||
],
|
||||
onClick: onMenuClickHandler,
|
||||
}),
|
||||
[onMenuClickHandler],
|
||||
);
|
||||
|
||||
const onCancel = (value: boolean) => (): void => {
|
||||
onModalToggle(value);
|
||||
};
|
||||
|
||||
const onAddToDashboard = (): void => {
|
||||
setIsExport(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dropdown trigger={['click']} menu={menu}>
|
||||
<Button>Actions</Button>
|
||||
</Dropdown>
|
||||
<Space size={24}>
|
||||
<Button
|
||||
icon={<AreaChartOutlined />}
|
||||
onClick={onAddToDashboard}
|
||||
type="primary"
|
||||
>
|
||||
Add to Dashboard
|
||||
</Button>
|
||||
|
||||
<Button onClick={onCreateAlertsHandler} icon={<AlertOutlined />}>
|
||||
Setup Alerts
|
||||
</Button>
|
||||
</Space>
|
||||
|
||||
<Modal
|
||||
footer={null}
|
||||
onOk={onCancel(false)}
|
||||
onCancel={onCancel(false)}
|
||||
open={isExport}
|
||||
centered
|
||||
destroyOnClose
|
||||
>
|
||||
<ExportPanelContainer
|
||||
query={query}
|
||||
@ -84,10 +70,6 @@ function ExportPanel({
|
||||
);
|
||||
}
|
||||
|
||||
interface OnClickProps {
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface ExportPanelProps {
|
||||
isLoading?: boolean;
|
||||
onExport: (dashboard: Dashboard | null) => void;
|
||||
|
@ -8,3 +8,11 @@ export const getSelectOptions = (
|
||||
label: data.title,
|
||||
value: uuid,
|
||||
}));
|
||||
|
||||
export const filterOptions: SelectProps['filterOption'] = (
|
||||
input,
|
||||
options,
|
||||
): boolean =>
|
||||
(options?.label?.toString() ?? '')
|
||||
?.toLowerCase()
|
||||
.includes(input.toLowerCase());
|
||||
|
@ -3,7 +3,9 @@ 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 AddToQueryHOC, {
|
||||
AddToQueryHOCProps,
|
||||
} from 'components/Logs/AddToQueryHOC';
|
||||
import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import ROUTES from 'constants/routes';
|
||||
@ -27,7 +29,10 @@ const RESTRICTED_FIELDS = ['timestamp'];
|
||||
interface TableViewProps {
|
||||
logData: ILog;
|
||||
}
|
||||
function TableView({ logData }: TableViewProps): JSX.Element | null {
|
||||
|
||||
type Props = TableViewProps & Pick<AddToQueryHOCProps, 'onAddToQuery'>;
|
||||
|
||||
function TableView({ logData, onAddToQuery }: Props): JSX.Element | null {
|
||||
const [fieldSearchInput, setFieldSearchInput] = useState<string>('');
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
@ -128,7 +133,11 @@ function TableView({ logData }: TableViewProps): JSX.Element | null {
|
||||
|
||||
if (!RESTRICTED_FIELDS.includes(fieldKey[0])) {
|
||||
return (
|
||||
<AddToQueryHOC fieldKey={fieldKey[0]} fieldValue={flattenLogData[field]}>
|
||||
<AddToQueryHOC
|
||||
fieldKey={fieldKey[0]}
|
||||
fieldValue={flattenLogData[field]}
|
||||
onAddToQuery={onAddToQuery}
|
||||
>
|
||||
{renderedField}
|
||||
</AddToQueryHOC>
|
||||
);
|
||||
|
@ -1,5 +1,9 @@
|
||||
import LogDetail from 'components/LogDetail';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
|
||||
import { useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
@ -7,9 +11,11 @@ import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
function LogDetailedView(): JSX.Element {
|
||||
const { detailedLog } = useSelector<AppState, ILogsReducer>(
|
||||
(state) => state.logs,
|
||||
);
|
||||
const history = useHistory();
|
||||
const {
|
||||
detailedLog,
|
||||
searchFilter: { queryString },
|
||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
@ -20,7 +26,32 @@ function LogDetailedView(): JSX.Element {
|
||||
});
|
||||
};
|
||||
|
||||
return <LogDetail log={detailedLog} onClose={onDrawerClose} />;
|
||||
const handleQueryAdd = useCallback(
|
||||
(fieldKey: string, fieldValue: string) => {
|
||||
const generatedQuery = generateFilterQuery({
|
||||
fieldKey,
|
||||
fieldValue,
|
||||
type: 'IN',
|
||||
});
|
||||
|
||||
let updatedQueryString = queryString || '';
|
||||
if (updatedQueryString.length === 0) {
|
||||
updatedQueryString += `${generatedQuery}`;
|
||||
} else {
|
||||
updatedQueryString += ` AND ${generatedQuery}`;
|
||||
}
|
||||
history.replace(`${ROUTES.LOGS}?q=${updatedQueryString}`);
|
||||
},
|
||||
[history, queryString],
|
||||
);
|
||||
|
||||
return (
|
||||
<LogDetail
|
||||
log={detailedLog}
|
||||
onClose={onDrawerClose}
|
||||
onAddToQuery={handleQueryAdd}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default LogDetailedView;
|
||||
|
@ -1,4 +1,16 @@
|
||||
import LogDetail from 'components/LogDetail';
|
||||
import { QueryBuilderKeys } from 'constants/queryBuilder';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { chooseAutocompleteFromCustomValue } from 'lib/newQueryBuilder/chooseAutocompleteFromCustomValue';
|
||||
import { useCallback } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import {
|
||||
BaseAutocompleteData,
|
||||
IQueryAutocompleteResponse,
|
||||
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { LogExplorerDetailedViewProps } from './LogExplorerDetailedView.interfaces';
|
||||
|
||||
@ -6,11 +18,52 @@ function LogExplorerDetailedView({
|
||||
log,
|
||||
onClose,
|
||||
}: LogExplorerDetailedViewProps): JSX.Element {
|
||||
const onDrawerClose = (): void => {
|
||||
onClose();
|
||||
const queryClient = useQueryClient();
|
||||
const { redirectWithQueryBuilderData, currentQuery } = useQueryBuilder();
|
||||
|
||||
const handleAddQuery = useCallback(
|
||||
(fieldKey: string, fieldValue: string): void => {
|
||||
const keysAutocomplete: BaseAutocompleteData[] =
|
||||
queryClient.getQueryData<SuccessResponse<IQueryAutocompleteResponse>>(
|
||||
[QueryBuilderKeys.GET_AGGREGATE_KEYS],
|
||||
{ exact: false },
|
||||
)?.payload.attributeKeys || [];
|
||||
|
||||
const existAutocompleteKey = chooseAutocompleteFromCustomValue(
|
||||
keysAutocomplete,
|
||||
[fieldKey],
|
||||
)[0];
|
||||
|
||||
const nextQuery: Query = {
|
||||
...currentQuery,
|
||||
builder: {
|
||||
...currentQuery.builder,
|
||||
queryData: currentQuery.builder.queryData.map((item) => ({
|
||||
...item,
|
||||
filters: {
|
||||
...item.filters,
|
||||
items: [
|
||||
...item.filters.items.filter(
|
||||
(item) => item.key?.id !== existAutocompleteKey.id,
|
||||
),
|
||||
{
|
||||
id: uuid(),
|
||||
key: existAutocompleteKey,
|
||||
op: '=',
|
||||
value: fieldValue,
|
||||
},
|
||||
],
|
||||
},
|
||||
})),
|
||||
},
|
||||
};
|
||||
|
||||
return <LogDetail log={log} onClose={onDrawerClose} />;
|
||||
redirectWithQueryBuilderData(nextQuery);
|
||||
},
|
||||
[currentQuery, queryClient, redirectWithQueryBuilderData],
|
||||
);
|
||||
|
||||
return <LogDetail log={log} onClose={onClose} onAddToQuery={handleAddQuery} />;
|
||||
}
|
||||
|
||||
export default LogExplorerDetailedView;
|
||||
|
@ -6,8 +6,8 @@ import {
|
||||
getTagToken,
|
||||
isInNInOperator,
|
||||
} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
|
||||
import useDebounceValue from 'hooks/useDebounce';
|
||||
import { isEqual, uniqWith } from 'lodash-es';
|
||||
import debounce from 'lodash-es/debounce';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useDebounce } from 'react-use';
|
||||
@ -39,25 +39,23 @@ export const useFetchKeysAndValues = (
|
||||
const [sourceKeys, setSourceKeys] = useState<BaseAutocompleteData[]>([]);
|
||||
const [results, setResults] = useState<string[]>([]);
|
||||
|
||||
const searchParams = useMemo(
|
||||
() =>
|
||||
debounce(
|
||||
const memoizedSearchParams = useMemo(
|
||||
() => [
|
||||
searchKey,
|
||||
query.dataSource,
|
||||
query.aggregateOperator,
|
||||
query.aggregateAttribute.key,
|
||||
],
|
||||
300,
|
||||
),
|
||||
[
|
||||
query.aggregateAttribute.key,
|
||||
query.aggregateOperator,
|
||||
query.dataSource,
|
||||
searchKey,
|
||||
query.dataSource,
|
||||
query.aggregateOperator,
|
||||
query.aggregateAttribute.key,
|
||||
],
|
||||
);
|
||||
|
||||
const searchParams = useDebounceValue(memoizedSearchParams, 300);
|
||||
|
||||
const isQueryEnabled = useMemo(
|
||||
() =>
|
||||
query.dataSource === DataSource.METRICS
|
||||
@ -73,7 +71,7 @@ export const useFetchKeysAndValues = (
|
||||
);
|
||||
|
||||
const { data, isFetching, status } = useQuery(
|
||||
[QueryBuilderKeys.GET_ATTRIBUTE_KEY, searchParams()],
|
||||
[QueryBuilderKeys.GET_AGGREGATE_KEYS, searchParams],
|
||||
async () =>
|
||||
getAggregateKeys({
|
||||
searchText: searchKey,
|
||||
|
Loading…
x
Reference in New Issue
Block a user