From 2c5c97280196edd00d5e70043ed1a86311bb5669 Mon Sep 17 00:00:00 2001 From: Yevhen Shevchenko <90138953+yeshev@users.noreply.github.com> Date: Thu, 27 Jul 2023 10:22:44 +0300 Subject: [PATCH] fix: custom where clause value (#3209) * fix: custom where clause value * fix: operations * fix: return suggestions for body --------- Co-authored-by: Palash Gupta --- .../LogExplorerQuerySection/index.tsx | 10 ++- .../QueryBuilder/QueryBuilder.interfaces.ts | 12 +++- .../QueryBuilder/components/Query/Query.tsx | 6 +- .../filters/QueryBuilderSearch/index.tsx | 15 ++++- .../src/hooks/queryBuilder/useAutoComplete.ts | 19 ++++-- frontend/src/hooks/queryBuilder/useOptions.ts | 66 ++++++++++++++++--- .../hooks/queryBuilder/useQueryOperations.ts | 11 +++- .../useSetCurrentKeyAndOperator.ts | 19 +++--- frontend/src/hooks/queryBuilder/useTag.ts | 23 ++++++- 9 files changed, 143 insertions(+), 38 deletions(-) diff --git a/frontend/src/container/LogExplorerQuerySection/index.tsx b/frontend/src/container/LogExplorerQuerySection/index.tsx index 3cd972c2fa..234d2b589f 100644 --- a/frontend/src/container/LogExplorerQuerySection/index.tsx +++ b/frontend/src/container/LogExplorerQuerySection/index.tsx @@ -1,5 +1,9 @@ import { Button } from 'antd'; -import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; +import { + initialQueriesMap, + OPERATORS, + PANEL_TYPES, +} from 'constants/queryBuilder'; import ExplorerOrderBy from 'container/ExplorerOrderBy'; import { QueryBuilder } from 'container/QueryBuilder'; import { OrderByFilterProps } from 'container/QueryBuilder/filters/OrderByFilter/OrderByFilter.interfaces'; @@ -30,6 +34,10 @@ function LogExplorerQuerySection(): JSX.Element { const isTable = panelTypes === PANEL_TYPES.TABLE; const config: QueryBuilderProps['filterConfigs'] = { stepInterval: { isHidden: isTable, isDisabled: true }, + filters: { + customKey: 'body', + customOp: OPERATORS.CONTAINS, + }, }; return config; diff --git a/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts b/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts index 4758ad882b..0923395296 100644 --- a/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts +++ b/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts @@ -1,10 +1,18 @@ import { PANEL_TYPES } from 'constants/queryBuilder'; +import { WhereClauseConfig } from 'hooks/queryBuilder/useAutoComplete'; import { ReactNode } from 'react'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; import { OrderByFilterProps } from './filters/OrderByFilter/OrderByFilter.interfaces'; +type FilterConfigs = { + [Key in keyof Omit]: { + isHidden: boolean; + isDisabled: boolean; + }; +} & { filters: WhereClauseConfig }; + export type QueryBuilderConfig = | { queryVariant: 'static'; @@ -16,8 +24,6 @@ export type QueryBuilderProps = { config?: QueryBuilderConfig; panelType: PANEL_TYPES; actions?: ReactNode; - filterConfigs?: Partial< - Record - >; + filterConfigs?: Partial; queryComponents?: { renderOrderBy?: (props: OrderByFilterProps) => ReactNode }; }; diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.tsx b/frontend/src/container/QueryBuilder/components/Query/Query.tsx index e60c562200..4418daec5e 100644 --- a/frontend/src/container/QueryBuilder/components/Query/Query.tsx +++ b/frontend/src/container/QueryBuilder/components/Query/Query.tsx @@ -305,7 +305,11 @@ export const Query = memo(function Query({ )} - + diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx index 8b3e8e54ea..7111c1b0e8 100644 --- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx @@ -1,5 +1,8 @@ import { Select, Spin, Tag, Tooltip } from 'antd'; -import { useAutoComplete } from 'hooks/queryBuilder/useAutoComplete'; +import { + useAutoComplete, + WhereClauseConfig, +} from 'hooks/queryBuilder/useAutoComplete'; import { useFetchKeysAndValues } from 'hooks/queryBuilder/useFetchKeysAndValues'; import { KeyboardEvent, @@ -31,6 +34,7 @@ import { function QueryBuilderSearch({ query, onChange, + whereClauseConfig, }: QueryBuilderSearchProps): JSX.Element { const { updateTag, @@ -45,7 +49,7 @@ function QueryBuilderSearch({ isFetching, setSearchKey, searchKey, - } = useAutoComplete(query); + } = useAutoComplete(query, whereClauseConfig); const { sourceKeys, handleRemoveSourceKey } = useFetchKeysAndValues( searchValue, @@ -169,7 +173,7 @@ function QueryBuilderSearch({ notFoundContent={isFetching ? : null} > {options.map((option) => ( - + {option.label} {option.selected && } @@ -181,8 +185,13 @@ function QueryBuilderSearch({ interface QueryBuilderSearchProps { query: IBuilderQuery; onChange: (value: TagFilter) => void; + whereClauseConfig?: WhereClauseConfig; } +QueryBuilderSearch.defaultProps = { + whereClauseConfig: undefined, +}; + export interface CustomTagProps { label: ReactNode; value: string; diff --git a/frontend/src/hooks/queryBuilder/useAutoComplete.ts b/frontend/src/hooks/queryBuilder/useAutoComplete.ts index c7519761ec..e326a0e33f 100644 --- a/frontend/src/hooks/queryBuilder/useAutoComplete.ts +++ b/frontend/src/hooks/queryBuilder/useAutoComplete.ts @@ -1,7 +1,6 @@ import { getRemovePrefixFromKey, getTagToken, - isExistsNotExistsOperator, replaceStringWithMaxLength, tagRegexp, } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils'; @@ -16,7 +15,15 @@ import { useSetCurrentKeyAndOperator } from './useSetCurrentKeyAndOperator'; import { useTag } from './useTag'; import { useTagValidation } from './useTagValidation'; -export const useAutoComplete = (query: IBuilderQuery): IAutoComplete => { +export type WhereClauseConfig = { + customKey: string; + customOp: string; +}; + +export const useAutoComplete = ( + query: IBuilderQuery, + whereClauseConfig?: WhereClauseConfig, +): IAutoComplete => { const [searchValue, setSearchValue] = useState(''); const [searchKey, setSearchKey] = useState(''); @@ -40,11 +47,11 @@ export const useAutoComplete = (query: IBuilderQuery): IAutoComplete => { ); const { handleAddTag, handleClearTag, tags, updateTag } = useTag( - key, isValidTag, handleSearch, query, setSearchKey, + whereClauseConfig, ); const handleSelect = useCallback( @@ -59,11 +66,10 @@ export const useAutoComplete = (query: IBuilderQuery): IAutoComplete => { }); } if (!isMulti) { - if (isExistsNotExistsOperator(value)) handleAddTag(value); - if (isValidTag && !isExistsNotExistsOperator(value)) handleAddTag(value); + handleAddTag(value); } }, - [handleAddTag, isMulti, isValidTag], + [handleAddTag, isMulti], ); const handleKeyDown = useCallback( @@ -102,6 +108,7 @@ export const useAutoComplete = (query: IBuilderQuery): IAutoComplete => { isExist, results, result, + whereClauseConfig, ); return { diff --git a/frontend/src/hooks/queryBuilder/useOptions.ts b/frontend/src/hooks/queryBuilder/useOptions.ts index 07a326852f..82dc2c1e24 100644 --- a/frontend/src/hooks/queryBuilder/useOptions.ts +++ b/frontend/src/hooks/queryBuilder/useOptions.ts @@ -7,8 +7,11 @@ import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { WhereClauseConfig } from './useAutoComplete'; import { useOperators } from './useOperators'; +export const WHERE_CLAUSE_CUSTOM_SUFFIX = '-custom'; + export const useOptions = ( key: string, keys: BaseAutocompleteData[], @@ -19,6 +22,7 @@ export const useOptions = ( isExist: boolean, results: string[], result: string[], + whereClauseConfig?: WhereClauseConfig, ): Option[] => { const [options, setOptions] = useState([]); const operators = useOperators(key, keys); @@ -51,21 +55,64 @@ export const useOptions = ( [key, operator], ); + const getOptionsWithValidOperator = useCallback( + (key: string, results: string[], searchValue: string) => { + const hasAllResults = results.every((value) => result.includes(value)); + const values = getKeyOpValue(results); + + return hasAllResults + ? [ + { + label: searchValue, + value: searchValue, + }, + ] + : [ + { + label: searchValue, + value: searchValue, + }, + ...values, + ]; + }, + [getKeyOpValue, result], + ); + + const getKeyOperatorOptions = useCallback( + (key: string) => { + const operatorsOptions = operators?.map((operator) => ({ + value: `${key} ${operator} `, + label: `${key} ${operator} `, + })); + if (whereClauseConfig) { + return [ + { + label: `${searchValue} `, + value: `${searchValue}${WHERE_CLAUSE_CUSTOM_SUFFIX}`, + }, + ...operatorsOptions, + ]; + } + return operatorsOptions; + }, + [operators, searchValue, whereClauseConfig], + ); + useEffect(() => { let newOptions: Option[] = []; if (!key) { newOptions = searchValue ? [ - { label: `${searchValue} `, value: `${searchValue} ` }, + { + label: `${searchValue} `, + value: `${searchValue} `, + }, ...getOptionsFromKeys(keys), ] : getOptionsFromKeys(keys); } else if (key && !operator) { - newOptions = operators?.map((operator) => ({ - value: `${key} ${operator} `, - label: `${key} ${operator} `, - })); + newOptions = getKeyOperatorOptions(key); } else if (key && operator) { if (isMulti) { newOptions = results.map((item) => ({ @@ -75,17 +122,14 @@ export const useOptions = ( } else if (isExist) { newOptions = []; } else if (isValidOperator) { - const hasAllResults = results.every((value) => result.includes(value)); - const values = getKeyOpValue(results); - newOptions = hasAllResults - ? [{ label: searchValue, value: searchValue }] - : [{ label: searchValue, value: searchValue }, ...values]; + newOptions = getOptionsWithValidOperator(key, results, searchValue); } } if (newOptions.length > 0) { setOptions(newOptions); } }, [ + whereClauseConfig, getKeyOpValue, getOptionsFromKeys, isExist, @@ -98,6 +142,8 @@ export const useOptions = ( result, results, searchValue, + getKeyOperatorOptions, + getOptionsWithValidOperator, ]); return useMemo( diff --git a/frontend/src/hooks/queryBuilder/useQueryOperations.ts b/frontend/src/hooks/queryBuilder/useQueryOperations.ts index 04c2002d30..8e0251d0b5 100644 --- a/frontend/src/hooks/queryBuilder/useQueryOperations.ts +++ b/frontend/src/hooks/queryBuilder/useQueryOperations.ts @@ -63,9 +63,18 @@ export const useQueryOperations: UseQueryOperations = ({ const getNewListOfAdditionalFilters = useCallback( (dataSource: DataSource): string[] => { + const additionalFiltersKeys: (keyof Pick< + IBuilderQuery, + 'orderBy' | 'limit' | 'having' | 'stepInterval' + >)[] = ['having', 'limit', 'orderBy', 'stepInterval']; + const result: string[] = mapOfFilters[dataSource].reduce( (acc, item) => { - if (filterConfigs && filterConfigs[item.field]?.isHidden) { + if ( + filterConfigs && + filterConfigs[item.field as typeof additionalFiltersKeys[number]] + ?.isHidden + ) { return acc; } diff --git a/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts b/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts index c848373b6c..2da205b349 100644 --- a/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts +++ b/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts @@ -15,17 +15,14 @@ export const useSetCurrentKeyAndOperator = ( let key = ''; let operator = ''; let result: string[] = []; - - if (value) { - const { tagKey, tagOperator, tagValue } = getTagToken(value); - const isSuggestKey = keys?.some( - (el) => el?.key === getRemovePrefixFromKey(tagKey), - ); - if (isSuggestKey || keys.length === 0) { - key = tagKey || ''; - operator = tagOperator || ''; - result = tagValue || []; - } + const { tagKey, tagOperator, tagValue } = getTagToken(value); + const isSuggestKey = keys?.some( + (el) => el?.key === getRemovePrefixFromKey(tagKey), + ); + if (isSuggestKey || keys.length === 0) { + key = tagKey || ''; + operator = tagOperator || ''; + result = tagValue || []; } return [key, operator, result]; diff --git a/frontend/src/hooks/queryBuilder/useTag.ts b/frontend/src/hooks/queryBuilder/useTag.ts index 3bc272fc86..704d8796c1 100644 --- a/frontend/src/hooks/queryBuilder/useTag.ts +++ b/frontend/src/hooks/queryBuilder/useTag.ts @@ -1,5 +1,6 @@ import { getOperatorFromValue, + getTagToken, isExistsNotExistsOperator, isInNInOperator, } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils'; @@ -8,6 +9,8 @@ import * as Papa from 'papaparse'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { WhereClauseConfig } from './useAutoComplete'; + type IUseTag = { handleAddTag: (value: string) => void; handleClearTag: (value: string) => void; @@ -24,11 +27,11 @@ type IUseTag = { */ export const useTag = ( - key: string, isValidTag: boolean, handleSearch: (value: string) => void, query: IBuilderQuery, setSearchKey: (value: string) => void, + whereClauseConfig?: WhereClauseConfig, ): IUseTag => { const initTagsData = useMemo( () => @@ -57,15 +60,31 @@ export const useTag = ( * Adds a new tag to the tag list. * @param {string} value - The tag value to be added. */ + const handleAddTag = useCallback( (value: string): void => { + const { tagKey } = getTagToken(value); + const [key, id] = tagKey.split('-'); + + if (id === 'custom') { + const customValue = whereClauseConfig + ? `${whereClauseConfig.customKey} ${whereClauseConfig.customOp} ${key}` + : ''; + setTags((prevTags) => + prevTags.includes(customValue) ? prevTags : [...prevTags, customValue], + ); + handleSearch(''); + setSearchKey(''); + return; + } + if ((value && key && isValidTag) || isExistsNotExistsOperator(value)) { setTags((prevTags) => [...prevTags, value]); handleSearch(''); setSearchKey(''); } }, - [key, isValidTag, handleSearch, setSearchKey], + [whereClauseConfig, isValidTag, handleSearch, setSearchKey], ); /**