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
This commit is contained in:
dnazarenkoo 2023-07-07 13:29:45 +03:00 committed by GitHub
parent 1295e179b2
commit 857e505323
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 156 additions and 60 deletions

View File

@ -1,5 +1,5 @@
import { SearchOutlined } from '@ant-design/icons'; import { SearchOutlined } from '@ant-design/icons';
import { Input } from 'antd'; import { Input, Spin } from 'antd';
import Typography from 'antd/es/typography/Typography'; import Typography from 'antd/es/typography/Typography';
import { useIsDarkMode } from 'hooks/useDarkMode'; import { useIsDarkMode } from 'hooks/useDarkMode';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -26,12 +26,17 @@ function AddColumnField({ config }: AddColumnFieldProps): JSX.Element | null {
<Input.Group compact> <Input.Group compact>
<AddColumnSelect <AddColumnSelect
loading={config.isFetching}
size="small" size="small"
mode="multiple" mode="multiple"
placeholder="Search" placeholder="Search"
options={config.options} options={config.options}
value={[]} value={[]}
onChange={config.onChange} onSelect={config.onSelect}
onSearch={config.onSearch}
onFocus={config.onFocus}
onBlur={config.onBlur}
notFoundContent={config.isFetching ? <Spin size="small" /> : null}
/> />
<SearchIconWrapper $isDarkMode={isDarkMode}> <SearchIconWrapper $isDarkMode={isDarkMode}>
<SearchOutlined /> <SearchOutlined />

View File

@ -16,7 +16,11 @@ export interface InitialOptions
export type OptionsMenuConfig = { export type OptionsMenuConfig = {
format?: Pick<RadioProps, 'value' | 'onChange'>; format?: Pick<RadioProps, 'value' | 'onChange'>;
maxLines?: Pick<InputNumberProps, 'value' | 'onChange'>; maxLines?: Pick<InputNumberProps, 'value' | 'onChange'>;
addColumn?: Pick<SelectProps, 'options' | 'onChange'> & { addColumn?: Pick<
SelectProps,
'options' | 'onSelect' | 'onFocus' | 'onSearch' | 'onBlur'
> & {
isFetching: boolean;
value: BaseAutocompleteData[]; value: BaseAutocompleteData[];
onRemove: (key: string) => void; onRemove: (key: string) => void;
}; };

View File

@ -4,15 +4,21 @@ import setToLocalstorage from 'api/browser/localstorage/set';
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys'; import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
import { LOCALSTORAGE } from 'constants/localStorage'; import { LOCALSTORAGE } from 'constants/localStorage';
import { QueryBuilderKeys } from 'constants/queryBuilder'; import { QueryBuilderKeys } from 'constants/queryBuilder';
import useDebounce from 'hooks/useDebounce';
import { useNotifications } from 'hooks/useNotifications';
import useUrlQueryData from 'hooks/useUrlQueryData'; import useUrlQueryData from 'hooks/useUrlQueryData';
import { useCallback, useEffect, useMemo } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { useQuery } from 'react-query'; import { useQueries, useQuery } from 'react-query';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { ErrorResponse, SuccessResponse } from 'types/api';
import {
BaseAutocompleteData,
IQueryAutocompleteResponse,
} from 'types/api/queryBuilder/queryAutocompleteResponse';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import { defaultOptionsQuery, URL_OPTIONS } from './constants'; import { defaultOptionsQuery, URL_OPTIONS } from './constants';
import { InitialOptions, OptionsMenuConfig, OptionsQuery } from './types'; import { InitialOptions, OptionsMenuConfig, OptionsQuery } from './types';
import { getInitialColumns, getOptionsFromKeys } from './utils'; import { getOptionsFromKeys } from './utils';
interface UseOptionsMenuProps { interface UseOptionsMenuProps {
dataSource: DataSource; dataSource: DataSource;
@ -21,7 +27,6 @@ interface UseOptionsMenuProps {
} }
interface UseOptionsMenu { interface UseOptionsMenu {
isLoading: boolean;
options: OptionsQuery; options: OptionsQuery;
config: OptionsMenuConfig; config: OptionsMenuConfig;
} }
@ -31,41 +36,108 @@ const useOptionsMenu = ({
aggregateOperator, aggregateOperator,
initialOptions = {}, initialOptions = {},
}: UseOptionsMenuProps): UseOptionsMenu => { }: UseOptionsMenuProps): UseOptionsMenu => {
const { notifications } = useNotifications();
const [searchText, setSearchText] = useState<string>('');
const [isFocused, setIsFocused] = useState<boolean>(false);
const debouncedSearchText = useDebounce(searchText, 300);
const localStorageOptionsQuery = getFromLocalstorage( const localStorageOptionsQuery = getFromLocalstorage(
LOCALSTORAGE.LIST_OPTIONS, LOCALSTORAGE.LIST_OPTIONS,
); );
const initialQueryParams = useMemo(
() => ({
searchText: '',
aggregateAttribute: '',
tagType: null,
dataSource,
aggregateOperator,
}),
[dataSource, aggregateOperator],
);
const { const {
query: optionsQuery, query: optionsQuery,
queryData: optionsQueryData, queryData: optionsQueryData,
redirectWithQuery: redirectWithOptionsData, redirectWithQuery: redirectWithOptionsData,
} = useUrlQueryData<OptionsQuery>(URL_OPTIONS, defaultOptionsQuery); } = useUrlQueryData<OptionsQuery>(URL_OPTIONS, defaultOptionsQuery);
const { data, isFetched, isLoading } = useQuery( const initialQueries = useMemo(
[QueryBuilderKeys.GET_ATTRIBUTE_KEY, dataSource, aggregateOperator], () =>
async () => initialOptions?.selectColumns?.map((column) => ({
getAggregateKeys({ queryKey: column,
searchText: '', queryFn: (): Promise<
dataSource, SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
aggregateOperator, > =>
aggregateAttribute: '', getAggregateKeys({
tagType: null, ...initialQueryParams,
}), searchText: column,
}),
enabled: !!column && !optionsQuery,
})) || [],
[initialOptions?.selectColumns, initialQueryParams, optionsQuery],
); );
const attributeKeys = useMemo(() => data?.payload?.attributeKeys || [], [ const initialAttributesResult = useQueries(initialQueries);
data?.payload?.attributeKeys,
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( const initialOptionsQuery: OptionsQuery = useMemo(
() => ({ () => ({
...defaultOptionsQuery, ...defaultOptionsQuery,
...initialOptions, ...initialOptions,
selectColumns: initialOptions?.selectColumns selectColumns: initialOptions?.selectColumns
? getInitialColumns(initialOptions?.selectColumns || [], attributeKeys) ? initialSelectedColumns
: defaultOptionsQuery.selectColumns, : defaultOptionsQuery.selectColumns,
}), }),
[initialOptions, attributeKeys], [initialOptions, initialSelectedColumns],
); );
const selectedColumnKeys = useMemo( const selectedColumnKeys = useMemo(
@ -73,13 +145,13 @@ const useOptionsMenu = ({
[optionsQueryData], [optionsQueryData],
); );
const addColumnOptions = useMemo(() => { const optionsFromAttributeKeys = useMemo(() => {
const filteredAttributeKeys = attributeKeys.filter( const filteredAttributeKeys = searchedAttributeKeys.filter(
(item) => item.key !== 'body', (item) => item.key !== 'body',
); );
return getOptionsFromKeys(filteredAttributeKeys, selectedColumnKeys); return getOptionsFromKeys(filteredAttributeKeys, selectedColumnKeys);
}, [attributeKeys, selectedColumnKeys]); }, [searchedAttributeKeys, selectedColumnKeys]);
const handleRedirectWithOptionsData = useCallback( const handleRedirectWithOptionsData = useCallback(
(newQueryData: OptionsQuery) => { (newQueryData: OptionsQuery) => {
@ -90,13 +162,14 @@ const useOptionsMenu = ({
[redirectWithOptionsData], [redirectWithOptionsData],
); );
const handleSelectedColumnsChange = useCallback( const handleSelectColumns = useCallback(
(value: string[]) => { (value: string) => {
const newSelectedColumnKeys = [ const newSelectedColumnKeys = [...new Set([...selectedColumnKeys, value])];
...new Set([...selectedColumnKeys, ...value]),
];
const newSelectedColumns = newSelectedColumnKeys.reduce((acc, key) => { 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; if (!column) return acc;
return [...acc, column]; return [...acc, column];
@ -110,10 +183,10 @@ const useOptionsMenu = ({
handleRedirectWithOptionsData(optionsData); handleRedirectWithOptionsData(optionsData);
}, },
[ [
searchedAttributeKeys,
selectedColumnKeys, selectedColumnKeys,
optionsQueryData, optionsQueryData,
handleRedirectWithOptionsData, handleRedirectWithOptionsData,
attributeKeys,
], ],
); );
@ -123,14 +196,20 @@ const useOptionsMenu = ({
({ id }) => id !== columnKey, ({ id }) => id !== columnKey,
); );
const optionsData: OptionsQuery = { if (!newSelectedColumns.length && dataSource !== DataSource.LOGS) {
...optionsQueryData, notifications.error({
selectColumns: newSelectedColumns, 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( const handleFormatChange = useCallback(
@ -157,13 +236,30 @@ const useOptionsMenu = ({
[handleRedirectWithOptionsData, optionsQueryData], [handleRedirectWithOptionsData, optionsQueryData],
); );
const handleSearchAttribute = useCallback((value: string) => {
setSearchText(value);
}, []);
const handleFocus = (): void => {
setIsFocused(true);
};
const handleBlur = (): void => {
setIsFocused(false);
setSearchText('');
};
const optionsMenuConfig: Required<OptionsMenuConfig> = useMemo( const optionsMenuConfig: Required<OptionsMenuConfig> = useMemo(
() => ({ () => ({
addColumn: { addColumn: {
value: optionsQueryData.selectColumns || defaultOptionsQuery.selectColumns, isFetching: isSearchedAttributesFetching,
options: addColumnOptions || [], value: optionsQueryData?.selectColumns || defaultOptionsQuery.selectColumns,
onChange: handleSelectedColumnsChange, options: optionsFromAttributeKeys || [],
onFocus: handleFocus,
onBlur: handleBlur,
onSelect: handleSelectColumns,
onRemove: handleRemoveSelectedColumn, onRemove: handleRemoveSelectedColumn,
onSearch: handleSearchAttribute,
}, },
format: { format: {
value: optionsQueryData.format || defaultOptionsQuery.format, value: optionsQueryData.format || defaultOptionsQuery.format,
@ -175,11 +271,13 @@ const useOptionsMenu = ({
}, },
}), }),
[ [
addColumnOptions, optionsFromAttributeKeys,
optionsQueryData?.maxLines, optionsQueryData?.maxLines,
optionsQueryData?.format, optionsQueryData?.format,
optionsQueryData?.selectColumns, optionsQueryData?.selectColumns,
handleSelectedColumnsChange, isSearchedAttributesFetching,
handleSearchAttribute,
handleSelectColumns,
handleRemoveSelectedColumn, handleRemoveSelectedColumn,
handleFormatChange, handleFormatChange,
handleMaxLinesChange, handleMaxLinesChange,
@ -187,7 +285,7 @@ const useOptionsMenu = ({
); );
useEffect(() => { useEffect(() => {
if (optionsQuery || !isFetched) return; if (optionsQuery || !isFetchedInitialAttributes) return;
const nextOptionsQuery = localStorageOptionsQuery const nextOptionsQuery = localStorageOptionsQuery
? JSON.parse(localStorageOptionsQuery) ? JSON.parse(localStorageOptionsQuery)
@ -195,15 +293,14 @@ const useOptionsMenu = ({
redirectWithOptionsData(nextOptionsQuery); redirectWithOptionsData(nextOptionsQuery);
}, [ }, [
isFetched, isFetchedInitialAttributes,
optionsQuery, optionsQuery,
initialOptionsQuery, initialOptionsQuery,
redirectWithOptionsData,
localStorageOptionsQuery, localStorageOptionsQuery,
redirectWithOptionsData,
]); ]);
return { return {
isLoading,
options: optionsQueryData, options: optionsQueryData,
config: optionsMenuConfig, config: optionsMenuConfig,
}; };

View File

@ -14,15 +14,3 @@ export const getOptionsFromKeys = (
({ value }) => !selectedKeys.find((key) => key === value), ({ 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[]);

View File

@ -65,7 +65,9 @@ function ListView(): JSX.Element {
options?.selectColumns, options?.selectColumns,
], ],
enabled: enabled:
!!stagedQuery && panelType === PANEL_TYPES.LIST && !!options?.selectColumns, !!stagedQuery &&
panelType === PANEL_TYPES.LIST &&
!!options?.selectColumns?.length,
}, },
); );