From 857e505323c48b2f54feb79c2a7e1f2b5a7175cf Mon Sep 17 00:00:00 2001 From: dnazarenkoo <134951516+dnazarenkoo@users.noreply.github.com> Date: Fri, 7 Jul 2023 13:29:45 +0300 Subject: [PATCH] fix: fix initial columns for the list view / fix attributes API call (#3056) * fix: fix initial columns for the list view * fix: replace columns * fix: update attribute api call for the options menu * fix: update call attributes API call * fix: add error msg for the options list --- .../OptionsMenu/AddColumnField/index.tsx | 9 +- frontend/src/container/OptionsMenu/types.ts | 6 +- .../container/OptionsMenu/useOptionsMenu.ts | 185 +++++++++++++----- frontend/src/container/OptionsMenu/utils.ts | 12 -- .../TracesExplorer/ListView/index.tsx | 4 +- 5 files changed, 156 insertions(+), 60 deletions(-) diff --git a/frontend/src/container/OptionsMenu/AddColumnField/index.tsx b/frontend/src/container/OptionsMenu/AddColumnField/index.tsx index 384dba8aaa..5b5382a016 100644 --- a/frontend/src/container/OptionsMenu/AddColumnField/index.tsx +++ b/frontend/src/container/OptionsMenu/AddColumnField/index.tsx @@ -1,5 +1,5 @@ import { SearchOutlined } from '@ant-design/icons'; -import { Input } from 'antd'; +import { Input, Spin } from 'antd'; import Typography from 'antd/es/typography/Typography'; import { useIsDarkMode } from 'hooks/useDarkMode'; import { useTranslation } from 'react-i18next'; @@ -26,12 +26,17 @@ function AddColumnField({ config }: AddColumnFieldProps): JSX.Element | null { : null} /> diff --git a/frontend/src/container/OptionsMenu/types.ts b/frontend/src/container/OptionsMenu/types.ts index e7fdf89f97..a4bbdc8641 100644 --- a/frontend/src/container/OptionsMenu/types.ts +++ b/frontend/src/container/OptionsMenu/types.ts @@ -16,7 +16,11 @@ export interface InitialOptions export type OptionsMenuConfig = { format?: Pick; maxLines?: Pick; - addColumn?: Pick & { + addColumn?: Pick< + SelectProps, + 'options' | 'onSelect' | 'onFocus' | 'onSearch' | 'onBlur' + > & { + isFetching: boolean; value: BaseAutocompleteData[]; onRemove: (key: string) => void; }; diff --git a/frontend/src/container/OptionsMenu/useOptionsMenu.ts b/frontend/src/container/OptionsMenu/useOptionsMenu.ts index f5719fe37a..5017580e2f 100644 --- a/frontend/src/container/OptionsMenu/useOptionsMenu.ts +++ b/frontend/src/container/OptionsMenu/useOptionsMenu.ts @@ -4,15 +4,21 @@ import setToLocalstorage from 'api/browser/localstorage/set'; import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys'; import { LOCALSTORAGE } from 'constants/localStorage'; import { QueryBuilderKeys } from 'constants/queryBuilder'; +import useDebounce from 'hooks/useDebounce'; +import { useNotifications } from 'hooks/useNotifications'; import useUrlQueryData from 'hooks/useUrlQueryData'; -import { useCallback, useEffect, useMemo } from 'react'; -import { useQuery } from 'react-query'; -import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useQueries, useQuery } from 'react-query'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { + BaseAutocompleteData, + IQueryAutocompleteResponse, +} from 'types/api/queryBuilder/queryAutocompleteResponse'; import { DataSource } from 'types/common/queryBuilder'; import { defaultOptionsQuery, URL_OPTIONS } from './constants'; import { InitialOptions, OptionsMenuConfig, OptionsQuery } from './types'; -import { getInitialColumns, getOptionsFromKeys } from './utils'; +import { getOptionsFromKeys } from './utils'; interface UseOptionsMenuProps { dataSource: DataSource; @@ -21,7 +27,6 @@ interface UseOptionsMenuProps { } interface UseOptionsMenu { - isLoading: boolean; options: OptionsQuery; config: OptionsMenuConfig; } @@ -31,41 +36,108 @@ const useOptionsMenu = ({ aggregateOperator, initialOptions = {}, }: UseOptionsMenuProps): UseOptionsMenu => { + const { notifications } = useNotifications(); + + const [searchText, setSearchText] = useState(''); + const [isFocused, setIsFocused] = useState(false); + const debouncedSearchText = useDebounce(searchText, 300); + const localStorageOptionsQuery = getFromLocalstorage( LOCALSTORAGE.LIST_OPTIONS, ); + const initialQueryParams = useMemo( + () => ({ + searchText: '', + aggregateAttribute: '', + tagType: null, + dataSource, + aggregateOperator, + }), + [dataSource, aggregateOperator], + ); + const { query: optionsQuery, queryData: optionsQueryData, redirectWithQuery: redirectWithOptionsData, } = useUrlQueryData(URL_OPTIONS, defaultOptionsQuery); - const { data, isFetched, isLoading } = useQuery( - [QueryBuilderKeys.GET_ATTRIBUTE_KEY, dataSource, aggregateOperator], - async () => - getAggregateKeys({ - searchText: '', - dataSource, - aggregateOperator, - aggregateAttribute: '', - tagType: null, - }), + const initialQueries = useMemo( + () => + initialOptions?.selectColumns?.map((column) => ({ + queryKey: column, + queryFn: (): Promise< + SuccessResponse | ErrorResponse + > => + getAggregateKeys({ + ...initialQueryParams, + searchText: column, + }), + enabled: !!column && !optionsQuery, + })) || [], + [initialOptions?.selectColumns, initialQueryParams, optionsQuery], ); - const attributeKeys = useMemo(() => data?.payload?.attributeKeys || [], [ - data?.payload?.attributeKeys, + const initialAttributesResult = useQueries(initialQueries); + + const isFetchedInitialAttributes = useMemo( + () => initialAttributesResult.every((result) => result.isFetched), + [initialAttributesResult], + ); + + const initialSelectedColumns = useMemo(() => { + if (!isFetchedInitialAttributes) return []; + + const attributesData = initialAttributesResult?.reduce( + (acc, attributeResponse) => { + const data = attributeResponse?.data?.payload?.attributeKeys || []; + + return [...acc, ...data]; + }, + [] as BaseAutocompleteData[], + ); + + return ( + (initialOptions.selectColumns + ?.map((column) => attributesData.find(({ key }) => key === column)) + .filter(Boolean) as BaseAutocompleteData[]) || [] + ); + }, [ + isFetchedInitialAttributes, + initialOptions?.selectColumns, + initialAttributesResult, ]); + const { + data: searchedAttributesData, + isFetching: isSearchedAttributesFetching, + } = useQuery( + [QueryBuilderKeys.GET_AGGREGATE_KEYS, debouncedSearchText, isFocused], + async () => + getAggregateKeys({ + ...initialQueryParams, + searchText: debouncedSearchText, + }), + { + enabled: isFocused, + }, + ); + + const searchedAttributeKeys = useMemo( + () => searchedAttributesData?.payload?.attributeKeys || [], + [searchedAttributesData?.payload?.attributeKeys], + ); + const initialOptionsQuery: OptionsQuery = useMemo( () => ({ ...defaultOptionsQuery, ...initialOptions, selectColumns: initialOptions?.selectColumns - ? getInitialColumns(initialOptions?.selectColumns || [], attributeKeys) + ? initialSelectedColumns : defaultOptionsQuery.selectColumns, }), - [initialOptions, attributeKeys], + [initialOptions, initialSelectedColumns], ); const selectedColumnKeys = useMemo( @@ -73,13 +145,13 @@ const useOptionsMenu = ({ [optionsQueryData], ); - const addColumnOptions = useMemo(() => { - const filteredAttributeKeys = attributeKeys.filter( + const optionsFromAttributeKeys = useMemo(() => { + const filteredAttributeKeys = searchedAttributeKeys.filter( (item) => item.key !== 'body', ); return getOptionsFromKeys(filteredAttributeKeys, selectedColumnKeys); - }, [attributeKeys, selectedColumnKeys]); + }, [searchedAttributeKeys, selectedColumnKeys]); const handleRedirectWithOptionsData = useCallback( (newQueryData: OptionsQuery) => { @@ -90,13 +162,14 @@ const useOptionsMenu = ({ [redirectWithOptionsData], ); - const handleSelectedColumnsChange = useCallback( - (value: string[]) => { - const newSelectedColumnKeys = [ - ...new Set([...selectedColumnKeys, ...value]), - ]; + const handleSelectColumns = useCallback( + (value: string) => { + const newSelectedColumnKeys = [...new Set([...selectedColumnKeys, value])]; const newSelectedColumns = newSelectedColumnKeys.reduce((acc, key) => { - const column = attributeKeys.find(({ id }) => id === key); + const column = [ + ...searchedAttributeKeys, + ...optionsQueryData.selectColumns, + ].find(({ id }) => id === key); if (!column) return acc; return [...acc, column]; @@ -110,10 +183,10 @@ const useOptionsMenu = ({ handleRedirectWithOptionsData(optionsData); }, [ + searchedAttributeKeys, selectedColumnKeys, optionsQueryData, handleRedirectWithOptionsData, - attributeKeys, ], ); @@ -123,14 +196,20 @@ const useOptionsMenu = ({ ({ id }) => id !== columnKey, ); - const optionsData: OptionsQuery = { - ...optionsQueryData, - selectColumns: newSelectedColumns, - }; + if (!newSelectedColumns.length && dataSource !== DataSource.LOGS) { + notifications.error({ + message: 'There must be at least one selected column', + }); + } else { + const optionsData: OptionsQuery = { + ...optionsQueryData, + selectColumns: newSelectedColumns, + }; - handleRedirectWithOptionsData(optionsData); + handleRedirectWithOptionsData(optionsData); + } }, - [optionsQueryData, handleRedirectWithOptionsData], + [dataSource, notifications, optionsQueryData, handleRedirectWithOptionsData], ); const handleFormatChange = useCallback( @@ -157,13 +236,30 @@ const useOptionsMenu = ({ [handleRedirectWithOptionsData, optionsQueryData], ); + const handleSearchAttribute = useCallback((value: string) => { + setSearchText(value); + }, []); + + const handleFocus = (): void => { + setIsFocused(true); + }; + + const handleBlur = (): void => { + setIsFocused(false); + setSearchText(''); + }; + const optionsMenuConfig: Required = useMemo( () => ({ addColumn: { - value: optionsQueryData.selectColumns || defaultOptionsQuery.selectColumns, - options: addColumnOptions || [], - onChange: handleSelectedColumnsChange, + isFetching: isSearchedAttributesFetching, + value: optionsQueryData?.selectColumns || defaultOptionsQuery.selectColumns, + options: optionsFromAttributeKeys || [], + onFocus: handleFocus, + onBlur: handleBlur, + onSelect: handleSelectColumns, onRemove: handleRemoveSelectedColumn, + onSearch: handleSearchAttribute, }, format: { value: optionsQueryData.format || defaultOptionsQuery.format, @@ -175,11 +271,13 @@ const useOptionsMenu = ({ }, }), [ - addColumnOptions, + optionsFromAttributeKeys, optionsQueryData?.maxLines, optionsQueryData?.format, optionsQueryData?.selectColumns, - handleSelectedColumnsChange, + isSearchedAttributesFetching, + handleSearchAttribute, + handleSelectColumns, handleRemoveSelectedColumn, handleFormatChange, handleMaxLinesChange, @@ -187,7 +285,7 @@ const useOptionsMenu = ({ ); useEffect(() => { - if (optionsQuery || !isFetched) return; + if (optionsQuery || !isFetchedInitialAttributes) return; const nextOptionsQuery = localStorageOptionsQuery ? JSON.parse(localStorageOptionsQuery) @@ -195,15 +293,14 @@ const useOptionsMenu = ({ redirectWithOptionsData(nextOptionsQuery); }, [ - isFetched, + isFetchedInitialAttributes, optionsQuery, initialOptionsQuery, - redirectWithOptionsData, localStorageOptionsQuery, + redirectWithOptionsData, ]); return { - isLoading, options: optionsQueryData, config: optionsMenuConfig, }; diff --git a/frontend/src/container/OptionsMenu/utils.ts b/frontend/src/container/OptionsMenu/utils.ts index 1a0b904ac5..c33b2b553e 100644 --- a/frontend/src/container/OptionsMenu/utils.ts +++ b/frontend/src/container/OptionsMenu/utils.ts @@ -14,15 +14,3 @@ export const getOptionsFromKeys = ( ({ value }) => !selectedKeys.find((key) => key === value), ); }; - -export const getInitialColumns = ( - initialColumnTitles: string[], - attributeKeys: BaseAutocompleteData[], -): BaseAutocompleteData[] => - initialColumnTitles.reduce((acc, title) => { - const initialColumn = attributeKeys.find(({ key }) => title === key); - - if (!initialColumn) return acc; - - return [...acc, initialColumn]; - }, [] as BaseAutocompleteData[]); diff --git a/frontend/src/container/TracesExplorer/ListView/index.tsx b/frontend/src/container/TracesExplorer/ListView/index.tsx index faaad6a3ff..7869a5d2b5 100644 --- a/frontend/src/container/TracesExplorer/ListView/index.tsx +++ b/frontend/src/container/TracesExplorer/ListView/index.tsx @@ -65,7 +65,9 @@ function ListView(): JSX.Element { options?.selectColumns, ], enabled: - !!stagedQuery && panelType === PANEL_TYPES.LIST && !!options?.selectColumns, + !!stagedQuery && + panelType === PANEL_TYPES.LIST && + !!options?.selectColumns?.length, }, );