feat: lowercase operators support in the where clause is updated (#3657)

* feat: lowercase operators suuport in the where clause is updated

* feat: options is now updated

* chore: log message is updated

* chore: auto completed is updated

* chore: tagRegex is updated

* feat: update regex to math operators and text operators

* chore: operator is updated

* chore: options is updated

---------

Co-authored-by: Yunus A M <myounis.ar@live.com>
This commit is contained in:
Palash Gupta 2023-10-06 17:32:17 +05:30 committed by GitHub
parent 587034f573
commit 0e04b779a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 81 additions and 99 deletions

View File

@ -278,23 +278,35 @@ export const QUERY_BUILDER_SEARCH_VALUES = {
export const OPERATORS = { export const OPERATORS = {
IN: 'IN', IN: 'IN',
in: 'in',
NIN: 'NOT_IN', NIN: 'NOT_IN',
not_in: 'not_in',
LIKE: 'LIKE', LIKE: 'LIKE',
like: 'like',
NLIKE: 'NOT_LIKE', NLIKE: 'NOT_LIKE',
not_like: 'not_like',
REGEX: 'REGEX', REGEX: 'REGEX',
regex: 'regex',
NREGEX: 'NOT_REGEX', NREGEX: 'NOT_REGEX',
nregex: 'not_regex',
'=': '=', '=': '=',
'!=': '!=', '!=': '!=',
EXISTS: 'EXISTS', EXISTS: 'EXISTS',
exists: 'exists',
NOT_EXISTS: 'NOT_EXISTS', NOT_EXISTS: 'NOT_EXISTS',
not_exists: 'not_exists',
CONTAINS: 'CONTAINS', CONTAINS: 'CONTAINS',
contains: 'contains',
NOT_CONTAINS: 'NOT_CONTAINS', NOT_CONTAINS: 'NOT_CONTAINS',
not_contains: 'not_contains',
'>=': '>=', '>=': '>=',
'>': '>', '>': '>',
'<=': '<=', '<=': '<=',
'<': '<', '<': '<',
HAS: 'HAS', HAS: 'HAS',
has: 'has',
NHAS: 'NHAS', NHAS: 'NHAS',
nhas: 'nhas',
}; };
export const QUERY_BUILDER_OPERATORS_BY_TYPES = { export const QUERY_BUILDER_OPERATORS_BY_TYPES = {

View File

@ -3,25 +3,42 @@ import { parse } from 'papaparse';
import { orderByValueDelimiter } from '../OrderByFilter/utils'; import { orderByValueDelimiter } from '../OrderByFilter/utils';
const operators = /=|!=|>=|>|<=|<$/;
// eslint-disable-next-line no-useless-escape // 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 { export function isInNInOperator(value: string): boolean {
return value === OPERATORS.IN || value === OPERATORS.NIN; return value === OPERATORS.IN || value === OPERATORS.NIN;
} }
function endsWithOperator(inputString: string): boolean {
return operators.test(inputString);
}
interface ITagToken { interface ITagToken {
tagKey: string; tagKey: string;
tagOperator: string; tagOperator: string;
tagValue: string[]; tagValue: string[];
} }
export function getMatchRegex(str: string): RegExp {
if (endsWithOperator(str)) {
return tagRegexpV1;
}
return tagRegexpV2;
}
export function getTagToken(tag: string): ITagToken { export function getTagToken(tag: string): ITagToken {
const matches = tag?.matchAll(tagRegexp); const matches = tag?.matchAll(getMatchRegex(tag));
const [match] = matches ? Array.from(matches) : []; const [match] = matches ? Array.from(matches) : [];
if (match) { if (match) {
const [, matchTagKey, matchTagOperator, matchTagValue] = match; const [, matchTagKey, matchTagOperator, matchTagValue] = match;
return { return {
tagKey: matchTagKey, tagKey: matchTagKey,
tagOperator: matchTagOperator, tagOperator: matchTagOperator,
@ -51,65 +68,11 @@ export function getRemovePrefixFromKey(tag: string): string {
} }
export function getOperatorValue(op: string): string { export function getOperatorValue(op: string): string {
switch (op) { return op.toLocaleLowerCase();
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 { export function getOperatorFromValue(op: string): string {
switch (op) { return op.toLocaleLowerCase();
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( export function replaceStringWithMaxLength(

View File

@ -1,8 +1,8 @@
import { import {
getMatchRegex,
getRemovePrefixFromKey, getRemovePrefixFromKey,
getTagToken, getTagToken,
replaceStringWithMaxLength, replaceStringWithMaxLength,
tagRegexp,
} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils'; } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
import { Option } from 'container/QueryBuilder/type'; import { Option } from 'container/QueryBuilder/type';
import { parse } from 'papaparse'; import { parse } from 'papaparse';
@ -33,7 +33,7 @@ export const useAutoComplete = (
searchKey, searchKey,
); );
const [key, operator, result] = useSetCurrentKeyAndOperator(searchValue, keys); const [key, operator, result] = useSetCurrentKeyAndOperator(searchValue);
const handleSearch = (value: string): void => { const handleSearch = (value: string): void => {
const prefixFreeValue = getRemovePrefixFromKey(getTagToken(value).tagKey); const prefixFreeValue = getRemovePrefixFromKey(getTagToken(value).tagKey);
@ -58,7 +58,7 @@ export const useAutoComplete = (
(value: string): void => { (value: string): void => {
if (isMulti) { if (isMulti) {
setSearchValue((prev: string) => { setSearchValue((prev: string) => {
const matches = prev?.matchAll(tagRegexp); const matches = prev?.matchAll(getMatchRegex(prev));
const [match] = matches ? Array.from(matches) : []; const [match] = matches ? Array.from(matches) : [];
const [, , , matchTagValue] = match; const [, , , matchTagValue] = match;
const data = parse(matchTagValue).data.flat(); const data = parse(matchTagValue).data.flat();

View File

@ -8,23 +8,35 @@ export type OperatorType =
const operatorTypeMapper: Record<string, OperatorType> = { const operatorTypeMapper: Record<string, OperatorType> = {
[OPERATORS.IN]: 'MULTIPLY_VALUE', [OPERATORS.IN]: 'MULTIPLY_VALUE',
[OPERATORS.in]: 'MULTIPLY_VALUE',
[OPERATORS.NIN]: 'MULTIPLY_VALUE', [OPERATORS.NIN]: 'MULTIPLY_VALUE',
[OPERATORS.not_in]: 'MULTIPLY_VALUE',
[OPERATORS.EXISTS]: 'NON_VALUE', [OPERATORS.EXISTS]: 'NON_VALUE',
[OPERATORS.exists]: 'NON_VALUE',
[OPERATORS.NOT_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['>=']]: 'SINGLE_VALUE', [OPERATORS['>=']]: 'SINGLE_VALUE',
[OPERATORS['>']]: 'SINGLE_VALUE', [OPERATORS['>']]: 'SINGLE_VALUE',
[OPERATORS.LIKE]: 'SINGLE_VALUE', [OPERATORS.LIKE]: 'SINGLE_VALUE',
[OPERATORS.like]: 'SINGLE_VALUE',
[OPERATORS.NLIKE]: 'SINGLE_VALUE', [OPERATORS.NLIKE]: 'SINGLE_VALUE',
[OPERATORS.not_like]: 'SINGLE_VALUE',
[OPERATORS.REGEX]: 'SINGLE_VALUE', [OPERATORS.REGEX]: 'SINGLE_VALUE',
[OPERATORS.regex]: 'SINGLE_VALUE',
[OPERATORS.NREGEX]: 'SINGLE_VALUE', [OPERATORS.NREGEX]: 'SINGLE_VALUE',
[OPERATORS.nregex]: 'SINGLE_VALUE',
[OPERATORS.CONTAINS]: 'SINGLE_VALUE', [OPERATORS.CONTAINS]: 'SINGLE_VALUE',
[OPERATORS.contains]: 'SINGLE_VALUE',
[OPERATORS.NOT_CONTAINS]: 'SINGLE_VALUE', [OPERATORS.NOT_CONTAINS]: 'SINGLE_VALUE',
[OPERATORS.not_contains]: 'SINGLE_VALUE',
[OPERATORS['=']]: 'SINGLE_VALUE', [OPERATORS['=']]: 'SINGLE_VALUE',
[OPERATORS['!=']]: 'SINGLE_VALUE', [OPERATORS['!=']]: 'SINGLE_VALUE',
[OPERATORS.HAS]: 'SINGLE_VALUE', [OPERATORS.HAS]: 'SINGLE_VALUE',
[OPERATORS.has]: 'SINGLE_VALUE',
[OPERATORS.NHAS]: 'SINGLE_VALUE', [OPERATORS.NHAS]: 'SINGLE_VALUE',
[OPERATORS.nhas]: 'SINGLE_VALUE',
}; };
export const useOperatorType = (operator: string): OperatorType => export const useOperatorType = (operator: string): OperatorType =>

View File

@ -81,8 +81,8 @@ export const useOptions = (
const getKeyOperatorOptions = useCallback( const getKeyOperatorOptions = useCallback(
(key: string) => { (key: string) => {
const operatorsOptions = operators?.map((operator) => ({ const operatorsOptions = operators?.map((operator) => ({
value: `${key} ${operator} `, value: `${key} ${operator.toLowerCase()} `,
label: `${key} ${operator} `, label: `${key} ${operator.toLowerCase()} `,
})); }));
if (whereClauseConfig) { if (whereClauseConfig) {
return [ return [
@ -148,26 +148,28 @@ export const useOptions = (
return useMemo( return useMemo(
() => () =>
( options
options.filter( .filter(
(option, index, self) => (option, index, self) =>
index === index ===
self.findIndex( self.findIndex(
(o) => o.label === option.label && o.value === option.value, // to remove duplicate & empty options from list (o) => o.label === option.label && o.value === option.value, // to remove duplicate & empty options from list
) && option.value !== '', ) && option.value !== '',
) || [] )
).map((option) => { .map((option) => {
const { tagValue } = getTagToken(searchValue); const { tagValue } = getTagToken(searchValue);
if (isMulti) { if (isMulti) {
return { return {
...option, ...option,
selected: tagValue selected: Array.isArray(tagValue)
.filter((i) => i.trim().replace(/^\s+/, '') === option.value) ? tagValue
.includes(option.value), ?.filter((i) => i.trim().replace(/^\s+/, '') === option.value)
}; ?.includes(option.value)
} : String(tagValue).includes(option.value),
return option; };
}), }
return option;
}),
[isMulti, options, searchValue], [isMulti, options, searchValue],
); );
}; };

View File

@ -1,32 +1,24 @@
import { import { getTagToken } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
getRemovePrefixFromKey, import { useMemo, useRef } from 'react';
getTagToken,
} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
import { useMemo } from 'react';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
type ICurrentKeyAndOperator = [string, string, string[]]; type ICurrentKeyAndOperator = [string, string, string[]];
export const useSetCurrentKeyAndOperator = ( export const useSetCurrentKeyAndOperator = (
value: string, value: string,
keys: BaseAutocompleteData[],
): ICurrentKeyAndOperator => { ): ICurrentKeyAndOperator => {
const [key, operator, result] = useMemo(() => { const keyRef = useRef<string>('');
let key = ''; const operatorRef = useRef<string>('');
let operator = '';
const result = useMemo(() => {
let result: string[] = []; let result: string[] = [];
const { tagKey, tagOperator, tagValue } = getTagToken(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 || [];
}
return [key, operator, result]; keyRef.current = tagKey || '';
}, [value, keys]); operatorRef.current = tagOperator || '';
result = tagValue || [];
return [key, operator, result]; return result;
}, [value]);
return [keyRef.current, operatorRef.current, result];
}; };

View File

@ -74,6 +74,7 @@ export const useTag = (
const handleAddTag = useCallback( const handleAddTag = useCallback(
(value: string): void => { (value: string): void => {
const { tagKey } = getTagToken(value); const { tagKey } = getTagToken(value);
const [key, id] = tagKey.split('-'); const [key, id] = tagKey.split('-');
if (id === 'custom') { if (id === 'custom') {