diff --git a/frontend/src/constants/queryBuilder.ts b/frontend/src/constants/queryBuilder.ts index 2ab6d365fd..c53873bc5c 100644 --- a/frontend/src/constants/queryBuilder.ts +++ b/frontend/src/constants/queryBuilder.ts @@ -278,35 +278,23 @@ export const QUERY_BUILDER_SEARCH_VALUES = { export const OPERATORS = { IN: 'IN', - in: 'in', NIN: 'NOT_IN', - not_in: 'not_in', LIKE: 'LIKE', - like: 'like', NLIKE: 'NOT_LIKE', - not_like: 'not_like', REGEX: 'REGEX', - regex: 'regex', NREGEX: 'NOT_REGEX', - nregex: 'not_regex', '=': '=', '!=': '!=', EXISTS: 'EXISTS', - exists: 'exists', NOT_EXISTS: 'NOT_EXISTS', - not_exists: 'not_exists', CONTAINS: 'CONTAINS', - contains: 'contains', NOT_CONTAINS: 'NOT_CONTAINS', - not_contains: 'not_contains', '>=': '>=', '>': '>', '<=': '<=', '<': '<', HAS: 'HAS', - has: 'has', NHAS: 'NHAS', - nhas: 'nhas', }; export const QUERY_BUILDER_OPERATORS_BY_TYPES = { diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/utils.ts b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/utils.ts index dbe95d631f..1859254fc4 100644 --- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/utils.ts +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/utils.ts @@ -3,42 +3,25 @@ import { parse } from 'papaparse'; import { orderByValueDelimiter } from '../OrderByFilter/utils'; -const operators = /=|!=|>=|>|<=|<$/; - // eslint-disable-next-line no-useless-escape -export const tagRegexpV1 = /^\s*(.*?)\s*(IN|in|NOT_IN|nin|LIKE|like|NOT_LIKE|nlike|REGEX|regex|NOT_REGEX|nregex|=|!=|EXISTS|exists|NOT_EXISTS|nexists|CONTAINS|contains|NOT_CONTAINS|ncontains|>=|>|<=|<|HAS|has|NHAS|nhas)\s*(.*)$/g; - -export const tagRegexpV2 = /^\s*(.+?)\s+(IN|in|NOT_IN|nin|LIKE|like|NOT_LIKE|nlike|REGEX|regex|NOT_REGEX|nregex|EXISTS|exists|NOT_EXISTS|nexists|CONTAINS|contains|NOT_CONTAINS|ncontains|HAS|has|NHAS|nhas|=|!=|>=|>|<=|<)\s*(.*)$/g; +export const tagRegexp = /^\s*(.*?)\s*(IN|NOT_IN|LIKE|NOT_LIKE|REGEX|NOT_REGEX|=|!=|EXISTS|NOT_EXISTS|CONTAINS|NOT_CONTAINS|>=|>|<=|<|HAS|NHAS)\s*(.*)$/g; export function isInNInOperator(value: string): boolean { return value === OPERATORS.IN || value === OPERATORS.NIN; } -function endsWithOperator(inputString: string): boolean { - return operators.test(inputString); -} - interface ITagToken { tagKey: string; tagOperator: string; tagValue: string[]; } -export function getMatchRegex(str: string): RegExp { - if (endsWithOperator(str)) { - return tagRegexpV1; - } - - return tagRegexpV2; -} - export function getTagToken(tag: string): ITagToken { - const matches = tag?.matchAll(getMatchRegex(tag)); + const matches = tag?.matchAll(tagRegexp); const [match] = matches ? Array.from(matches) : []; if (match) { const [, matchTagKey, matchTagOperator, matchTagValue] = match; - return { tagKey: matchTagKey, tagOperator: matchTagOperator, @@ -68,11 +51,65 @@ export function getRemovePrefixFromKey(tag: string): string { } export function getOperatorValue(op: string): string { - return op.toLocaleLowerCase(); + switch (op) { + case 'IN': + return 'in'; + case 'NOT_IN': + return 'nin'; + case OPERATORS.REGEX: + return 'regex'; + case OPERATORS.HAS: + return 'has'; + case OPERATORS.NHAS: + return 'nhas'; + case OPERATORS.NREGEX: + return 'nregex'; + case 'LIKE': + return 'like'; + case 'NOT_LIKE': + return 'nlike'; + case 'EXISTS': + return 'exists'; + case 'NOT_EXISTS': + return 'nexists'; + case 'CONTAINS': + return 'contains'; + case 'NOT_CONTAINS': + return 'ncontains'; + default: + return op; + } } export function getOperatorFromValue(op: string): string { - return op.toLocaleLowerCase(); + switch (op) { + case 'in': + return 'IN'; + case 'nin': + return 'NOT_IN'; + case 'like': + return 'LIKE'; + case 'regex': + return OPERATORS.REGEX; + case 'nregex': + return OPERATORS.NREGEX; + case 'nlike': + return 'NOT_LIKE'; + case 'exists': + return 'EXISTS'; + case 'nexists': + return 'NOT_EXISTS'; + case 'contains': + return 'CONTAINS'; + case 'ncontains': + return 'NOT_CONTAINS'; + case 'has': + return OPERATORS.HAS; + case 'nhas': + return OPERATORS.NHAS; + default: + return op; + } } export function replaceStringWithMaxLength( diff --git a/frontend/src/hooks/queryBuilder/useAutoComplete.ts b/frontend/src/hooks/queryBuilder/useAutoComplete.ts index 9562f5e4d7..dad262757a 100644 --- a/frontend/src/hooks/queryBuilder/useAutoComplete.ts +++ b/frontend/src/hooks/queryBuilder/useAutoComplete.ts @@ -1,8 +1,8 @@ import { - getMatchRegex, getRemovePrefixFromKey, getTagToken, replaceStringWithMaxLength, + tagRegexp, } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils'; import { Option } from 'container/QueryBuilder/type'; import { parse } from 'papaparse'; @@ -33,7 +33,7 @@ export const useAutoComplete = ( searchKey, ); - const [key, operator, result] = useSetCurrentKeyAndOperator(searchValue); + const [key, operator, result] = useSetCurrentKeyAndOperator(searchValue, keys); const handleSearch = (value: string): void => { const prefixFreeValue = getRemovePrefixFromKey(getTagToken(value).tagKey); @@ -58,7 +58,7 @@ export const useAutoComplete = ( (value: string): void => { if (isMulti) { setSearchValue((prev: string) => { - const matches = prev?.matchAll(getMatchRegex(prev)); + const matches = prev?.matchAll(tagRegexp); const [match] = matches ? Array.from(matches) : []; const [, , , matchTagValue] = match; const data = parse(matchTagValue).data.flat(); diff --git a/frontend/src/hooks/queryBuilder/useOperatorType.ts b/frontend/src/hooks/queryBuilder/useOperatorType.ts index ff3a4ad115..94de55df92 100644 --- a/frontend/src/hooks/queryBuilder/useOperatorType.ts +++ b/frontend/src/hooks/queryBuilder/useOperatorType.ts @@ -8,35 +8,23 @@ export type OperatorType = const operatorTypeMapper: Record = { [OPERATORS.IN]: 'MULTIPLY_VALUE', - [OPERATORS.in]: 'MULTIPLY_VALUE', [OPERATORS.NIN]: 'MULTIPLY_VALUE', - [OPERATORS.not_in]: 'MULTIPLY_VALUE', [OPERATORS.EXISTS]: 'NON_VALUE', - [OPERATORS.exists]: 'NON_VALUE', [OPERATORS.NOT_EXISTS]: 'NON_VALUE', - [OPERATORS.not_exists]: 'NON_VALUE', [OPERATORS['<=']]: 'SINGLE_VALUE', [OPERATORS['<']]: 'SINGLE_VALUE', [OPERATORS['>=']]: 'SINGLE_VALUE', [OPERATORS['>']]: 'SINGLE_VALUE', [OPERATORS.LIKE]: 'SINGLE_VALUE', - [OPERATORS.like]: 'SINGLE_VALUE', [OPERATORS.NLIKE]: 'SINGLE_VALUE', - [OPERATORS.not_like]: 'SINGLE_VALUE', [OPERATORS.REGEX]: 'SINGLE_VALUE', - [OPERATORS.regex]: 'SINGLE_VALUE', [OPERATORS.NREGEX]: 'SINGLE_VALUE', - [OPERATORS.nregex]: 'SINGLE_VALUE', [OPERATORS.CONTAINS]: 'SINGLE_VALUE', - [OPERATORS.contains]: 'SINGLE_VALUE', [OPERATORS.NOT_CONTAINS]: 'SINGLE_VALUE', - [OPERATORS.not_contains]: 'SINGLE_VALUE', [OPERATORS['=']]: 'SINGLE_VALUE', [OPERATORS['!=']]: 'SINGLE_VALUE', [OPERATORS.HAS]: 'SINGLE_VALUE', - [OPERATORS.has]: 'SINGLE_VALUE', [OPERATORS.NHAS]: 'SINGLE_VALUE', - [OPERATORS.nhas]: 'SINGLE_VALUE', }; export const useOperatorType = (operator: string): OperatorType => diff --git a/frontend/src/hooks/queryBuilder/useOptions.ts b/frontend/src/hooks/queryBuilder/useOptions.ts index 548ea2de0b..82dc2c1e24 100644 --- a/frontend/src/hooks/queryBuilder/useOptions.ts +++ b/frontend/src/hooks/queryBuilder/useOptions.ts @@ -81,8 +81,8 @@ export const useOptions = ( const getKeyOperatorOptions = useCallback( (key: string) => { const operatorsOptions = operators?.map((operator) => ({ - value: `${key} ${operator.toLowerCase()} `, - label: `${key} ${operator.toLowerCase()} `, + value: `${key} ${operator} `, + label: `${key} ${operator} `, })); if (whereClauseConfig) { return [ @@ -148,28 +148,26 @@ export const useOptions = ( return useMemo( () => - options - .filter( + ( + options.filter( (option, index, self) => index === self.findIndex( (o) => o.label === option.label && o.value === option.value, // to remove duplicate & empty options from list ) && option.value !== '', - ) - .map((option) => { - const { tagValue } = getTagToken(searchValue); - if (isMulti) { - return { - ...option, - selected: Array.isArray(tagValue) - ? tagValue - ?.filter((i) => i.trim().replace(/^\s+/, '') === option.value) - ?.includes(option.value) - : String(tagValue).includes(option.value), - }; - } - return option; - }), + ) || [] + ).map((option) => { + const { tagValue } = getTagToken(searchValue); + if (isMulti) { + return { + ...option, + selected: tagValue + .filter((i) => i.trim().replace(/^\s+/, '') === option.value) + .includes(option.value), + }; + } + return option; + }), [isMulti, options, searchValue], ); }; diff --git a/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts b/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts index afaaa8ccab..2da205b349 100644 --- a/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts +++ b/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts @@ -1,24 +1,32 @@ -import { getTagToken } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils'; -import { useMemo, useRef } from 'react'; +import { + getRemovePrefixFromKey, + getTagToken, +} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils'; +import { useMemo } from 'react'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; type ICurrentKeyAndOperator = [string, string, string[]]; export const useSetCurrentKeyAndOperator = ( value: string, + keys: BaseAutocompleteData[], ): ICurrentKeyAndOperator => { - const keyRef = useRef(''); - const operatorRef = useRef(''); - - const result = useMemo(() => { + const [key, operator, result] = useMemo(() => { + let key = ''; + let operator = ''; let result: string[] = []; 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 || []; + } - keyRef.current = tagKey || ''; - operatorRef.current = tagOperator || ''; - result = tagValue || []; - - return result; - }, [value]); + return [key, operator, result]; + }, [value, keys]); - return [keyRef.current, operatorRef.current, result]; + return [key, operator, result]; }; diff --git a/frontend/src/hooks/queryBuilder/useTag.ts b/frontend/src/hooks/queryBuilder/useTag.ts index 45a9417403..268a01e0c6 100644 --- a/frontend/src/hooks/queryBuilder/useTag.ts +++ b/frontend/src/hooks/queryBuilder/useTag.ts @@ -74,7 +74,6 @@ export const useTag = ( const handleAddTag = useCallback( (value: string): void => { const { tagKey } = getTagToken(value); - const [key, id] = tagKey.split('-'); if (id === 'custom') {