diff --git a/src/QueryBuilder/QueryBuilder/QueryBuilderModal/DataTypeInput/DataTypeInput.js b/src/QueryBuilder/QueryBuilder/QueryBuilderModal/DataTypeInput/DataTypeInput.js index d1bb2609..cbea7562 100644 --- a/src/QueryBuilder/QueryBuilder/QueryBuilderModal/DataTypeInput/DataTypeInput.js +++ b/src/QueryBuilder/QueryBuilder/QueryBuilderModal/DataTypeInput/DataTypeInput.js @@ -14,6 +14,7 @@ import { SelectionContainer } from '../SelectionContainer/SelectionContainer'; import { ISO_FORMAT } from '../../helpers/timeUtils'; import css from '../../../QueryBuilder.css'; +import { staticBooleanOptions } from '../../helpers/selectOptions'; export const DataTypeInput = ({ availableValues, @@ -28,6 +29,7 @@ export const DataTypeInput = ({ }) => { const isInRelatedOperator = [OPERATORS.IN, OPERATORS.NOT_IN].includes(operator); const isEqualRelatedOperator = [OPERATORS.EQUAL, OPERATORS.NOT_EQUAL].includes(operator); + const isEmptyRelatedOperator = [OPERATORS.EMPTY].includes(operator); const hasSourceOrValues = source || availableValues; const textControl = ({ testId, type = 'text', textClass }) => { @@ -147,6 +149,18 @@ export const DataTypeInput = ({ : selectControl({ testId: 'data-input-select-arrayType' }); }; + if (isEmptyRelatedOperator) { + return ( + onChange(e.target.value, index, COLUMN_KEYS.VALUE)} + {...rest} + /> + ); + } + switch (dataType) { case DATA_TYPES.StringType: return stringTypeControls(); diff --git a/src/QueryBuilder/QueryBuilder/helpers/query.js b/src/QueryBuilder/QueryBuilder/helpers/query.js index 293aa3a4..4926eb91 100644 --- a/src/QueryBuilder/QueryBuilder/helpers/query.js +++ b/src/QueryBuilder/QueryBuilder/helpers/query.js @@ -102,6 +102,9 @@ const getQueryOperand = (item) => { case OPERATORS.NOT_CONTAINS: queryOperand = { [field]: { $not_contains: value } }; break; + case OPERATORS.EMPTY: + queryOperand = { [field]: { $empty: value } }; + break; default: break; } @@ -137,6 +140,7 @@ const getSourceFields = (field) => ({ $nin: (value) => ({ operator: OPERATORS.NOT_IN, value }), $contains: (value) => ({ operator: OPERATORS.CONTAINS, value }), $not_contains: (value) => ({ operator: OPERATORS.NOT_CONTAINS, value }), + $empty: (value) => ({ operator: OPERATORS.EMPTY, value }), $regex: (value) => { return value?.includes('^') ? { operator: OPERATORS.STARTS_WITH, value: value?.replace(cleanerRegex, '') } diff --git a/src/QueryBuilder/QueryBuilder/helpers/query.test.js b/src/QueryBuilder/QueryBuilder/helpers/query.test.js index fe79fcab..d83325cc 100644 --- a/src/QueryBuilder/QueryBuilder/helpers/query.test.js +++ b/src/QueryBuilder/QueryBuilder/helpers/query.test.js @@ -98,6 +98,12 @@ describe('mongoQueryToSource()', () => { operator: { options: expect.any(Array), current: OPERATORS.NOT_CONTAINS }, value: { current: 'value' }, }, + { + boolean: { options: booleanOptions, current: '$and' }, + field: { options: fieldOptions, current: 'department_ids', dataType: DATA_TYPES.ArrayType }, + operator: { options: expect.any(Array), current: OPERATORS.EMPTY }, + value: { current: 'value' }, + }, ]; const initialValues = { @@ -114,6 +120,7 @@ describe('mongoQueryToSource()', () => { { user_id: { $in: ['value', 'value2'] } }, { department_names: { $contains: 'value' } }, { department_names: { $not_contains: 'value' } }, + { department_ids: { $empty: 'value' } }, ], }; @@ -180,6 +187,7 @@ describe('mongoQueryToSource()', () => { { user_id: { $in: ['value', 'value2'] } }, { department_names: { $contains: 'value' } }, { department_names: { $not_contains: 'value' } }, + { department_ids: { $empty: 'value' } }, ], }; diff --git a/src/QueryBuilder/QueryBuilder/helpers/selectOptions.js b/src/QueryBuilder/QueryBuilder/helpers/selectOptions.js index 504fd5af..a7a9adb9 100644 --- a/src/QueryBuilder/QueryBuilder/helpers/selectOptions.js +++ b/src/QueryBuilder/QueryBuilder/helpers/selectOptions.js @@ -1,3 +1,4 @@ +import { FormattedMessage } from 'react-intl'; import { DATA_TYPES } from '../../../constants/dataTypes'; import { BOOLEAN_OPERATORS, OPERATORS } from '../../../constants/operators'; import { COLUMN_KEYS } from '../../../constants/columnKeys'; @@ -18,12 +19,14 @@ const baseLogicalOperators = () => [ { label: OPERATORS.NOT_EQUAL, value: OPERATORS.NOT_EQUAL }, { label: OPERATORS.GREATER_THAN, value: OPERATORS.GREATER_THAN }, { label: OPERATORS.LESS_THAN, value: OPERATORS.LESS_THAN }, + { label: OPERATORS.EMPTY, value: OPERATORS.EMPTY }, ]; const extendedLogicalOperators = () => [ ...baseLogicalOperators(), { label: OPERATORS.GREATER_THAN_OR_EQUAL, value: OPERATORS.GREATER_THAN_OR_EQUAL }, { label: OPERATORS.LESS_THAN_OR_EQUAL, value: OPERATORS.LESS_THAN_OR_EQUAL }, + { label: OPERATORS.EMPTY, value: OPERATORS.EMPTY }, ]; const UUIDOperators = () => [ @@ -31,6 +34,7 @@ const UUIDOperators = () => [ { label: OPERATORS.NOT_EQUAL, value: OPERATORS.NOT_EQUAL }, { label: OPERATORS.IN, value: OPERATORS.IN }, { label: OPERATORS.NOT_IN, value: OPERATORS.NOT_IN }, + { label: OPERATORS.EMPTY, value: OPERATORS.EMPTY }, ]; const ArrayOperators = () => [ @@ -40,6 +44,7 @@ const ArrayOperators = () => [ { label: OPERATORS.NOT_IN, value: OPERATORS.NOT_IN }, { label: OPERATORS.CONTAINS, value: OPERATORS.CONTAINS }, { label: OPERATORS.NOT_CONTAINS, value: OPERATORS.NOT_CONTAINS }, + { label: OPERATORS.EMPTY, value: OPERATORS.EMPTY }, ]; export const getFilledValues = (options) => { @@ -57,12 +62,14 @@ const stringOperators = (hasSourceOrValues) => { { label: OPERATORS.CONTAINS, value: OPERATORS.CONTAINS }, { label: OPERATORS.STARTS_WITH, value: OPERATORS.STARTS_WITH }, ]), + { label: OPERATORS.EMPTY, value: OPERATORS.EMPTY }, ]; }; const booleanOperators = () => [ { label: OPERATORS.EQUAL, value: OPERATORS.EQUAL }, { label: OPERATORS.NOT_EQUAL, value: OPERATORS.NOT_EQUAL }, + { label: OPERATORS.EMPTY, value: OPERATORS.EMPTY }, ]; export const getOperatorOptions = ({ @@ -122,6 +129,11 @@ export const booleanOptions = [ { label: 'AND', value: BOOLEAN_OPERATORS.AND }, ]; +export const staticBooleanOptions = [ + { label: , value: true }, + { label: , value: false }, +]; + export const sourceTemplate = (fieldOptions = []) => ({ [COLUMN_KEYS.BOOLEAN]: { options: booleanOptions, current: '' }, [COLUMN_KEYS.FIELD]: { options: fieldOptions, current: '' }, diff --git a/src/constants/operators.js b/src/constants/operators.js index b6a40819..846752e4 100644 --- a/src/constants/operators.js +++ b/src/constants/operators.js @@ -10,6 +10,7 @@ export const OPERATORS = { CONTAINS: 'contains', NOT_CONTAINS: 'not contains', STARTS_WITH: 'starts with', + EMPTY: ' is null/empty', }; export const BOOLEAN_OPERATORS = { diff --git a/test/jest/data/entityType.js b/test/jest/data/entityType.js index 157543f4..5bfb1f50 100644 --- a/test/jest/data/entityType.js +++ b/test/jest/data/entityType.js @@ -312,6 +312,14 @@ export const entityType = { 'labelAlias': 'Department names', 'visibleByDefault': true, }, + { + 'name': 'department_ids', + 'dataType': { + 'dataType': 'arrayType', + }, + 'labelAlias': 'Department Ids', + 'visibleByDefault': true, + }, ], 'defaultSort': [ { diff --git a/translations/ui-plugin-query-builder/en.json b/translations/ui-plugin-query-builder/en.json index 3770d5e9..9a11ad52 100644 --- a/translations/ui-plugin-query-builder/en.json +++ b/translations/ui-plugin-query-builder/en.json @@ -17,6 +17,9 @@ "columns.value": "Value", "columns.action": "Actions", + "options.true": "True", + "options.false": "False", + "control.info.separateValues": "Separate multiple values with a comma.", "control.selection.placeholder": "Select field", "control.value.placeholder": "Select value",