Merge branch 'develop' into chore/hotrod-locust

This commit is contained in:
Prashant Shahi 2023-07-11 19:24:30 +05:30 committed by GitHub
commit d7f5e5d6ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 210 additions and 120 deletions

View File

@ -1,3 +1,7 @@
import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
import { ILog } from 'types/api/logs/log'; 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'>;

View File

@ -4,7 +4,11 @@ import TableView from 'container/LogDetailedView/TableView';
import { LogDetailProps } from './LogDetail.interfaces'; import { LogDetailProps } from './LogDetail.interfaces';
function LogDetail({ log, onClose }: LogDetailProps): JSX.Element { function LogDetail({
log,
onClose,
onAddToQuery,
}: LogDetailProps): JSX.Element {
const onDrawerClose = (): void => { const onDrawerClose = (): void => {
onClose(); onClose();
}; };
@ -13,7 +17,7 @@ function LogDetail({ log, onClose }: LogDetailProps): JSX.Element {
{ {
label: 'Table', label: 'Table',
key: '1', key: '1',
children: log && <TableView logData={log} />, children: log && <TableView logData={log} onAddToQuery={onAddToQuery} />,
}, },
{ {
label: 'JSON', label: 'JSON',

View File

@ -1,39 +1,17 @@
import { Popover } from 'antd'; 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 { 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'; import { ButtonContainer } from './styles';
function AddToQueryHOC({ function AddToQueryHOC({
fieldKey, fieldKey,
fieldValue, fieldValue,
onAddToQuery,
children, children,
}: AddToQueryHOCProps): JSX.Element { }: 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(() => { const handleQueryAdd = useCallback(() => {
let updatedQueryString = queryString || ''; onAddToQuery(fieldKey, fieldValue);
}, [fieldKey, fieldValue, onAddToQuery]);
if (updatedQueryString.length === 0) {
updatedQueryString += `${generatedQuery}`;
} else {
updatedQueryString += ` AND ${generatedQuery}`;
}
history.replace(`${ROUTES.LOGS}?q=${updatedQueryString}`);
}, [generatedQuery, queryString]);
const popOverContent = useMemo(() => <span>Add to query: {fieldKey}</span>, [ const popOverContent = useMemo(() => <span>Add to query: {fieldKey}</span>, [
fieldKey, fieldKey,
@ -48,9 +26,10 @@ function AddToQueryHOC({
); );
} }
interface AddToQueryHOCProps { export interface AddToQueryHOCProps {
fieldKey: string; fieldKey: string;
fieldValue: string; fieldValue: string;
onAddToQuery: (fieldKey: string, fieldValue: string) => void;
children: ReactNode; children: ReactNode;
} }

View File

@ -2,16 +2,22 @@ import { blue, grey, orange } from '@ant-design/colors';
import { CopyFilled, ExpandAltOutlined } from '@ant-design/icons'; import { CopyFilled, ExpandAltOutlined } from '@ant-design/icons';
import Convert from 'ansi-to-html'; import Convert from 'ansi-to-html';
import { Button, Divider, Row, Typography } from 'antd'; import { Button, Divider, Row, Typography } from 'antd';
import ROUTES from 'constants/routes';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import dompurify from 'dompurify'; import dompurify from 'dompurify';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
// utils // utils
import { FlatLogData } from 'lib/logs/flatLogData'; import { FlatLogData } from 'lib/logs/flatLogData';
import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use'; import { useCopyToClipboard } from 'react-use';
import { AppState } from 'store/reducers';
// interfaces // interfaces
import { IField } from 'types/api/logs/fields'; import { IField } from 'types/api/logs/fields';
import { ILog } from 'types/api/logs/log'; import { ILog } from 'types/api/logs/log';
import { ILogsReducer } from 'types/reducer/logs';
// components // components
import AddToQueryHOC from '../AddToQueryHOC'; import AddToQueryHOC from '../AddToQueryHOC';
@ -57,9 +63,37 @@ function LogSelectedField({
fieldKey = '', fieldKey = '',
fieldValue = '', fieldValue = '',
}: LogFieldProps): JSX.Element { }: 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 ( return (
<SelectedLog> <SelectedLog>
<AddToQueryHOC fieldKey={fieldKey} fieldValue={fieldValue}> <AddToQueryHOC
fieldKey={fieldKey}
fieldValue={fieldValue}
onAddToQuery={handleQueryAdd}
>
<Typography.Text> <Typography.Text>
<span style={{ color: blue[4] }}>{fieldKey}</span> <span style={{ color: blue[4] }}>{fieldKey}</span>
</Typography.Text> </Typography.Text>

View File

@ -66,7 +66,6 @@ export const alphabet: string[] = alpha.map((str) => String.fromCharCode(str));
export enum QueryBuilderKeys { export enum QueryBuilderKeys {
GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE', GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE',
GET_AGGREGATE_KEYS = 'GET_AGGREGATE_KEYS', GET_AGGREGATE_KEYS = 'GET_AGGREGATE_KEYS',
GET_ATTRIBUTE_KEY = 'GET_ATTRIBUTE_KEY',
} }
export const mapOfOperators = { export const mapOfOperators = {

View File

@ -1,8 +1,7 @@
import { Button, Typography } from 'antd'; import { Button, Typography } from 'antd';
import createDashboard from 'api/dashboard/create'; import createDashboard from 'api/dashboard/create';
import axios from 'axios';
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard'; import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
import { useNotifications } from 'hooks/useNotifications'; import useAxiosError from 'hooks/useAxiosError';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useMutation } from 'react-query'; import { useMutation } from 'react-query';
@ -15,10 +14,9 @@ import {
Title, Title,
Wrapper, Wrapper,
} from './styles'; } from './styles';
import { getSelectOptions } from './utils'; import { filterOptions, getSelectOptions } from './utils';
function ExportPanel({ isLoading, onExport }: ExportPanelProps): JSX.Element { function ExportPanel({ isLoading, onExport }: ExportPanelProps): JSX.Element {
const { notifications } = useNotifications();
const { t } = useTranslation(['dashboard']); const { t } = useTranslation(['dashboard']);
const [selectedDashboardId, setSelectedDashboardId] = useState<string | null>( const [selectedDashboardId, setSelectedDashboardId] = useState<string | null>(
@ -31,21 +29,19 @@ function ExportPanel({ isLoading, onExport }: ExportPanelProps): JSX.Element {
refetch, refetch,
} = useGetAllDashboard(); } = useGetAllDashboard();
const handleError = useAxiosError();
const { const {
mutate: createNewDashboard, mutate: createNewDashboard,
isLoading: createDashboardLoading, isLoading: createDashboardLoading,
} = useMutation(createDashboard, { } = useMutation(createDashboard, {
onSuccess: (data) => { onSuccess: (data) => {
onExport(data?.payload || null); if (data.payload) {
onExport(data?.payload);
}
refetch(); refetch();
}, },
onError: (error) => { onError: handleError,
if (axios.isAxiosError(error)) {
notifications.error({
message: error.message,
});
}
},
}); });
const options = useMemo(() => getSelectOptions(data?.payload || []), [data]); const options = useMemo(() => getSelectOptions(data?.payload || []), [data]);
@ -90,10 +86,12 @@ function ExportPanel({ isLoading, onExport }: ExportPanelProps): JSX.Element {
<DashboardSelect <DashboardSelect
placeholder="Select Dashboard" placeholder="Select Dashboard"
options={options} options={options}
showSearch
loading={isDashboardLoading} loading={isDashboardLoading}
disabled={isDashboardLoading} disabled={isDashboardLoading}
value={selectedDashboardId} value={selectedDashboardId}
onSelect={handleSelect} onSelect={handleSelect}
filterOption={filterOptions}
/> />
<Button <Button
type="primary" type="primary"

View File

@ -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',
};

View File

@ -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 { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import history from 'lib/history'; import history from 'lib/history';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useState } from 'react';
import { Dashboard } from 'types/api/dashboard/getAll'; import { Dashboard } from 'types/api/dashboard/getAll';
import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { MENU_KEY, MENU_LABEL } from './config';
import ExportPanelContainer from './ExportPanel'; import ExportPanelContainer from './ExportPanel';
function ExportPanel({ function ExportPanel({
@ -28,51 +28,37 @@ function ExportPanel({
); );
}, [query]); }, [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 => { const onCancel = (value: boolean) => (): void => {
onModalToggle(value); onModalToggle(value);
}; };
const onAddToDashboard = (): void => {
setIsExport(true);
};
return ( return (
<> <>
<Dropdown trigger={['click']} menu={menu}> <Space size={24}>
<Button>Actions</Button> <Button
</Dropdown> icon={<AreaChartOutlined />}
onClick={onAddToDashboard}
type="primary"
>
Add to Dashboard
</Button>
<Button onClick={onCreateAlertsHandler} icon={<AlertOutlined />}>
Setup Alerts
</Button>
</Space>
<Modal <Modal
footer={null} footer={null}
onOk={onCancel(false)} onOk={onCancel(false)}
onCancel={onCancel(false)} onCancel={onCancel(false)}
open={isExport} open={isExport}
centered centered
destroyOnClose
> >
<ExportPanelContainer <ExportPanelContainer
query={query} query={query}
@ -84,10 +70,6 @@ function ExportPanel({
); );
} }
interface OnClickProps {
key: string;
}
export interface ExportPanelProps { export interface ExportPanelProps {
isLoading?: boolean; isLoading?: boolean;
onExport: (dashboard: Dashboard | null) => void; onExport: (dashboard: Dashboard | null) => void;

View File

@ -8,3 +8,11 @@ export const getSelectOptions = (
label: data.title, label: data.title,
value: uuid, value: uuid,
})); }));
export const filterOptions: SelectProps['filterOption'] = (
input,
options,
): boolean =>
(options?.label?.toString() ?? '')
?.toLowerCase()
.includes(input.toLowerCase());

View File

@ -3,7 +3,9 @@ import { LinkOutlined } from '@ant-design/icons';
import { Input, Space, Tooltip } from 'antd'; import { Input, Space, Tooltip } from 'antd';
import { ColumnsType } from 'antd/es/table'; import { ColumnsType } from 'antd/es/table';
import Editor from 'components/Editor'; 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 CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
import { ResizeTable } from 'components/ResizeTable'; import { ResizeTable } from 'components/ResizeTable';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
@ -27,7 +29,10 @@ const RESTRICTED_FIELDS = ['timestamp'];
interface TableViewProps { interface TableViewProps {
logData: ILog; 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 [fieldSearchInput, setFieldSearchInput] = useState<string>('');
const dispatch = useDispatch<Dispatch<AppActions>>(); const dispatch = useDispatch<Dispatch<AppActions>>();
@ -128,7 +133,11 @@ function TableView({ logData }: TableViewProps): JSX.Element | null {
if (!RESTRICTED_FIELDS.includes(fieldKey[0])) { if (!RESTRICTED_FIELDS.includes(fieldKey[0])) {
return ( return (
<AddToQueryHOC fieldKey={fieldKey[0]} fieldValue={flattenLogData[field]}> <AddToQueryHOC
fieldKey={fieldKey[0]}
fieldValue={flattenLogData[field]}
onAddToQuery={onAddToQuery}
>
{renderedField} {renderedField}
</AddToQueryHOC> </AddToQueryHOC>
); );

View File

@ -1,5 +1,9 @@
import LogDetail from 'components/LogDetail'; 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 { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
@ -7,9 +11,11 @@ import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
import { ILogsReducer } from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
function LogDetailedView(): JSX.Element { function LogDetailedView(): JSX.Element {
const { detailedLog } = useSelector<AppState, ILogsReducer>( const history = useHistory();
(state) => state.logs, const {
); detailedLog,
searchFilter: { queryString },
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
const dispatch = useDispatch<Dispatch<AppActions>>(); 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; export default LogDetailedView;

View File

@ -1,4 +1,16 @@
import LogDetail from 'components/LogDetail'; 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'; import { LogExplorerDetailedViewProps } from './LogExplorerDetailedView.interfaces';
@ -6,11 +18,52 @@ function LogExplorerDetailedView({
log, log,
onClose, onClose,
}: LogExplorerDetailedViewProps): JSX.Element { }: LogExplorerDetailedViewProps): JSX.Element {
const onDrawerClose = (): void => { const queryClient = useQueryClient();
onClose(); const { redirectWithQueryBuilderData, currentQuery } = useQueryBuilder();
};
return <LogDetail log={log} onClose={onDrawerClose} />; 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,
},
],
},
})),
},
};
redirectWithQueryBuilderData(nextQuery);
},
[currentQuery, queryClient, redirectWithQueryBuilderData],
);
return <LogDetail log={log} onClose={onClose} onAddToQuery={handleAddQuery} />;
} }
export default LogExplorerDetailedView; export default LogExplorerDetailedView;

View File

@ -6,8 +6,8 @@ import {
getTagToken, getTagToken,
isInNInOperator, isInNInOperator,
} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils'; } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
import useDebounceValue from 'hooks/useDebounce';
import { isEqual, uniqWith } from 'lodash-es'; import { isEqual, uniqWith } from 'lodash-es';
import debounce from 'lodash-es/debounce';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
import { useDebounce } from 'react-use'; import { useDebounce } from 'react-use';
@ -39,25 +39,23 @@ export const useFetchKeysAndValues = (
const [sourceKeys, setSourceKeys] = useState<BaseAutocompleteData[]>([]); const [sourceKeys, setSourceKeys] = useState<BaseAutocompleteData[]>([]);
const [results, setResults] = useState<string[]>([]); const [results, setResults] = useState<string[]>([]);
const searchParams = useMemo( const memoizedSearchParams = useMemo(
() => () => [
debounce(
() => [
searchKey,
query.dataSource,
query.aggregateOperator,
query.aggregateAttribute.key,
],
300,
),
[
query.aggregateAttribute.key,
query.aggregateOperator,
query.dataSource,
searchKey, searchKey,
query.dataSource,
query.aggregateOperator,
query.aggregateAttribute.key,
],
[
searchKey,
query.dataSource,
query.aggregateOperator,
query.aggregateAttribute.key,
], ],
); );
const searchParams = useDebounceValue(memoizedSearchParams, 300);
const isQueryEnabled = useMemo( const isQueryEnabled = useMemo(
() => () =>
query.dataSource === DataSource.METRICS query.dataSource === DataSource.METRICS
@ -73,7 +71,7 @@ export const useFetchKeysAndValues = (
); );
const { data, isFetching, status } = useQuery( const { data, isFetching, status } = useQuery(
[QueryBuilderKeys.GET_ATTRIBUTE_KEY, searchParams()], [QueryBuilderKeys.GET_AGGREGATE_KEYS, searchParams],
async () => async () =>
getAggregateKeys({ getAggregateKeys({
searchText: searchKey, searchText: searchKey,