diff --git a/frontend/src/constants/queryBuilder.ts b/frontend/src/constants/queryBuilder.ts index c53873bc5c..2ab6d365fd 100644 --- a/frontend/src/constants/queryBuilder.ts +++ b/frontend/src/constants/queryBuilder.ts @@ -278,23 +278,35 @@ 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 1859254fc4..dbe95d631f 100644 --- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/utils.ts +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/utils.ts @@ -3,25 +3,42 @@ import { parse } from 'papaparse'; import { orderByValueDelimiter } from '../OrderByFilter/utils'; +const operators = /=|!=|>=|>|<=|<$/; + // eslint-disable-next-line no-useless-escape -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 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 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(tagRegexp); + const matches = tag?.matchAll(getMatchRegex(tag)); const [match] = matches ? Array.from(matches) : []; if (match) { const [, matchTagKey, matchTagOperator, matchTagValue] = match; + return { tagKey: matchTagKey, tagOperator: matchTagOperator, @@ -51,65 +68,11 @@ export function getRemovePrefixFromKey(tag: string): string { } export function getOperatorValue(op: string): string { - 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; - } + return op.toLocaleLowerCase(); } export function getOperatorFromValue(op: string): string { - 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; - } + return op.toLocaleLowerCase(); } export function replaceStringWithMaxLength( diff --git a/frontend/src/hooks/queryBuilder/useAutoComplete.ts b/frontend/src/hooks/queryBuilder/useAutoComplete.ts index dad262757a..9562f5e4d7 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, keys); + const [key, operator, result] = useSetCurrentKeyAndOperator(searchValue); 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(tagRegexp); + const matches = prev?.matchAll(getMatchRegex(prev)); 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 94de55df92..ff3a4ad115 100644 --- a/frontend/src/hooks/queryBuilder/useOperatorType.ts +++ b/frontend/src/hooks/queryBuilder/useOperatorType.ts @@ -8,23 +8,35 @@ 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 82dc2c1e24..548ea2de0b 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} `, - label: `${key} ${operator} `, + value: `${key} ${operator.toLowerCase()} `, + label: `${key} ${operator.toLowerCase()} `, })); if (whereClauseConfig) { return [ @@ -148,26 +148,28 @@ 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: tagValue - .filter((i) => i.trim().replace(/^\s+/, '') === option.value) - .includes(option.value), - }; - } - return option; - }), + ) + .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; + }), [isMulti, options, searchValue], ); }; diff --git a/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts b/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts index 2da205b349..afaaa8ccab 100644 --- a/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts +++ b/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts @@ -1,32 +1,24 @@ -import { - getRemovePrefixFromKey, - getTagToken, -} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils'; -import { useMemo } from 'react'; -import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { getTagToken } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils'; +import { useMemo, useRef } from 'react'; type ICurrentKeyAndOperator = [string, string, string[]]; export const useSetCurrentKeyAndOperator = ( value: string, - keys: BaseAutocompleteData[], ): ICurrentKeyAndOperator => { - const [key, operator, result] = useMemo(() => { - let key = ''; - let operator = ''; + const keyRef = useRef(''); + const operatorRef = useRef(''); + + const result = useMemo(() => { 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 || []; - } - return [key, operator, result]; - }, [value, keys]); + keyRef.current = tagKey || ''; + operatorRef.current = tagOperator || ''; + result = tagValue || []; + + return result; + }, [value]); - return [key, operator, result]; + return [keyRef.current, operatorRef.current, result]; }; diff --git a/frontend/src/hooks/queryBuilder/useTag.ts b/frontend/src/hooks/queryBuilder/useTag.ts index 268a01e0c6..45a9417403 100644 --- a/frontend/src/hooks/queryBuilder/useTag.ts +++ b/frontend/src/hooks/queryBuilder/useTag.ts @@ -74,6 +74,7 @@ export const useTag = ( const handleAddTag = useCallback( (value: string): void => { const { tagKey } = getTagToken(value); + const [key, id] = tagKey.split('-'); if (id === 'custom') {