fix: custom where clause value (#3209)

* fix: custom where clause value

* fix: operations

* fix: return suggestions for body

---------

Co-authored-by: Palash Gupta <palashgdev@gmail.com>
This commit is contained in:
Yevhen Shevchenko 2023-07-27 10:22:44 +03:00 committed by GitHub
parent 872759f579
commit 2c5c972801
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 143 additions and 38 deletions

View File

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

View File

@ -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<IBuilderQuery, 'filters'>]: {
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<keyof IBuilderQuery, { isHidden: boolean; isDisabled: boolean }>
>;
filterConfigs?: Partial<FilterConfigs>;
queryComponents?: { renderOrderBy?: (props: OrderByFilterProps) => ReactNode };
};

View File

@ -305,7 +305,11 @@ export const Query = memo(function Query({
</Col>
)}
<Col flex="1">
<QueryBuilderSearch query={query} onChange={handleChangeTagFilters} />
<QueryBuilderSearch
query={query}
onChange={handleChangeTagFilters}
whereClauseConfig={filterConfigs?.filters}
/>
</Col>
</Row>
</Col>

View File

@ -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 ? <Spin size="small" /> : null}
>
{options.map((option) => (
<Select.Option key={option.label} value={option.label}>
<Select.Option key={option.label} value={option.value}>
{option.label}
{option.selected && <StyledCheckOutlined />}
</Select.Option>
@ -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;

View File

@ -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<string>('');
const [searchKey, setSearchKey] = useState<string>('');
@ -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 {

View File

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

View File

@ -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<string[]>(
(acc, item) => {
if (filterConfigs && filterConfigs[item.field]?.isHidden) {
if (
filterConfigs &&
filterConfigs[item.field as typeof additionalFiltersKeys[number]]
?.isHidden
) {
return acc;
}

View File

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

View File

@ -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],
);
/**