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

View File

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

View File

@ -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>

View File

@ -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 = {

View File

@ -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"

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 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;

View File

@ -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());

View File

@ -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>
);

View File

@ -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;

View File

@ -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();
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;

View File

@ -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(
() => [
searchKey,
query.dataSource,
query.aggregateOperator,
query.aggregateAttribute.key,
],
300,
),
[
query.aggregateAttribute.key,
query.aggregateOperator,
query.dataSource,
const memoizedSearchParams = useMemo(
() => [
searchKey,
query.dataSource,
query.aggregateOperator,
query.aggregateAttribute.key,
],
[
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,