diff --git a/frontend/src/components/LogDetail/LogDetail.interfaces.ts b/frontend/src/components/LogDetail/LogDetail.interfaces.ts index 8e4ed857f0..2e1597f905 100644 --- a/frontend/src/components/LogDetail/LogDetail.interfaces.ts +++ b/frontend/src/components/LogDetail/LogDetail.interfaces.ts @@ -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; diff --git a/frontend/src/components/LogDetail/index.tsx b/frontend/src/components/LogDetail/index.tsx index 054a9c3161..6fb43b1218 100644 --- a/frontend/src/components/LogDetail/index.tsx +++ b/frontend/src/components/LogDetail/index.tsx @@ -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 && , + children: log && , }, { label: 'JSON', diff --git a/frontend/src/components/Logs/AddToQueryHOC.tsx b/frontend/src/components/Logs/AddToQueryHOC.tsx index 874a9a0ec7..d4d1f9bd31 100644 --- a/frontend/src/components/Logs/AddToQueryHOC.tsx +++ b/frontend/src/components/Logs/AddToQueryHOC.tsx @@ -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((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(() => Add to query: {fieldKey}, [ fieldKey, @@ -48,9 +26,10 @@ function AddToQueryHOC({ ); } -interface AddToQueryHOCProps { +export interface AddToQueryHOCProps { fieldKey: string; fieldValue: string; + onAddToQuery: (fieldKey: string, fieldValue: string) => void; children: ReactNode; } diff --git a/frontend/src/components/Logs/ListLogView/index.tsx b/frontend/src/components/Logs/ListLogView/index.tsx index a4f1e9cc9c..ea4bada737 100644 --- a/frontend/src/components/Logs/ListLogView/index.tsx +++ b/frontend/src/components/Logs/ListLogView/index.tsx @@ -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((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 ( - + {fieldKey} diff --git a/frontend/src/constants/queryBuilder.ts b/frontend/src/constants/queryBuilder.ts index 26cc32d3c4..9ff71e1712 100644 --- a/frontend/src/constants/queryBuilder.ts +++ b/frontend/src/constants/queryBuilder.ts @@ -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 = { diff --git a/frontend/src/container/ExportPanel/ExportPanel.tsx b/frontend/src/container/ExportPanel/ExportPanel.tsx index 80092c75dc..24e9ed210e 100644 --- a/frontend/src/container/ExportPanel/ExportPanel.tsx +++ b/frontend/src/container/ExportPanel/ExportPanel.tsx @@ -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( @@ -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 { - + + + + + + void; diff --git a/frontend/src/container/ExportPanel/utils.ts b/frontend/src/container/ExportPanel/utils.ts index 128de92324..f2d36a278b 100644 --- a/frontend/src/container/ExportPanel/utils.ts +++ b/frontend/src/container/ExportPanel/utils.ts @@ -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()); diff --git a/frontend/src/container/LogDetailedView/TableView.tsx b/frontend/src/container/LogDetailedView/TableView.tsx index 7d4db12acc..b027c0e523 100644 --- a/frontend/src/container/LogDetailedView/TableView.tsx +++ b/frontend/src/container/LogDetailedView/TableView.tsx @@ -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; + +function TableView({ logData, onAddToQuery }: Props): JSX.Element | null { const [fieldSearchInput, setFieldSearchInput] = useState(''); const dispatch = useDispatch>(); @@ -128,7 +133,11 @@ function TableView({ logData }: TableViewProps): JSX.Element | null { if (!RESTRICTED_FIELDS.includes(fieldKey[0])) { return ( - + {renderedField} ); diff --git a/frontend/src/container/LogDetailedView/index.tsx b/frontend/src/container/LogDetailedView/index.tsx index 3860d657f0..25ccf8c50c 100644 --- a/frontend/src/container/LogDetailedView/index.tsx +++ b/frontend/src/container/LogDetailedView/index.tsx @@ -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( - (state) => state.logs, - ); + const history = useHistory(); + const { + detailedLog, + searchFilter: { queryString }, + } = useSelector((state) => state.logs); const dispatch = useDispatch>(); @@ -20,7 +26,32 @@ function LogDetailedView(): JSX.Element { }); }; - return ; + 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 ( + + ); } export default LogDetailedView; diff --git a/frontend/src/container/LogExplorerDetailedView/index.tsx b/frontend/src/container/LogExplorerDetailedView/index.tsx index 1a573f2ec8..26bb067b88 100644 --- a/frontend/src/container/LogExplorerDetailedView/index.tsx +++ b/frontend/src/container/LogExplorerDetailedView/index.tsx @@ -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 ; + const handleAddQuery = useCallback( + (fieldKey: string, fieldValue: string): void => { + const keysAutocomplete: BaseAutocompleteData[] = + queryClient.getQueryData>( + [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 ; } export default LogExplorerDetailedView; diff --git a/frontend/src/hooks/queryBuilder/useFetchKeysAndValues.ts b/frontend/src/hooks/queryBuilder/useFetchKeysAndValues.ts index 53651fddb9..b616398de4 100644 --- a/frontend/src/hooks/queryBuilder/useFetchKeysAndValues.ts +++ b/frontend/src/hooks/queryBuilder/useFetchKeysAndValues.ts @@ -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([]); const [results, setResults] = useState([]); - 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,