Skip to content

Commit 424885b

Browse files
Vojtěch Václav Portešvojtechportes
authored andcommitted
feat: Added new operators
- added: IS_NULL, IS_NOT_NULL, CONTAINS, NOT_CONTAINS, STARTS_WITH, ENDS_WITH, IN - deprecated: ANY_IN, LIKE, NOT_LIKE
1 parent 0a22402 commit 424885b

17 files changed

Lines changed: 308 additions & 89 deletions

README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,15 +174,27 @@ LARGER_EQUAL
174174
SMALLER_EQUAL
175175
EQUAL
176176
NOT_EQUAL
177+
IN
178+
NOT_IN
177179
ALL_IN
178180
ANY_IN
179-
NOT_IN
180181
BETWEEN
181182
NOT_BETWEEN
183+
IS_NULL
184+
IS_NOT_NULL
185+
CONTAINS
186+
NOT_CONTAINS
187+
STARTS_WITH
188+
ENDS_WITH
182189
LIKE
183190
NOT_LIKE
184191
```
185192

193+
`ANY_IN`, `LIKE`, and `NOT_LIKE` are deprecated and will be removed in `2.0.0`.
194+
Prefer `IN`, `CONTAINS`, and `NOT_CONTAINS`.
195+
196+
`IS_NULL` and `IS_NOT_NULL` are value-less operators, so the built-in UI does not render a value input for them.
197+
186198
### Data
187199

188200
`data` is a controlled prop. Pass an array of rules and groups, and update it through `onChange`.
@@ -388,14 +400,23 @@ const strings: IStrings = {
388400
EQUAL: 'Equal',
389401
NOT_EQUAL: 'Not equal',
390402
ALL_IN: 'All in',
403+
IN: 'In',
391404
ANY_IN: 'Any in',
392405
NOT_IN: 'Not in',
393406
BETWEEN: 'Between',
394407
NOT_BETWEEN: 'Not between',
408+
IS_NULL: 'Is null',
409+
IS_NOT_NULL: 'Is not null',
410+
CONTAINS: 'Contains',
411+
NOT_CONTAINS: 'Does not contain',
412+
STARTS_WITH: 'Starts with',
413+
ENDS_WITH: 'Ends with',
395414
LIKE: 'Like',
396415
NOT_LIKE: 'Not like',
397416
},
398417
};
399418
```
400419

420+
`ANY_IN`, `LIKE`, and `NOT_LIKE` are deprecated here as well and will be removed in `2.0.0`.
421+
401422
It is not required to translate every string. Any string you omit falls back to the built-in defaults.

src/constants/strings.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,28 @@ export interface IStrings {
2323
EQUAL?: string;
2424
NOT_EQUAL?: string;
2525
ALL_IN?: string;
26+
/**
27+
* @deprecated Use `IN` instead. This alias will be removed in 2.0.0.
28+
*/
2629
ANY_IN?: string;
30+
IN?: string;
2731
NOT_IN?: string;
2832
BETWEEN?: string;
2933
NOT_BETWEEN?: string;
34+
IS_NULL?: string;
35+
IS_NOT_NULL?: string;
36+
/**
37+
* @deprecated Use `CONTAINS` instead. This alias will be removed in 2.0.0.
38+
*/
3039
LIKE?: string;
40+
/**
41+
* @deprecated Use `NOT_CONTAINS` instead. This alias will be removed in 2.0.0.
42+
*/
3143
NOT_LIKE?: string;
44+
CONTAINS?: string;
45+
NOT_CONTAINS?: string;
46+
STARTS_WITH?: string;
47+
ENDS_WITH?: string;
3248
};
3349
}
3450

@@ -58,10 +74,17 @@ export const strings: IStrings = {
5874
NOT_EQUAL: 'Not equal',
5975
ALL_IN: 'All in',
6076
ANY_IN: 'Any in',
77+
IN: 'In',
6178
NOT_IN: 'Not in',
6279
BETWEEN: 'Between',
6380
NOT_BETWEEN: 'Not between',
81+
IS_NULL: 'Is null',
82+
IS_NOT_NULL: 'Is not null',
6483
LIKE: 'Like',
65-
NOT_LIKE: 'Not like'
84+
NOT_LIKE: 'Not like',
85+
CONTAINS: 'Contains',
86+
NOT_CONTAINS: 'Does not contain',
87+
STARTS_WITH: 'Starts with',
88+
ENDS_WITH: 'Ends with',
6689
},
6790
};

src/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export { colors } from './constants/colors';
5252
export type { IColors, IColorVariant, IGreyColorVariant } from './constants/colors';
5353
export { strings } from './constants/strings';
5454
export type { IStrings } from './constants/strings';
55+
export { queryOperators } from './utils/query-operators';
5556
export type {
5657
DenormalizedNode,
5758
DenormalizedQuery,

src/rule/rule.test.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const fields: IBuilderFieldProps[] = [
2020
{
2121
field: 'MOCK_FIELD_2',
2222
label: 'Mock Field 2',
23-
operators: ['EQUAL'],
23+
operators: ['EQUAL', 'IS_NULL'],
2424
type: 'TEXT',
2525
},
2626
{
@@ -74,6 +74,12 @@ const data: any[] = [
7474
id: 'test-3',
7575
parent: 'test-1',
7676
},
77+
{
78+
field: 'MOCK_FIELD_2',
79+
operator: 'IS_NULL',
80+
id: 'test-10',
81+
parent: 'test-1',
82+
},
7783
{
7884
field: 'MOCK_FIELD_3',
7985
value: '',
@@ -173,6 +179,7 @@ describe('#components/Rule', () => {
173179
id="test-9"
174180
field="SOME_FIELD_THAT_DOESNT_EXISTS"
175181
/>
182+
<Rule data-test="Rule[9]" id="test-10" field="MOCK_FIELD_2" operator="IS_NULL" />
176183
</BuilderContext.Provider>
177184
);
178185

@@ -184,6 +191,8 @@ describe('#components/Rule', () => {
184191
expect(wrapper.find('[data-test="Rule[5]"]')).toBeDefined();
185192
expect(wrapper.find('[data-test="Rule[6]"]')).toBeDefined();
186193
expect(wrapper.find('[data-test="Rule[7]"]')).toBeDefined();
194+
expect(wrapper.find('[data-test="Rule[9]"] select').length).toBeGreaterThan(0);
195+
expect(wrapper.find('[data-test="Rule[9]"] input').length).toEqual(0);
187196
expect(Object.keys(wrapper.find('[data-test="Rule[8]"]')).length).toBe(
188197
0
189198
);

src/rule/rule.tsx

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { isString } from '../utils/is-string.util';
1919
import { isStringArray } from '../utils/is-string-array.util';
2020
import { isStringOrNumberArray } from '../utils/is-string-or-number-array.util';
2121
import { isUndefined } from '../utils/is-undefined.util';
22+
import { operatorRequiresValue } from '../utils/operator-requires-value.util';
2223
import { QueryRuleValue } from '../utils/query-tree';
2324
import { removeItem } from '../utils/remove-item.util';
2425

@@ -93,6 +94,7 @@ export const Rule: FC<IRuleProps> = ({
9394
value: item,
9495
label: (strings.operators && strings.operators[item]) || item,
9596
}));
97+
const shouldRenderValueInput = operatorRequiresValue(operator);
9698

9799
return (
98100
<RuleContainer
@@ -104,14 +106,24 @@ export const Rule: FC<IRuleProps> = ({
104106
>
105107
<FieldSelect selectedValue={field} id={id} />
106108

107-
{type === 'BOOLEAN' && isBoolean(selectedValue) && (
108-
<BooleanContainer>
109-
<Boolean id={id} selectedValue={selectedValue} />
110-
</BooleanContainer>
109+
{type === 'BOOLEAN' && (
110+
<>
111+
{isOptionList(operatorsOptionList) && (
112+
<OperatorSelect
113+
id={id}
114+
values={operatorsOptionList}
115+
selectedValue={operator}
116+
/>
117+
)}
118+
{shouldRenderValueInput && isBoolean(selectedValue) && (
119+
<BooleanContainer>
120+
<Boolean id={id} selectedValue={selectedValue} />
121+
</BooleanContainer>
122+
)}
123+
</>
111124
)}
112125

113126
{type === 'LIST' &&
114-
isString(selectedValue) &&
115127
isOptionList(fieldValue) &&
116128
isOptionList(operatorsOptionList) && (
117129
<>
@@ -120,7 +132,7 @@ export const Rule: FC<IRuleProps> = ({
120132
values={operatorsOptionList}
121133
selectedValue={operator}
122134
/>
123-
{operator && (
135+
{operator && shouldRenderValueInput && isString(selectedValue) && (
124136
<Select
125137
id={id}
126138
selectedValue={selectedValue}
@@ -132,15 +144,16 @@ export const Rule: FC<IRuleProps> = ({
132144

133145
{type === 'MULTI_LIST' &&
134146
isOptionList(fieldValue) &&
135-
isOptionList(operatorsOptionList) &&
136-
isStringArray(selectedValue) && (
147+
isOptionList(operatorsOptionList) && (
137148
<>
138149
<OperatorSelect
139150
id={id}
140151
values={operatorsOptionList}
141152
selectedValue={operator}
142153
/>
143-
{operator && (
154+
{operator &&
155+
shouldRenderValueInput &&
156+
isStringArray(selectedValue) && (
144157
<SelectMulti
145158
id={id}
146159
values={fieldValue}
@@ -150,49 +163,51 @@ export const Rule: FC<IRuleProps> = ({
150163
</>
151164
)}
152165

153-
{type === 'TEXT' &&
154-
isOptionList(operatorsOptionList) &&
155-
(isString(selectedValue) || isStringArray(selectedValue)) && (
166+
{type === 'TEXT' && isOptionList(operatorsOptionList) && (
156167
<>
157168
<OperatorSelect
158169
id={id}
159170
values={operatorsOptionList}
160171
selectedValue={operator}
161172
/>
162-
{operator && <Input type="text" value={selectedValue} id={id} />}
173+
{operator &&
174+
shouldRenderValueInput &&
175+
(isString(selectedValue) || isStringArray(selectedValue)) && (
176+
<Input type="text" value={selectedValue} id={id} />
177+
)}
163178
</>
164179
)}
165180

166-
{type === 'NUMBER' &&
167-
isOptionList(operatorsOptionList) &&
168-
(isString(selectedValue) ||
169-
isNumber(selectedValue) ||
170-
isStringOrNumberArray(selectedValue) ||
171-
isNumberArray(selectedValue)) && (
181+
{type === 'NUMBER' && isOptionList(operatorsOptionList) && (
172182
<>
173183
<OperatorSelect
174184
id={id}
175185
values={operatorsOptionList}
176186
selectedValue={operator}
177187
/>
178-
{operator && (
188+
{operator &&
189+
shouldRenderValueInput &&
190+
(isString(selectedValue) ||
191+
isNumber(selectedValue) ||
192+
isStringOrNumberArray(selectedValue) ||
193+
isNumberArray(selectedValue)) && (
179194
<Input type="number" value={selectedValue} id={id} />
180-
)}
195+
)}
181196
</>
182197
)}
183198

184-
{type === 'DATE' &&
185-
isOptionList(operatorsOptionList) &&
186-
(isString(selectedValue) || isStringArray(selectedValue)) && (
199+
{type === 'DATE' && isOptionList(operatorsOptionList) && (
187200
<>
188201
<OperatorSelect
189202
id={id}
190203
values={operatorsOptionList}
191204
selectedValue={operator}
192205
/>
193-
{!isUndefined(operator) && (
206+
{!isUndefined(operator) &&
207+
shouldRenderValueInput &&
208+
(isString(selectedValue) || isStringArray(selectedValue)) && (
194209
<Input type="date" value={selectedValue} id={id} />
195-
)}
210+
)}
196211
</>
197212
)}
198213
</RuleContainer>

src/utils/create-rule-state-for-field.util.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,21 @@ describe('#utils/createRuleStateForField', () => {
8383
});
8484
});
8585

86+
it('Creates a valueless rule state for IS_NULL', () => {
87+
const field: IBuilderFieldProps = {
88+
field: 'DELETED_AT',
89+
label: 'Deleted At',
90+
type: 'DATE',
91+
operators: ['IS_NULL'],
92+
};
93+
94+
expect(createRuleStateForField(field)).toEqual({
95+
field: 'DELETED_AT',
96+
operator: 'IS_NULL',
97+
operators: ['IS_NULL'],
98+
});
99+
});
100+
86101
it('Creates a statement rule state', () => {
87102
const field: IBuilderFieldProps = {
88103
field: 'NOTE',

src/utils/create-rule-state-for-field.util.ts

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,30 @@
11
import { IBuilderFieldProps } from '../builder';
2-
import { isOptionList } from './is-option-list.util';
3-
4-
const getDefaultValueForTextLikeField = (field: IBuilderFieldProps) =>
5-
field.operators && ['BETWEEN', 'NOT_BETWEEN'].includes(field.operators[0])
6-
? ['', '']
7-
: '';
8-
9-
const getDefaultValueForNumberField = (field: IBuilderFieldProps) =>
10-
field.operators && ['BETWEEN', 'NOT_BETWEEN'].includes(field.operators[0])
11-
? [0, 0]
12-
: 0;
2+
import { createRuleValueForFieldOperator } from './create-rule-value-for-field-operator.util';
133

144
export const createRuleStateForField = (field: IBuilderFieldProps) => {
5+
const nextOperator = field.operators && field.operators[0];
156
const baseRuleState = {
167
field: field.field,
17-
operator: field.operators && field.operators[0],
8+
operator: nextOperator,
189
operators: field.operators,
1910
};
11+
const nextValue = createRuleValueForFieldOperator(field, nextOperator);
2012

2113
switch (field.type) {
2214
case 'BOOLEAN':
2315
return {
24-
field: field.field,
25-
value: false,
16+
...baseRuleState,
17+
...(typeof nextValue !== 'undefined' ? { value: nextValue } : {}),
2618
};
2719

2820
case 'DATE':
2921
case 'TEXT':
30-
return {
31-
...baseRuleState,
32-
value: getDefaultValueForTextLikeField(field),
33-
};
34-
3522
case 'NUMBER':
36-
return {
37-
...baseRuleState,
38-
value: getDefaultValueForNumberField(field),
39-
};
40-
4123
case 'LIST':
42-
return {
43-
...baseRuleState,
44-
value: isOptionList(field.value) ? field.value[0].value : undefined,
45-
};
46-
4724
case 'MULTI_LIST':
4825
return {
4926
...baseRuleState,
50-
value: isOptionList(field.value) ? [] : undefined,
27+
...(typeof nextValue !== 'undefined' ? { value: nextValue } : {}),
5128
};
5229

5330
case 'STATEMENT':

0 commit comments

Comments
 (0)