diff --git a/CHANGELOG.md b/CHANGELOG.md index 767d4c5..8367e68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * [UIPQB-168](https://folio-org.atlassian.net/browse/UIPQB-168) Allow editing queries containing no fields. * [UIPQB-141](https://folio-org.atlassian.net/browse/UIPQB-141) Modal dialog focus inconsistencies across screenreaders. +* [UIPQB-162](https://folio-org.atlassian.net/browse/UIPQB-162) Errors when query includes a modified custom field. ## [1.2.6](https://github.com/folio-org/ui-plugin-query-builder/tree/v1.2.6) (2024-12-11) diff --git a/src/QueryBuilder/QueryBuilder/QueryBuilderModal/RepeatableFields/RepeatableFields.js b/src/QueryBuilder/QueryBuilder/QueryBuilderModal/RepeatableFields/RepeatableFields.js index add54c1..097ed98 100644 --- a/src/QueryBuilder/QueryBuilder/QueryBuilderModal/RepeatableFields/RepeatableFields.js +++ b/src/QueryBuilder/QueryBuilder/QueryBuilderModal/RepeatableFields/RepeatableFields.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { memo, useEffect, useRef } from 'react'; import { IconButton, RepeatableField, @@ -8,9 +8,11 @@ import { Row, getFirstFocusable } from '@folio/stripes/components'; +import { useShowCallout } from "@folio/stripes-acq-components"; +import {MESSAGE_TYPES} from "@folio/lists/src/hooks/useMessages/useMessages"; import PropTypes from 'prop-types'; -import { useIntl } from 'react-intl'; +import {FormattedMessage, useIntl} from 'react-intl'; import { QueryBuilderTitle } from '../../QueryBuilderTitle'; import css from '../QueryBuilderModal.css'; import { COLUMN_KEYS } from '../../../../constants/columnKeys'; @@ -23,9 +25,12 @@ import { } from '../../helpers/selectOptions'; import { BOOLEAN_OPERATORS } from '../../../../constants/operators'; import { DataTypeInput } from '../DataTypeInput'; +import {findMissingValues} from "../../helpers/query"; -export const RepeatableFields = ({ source, setSource, getParamsSource, columns }) => { +export const RepeatableFields = memo(({ source, setSource, getParamsSource, columns }) => { const intl = useIntl(); + const callout = useShowCallout(); + const calloutCalledRef = useRef(false); const fieldOptions = getFieldOptions(columns); @@ -84,6 +89,7 @@ export const RepeatableFields = ({ source, setSource, getParamsSource, columns } }, }; } + if (isOperator) { return { [COLUMN_KEYS.VALUE]: { @@ -113,6 +119,27 @@ export const RepeatableFields = ({ source, setSource, getParamsSource, columns } })); }; + useEffect(() => { + if (calloutCalledRef.current) return; + + const deletedFields = findMissingValues(fieldOptions, source) + + if(deletedFields.length >= 1) { + calloutCalledRef.current = true; + + callout({ + type: MESSAGE_TYPES.WARNING, + message: ( + + ), + timeout: 0, + }) + } + }, []); + return ( <> @@ -201,7 +228,7 @@ export const RepeatableFields = ({ source, setSource, getParamsSource, columns } /> ); -}; +}); RepeatableFields.propTypes = { source: PropTypes.arrayOf(PropTypes.object), diff --git a/src/QueryBuilder/QueryBuilder/helpers/query.js b/src/QueryBuilder/QueryBuilder/helpers/query.js index d29cc45..eb15c82 100644 --- a/src/QueryBuilder/QueryBuilder/helpers/query.js +++ b/src/QueryBuilder/QueryBuilder/helpers/query.js @@ -279,3 +279,20 @@ export const mongoQueryToSource = async ({ return [singleItem]; }; + +export const findMissingValues = ( + mainArray, + secondaryArray +) => { + const mainValues = new Set(mainArray?.map((item) => item.value)); + + const missingValues = []; + for (const secondaryItem of secondaryArray) { + const currentValue = secondaryItem.field.current; + if (currentValue && !mainValues.has(currentValue)) { + missingValues.push(currentValue); + } + } + + return missingValues; +} diff --git a/src/QueryBuilder/QueryBuilder/helpers/query.test.js b/src/QueryBuilder/QueryBuilder/helpers/query.test.js index 1451062..eba8db7 100644 --- a/src/QueryBuilder/QueryBuilder/helpers/query.test.js +++ b/src/QueryBuilder/QueryBuilder/helpers/query.test.js @@ -1,4 +1,4 @@ -import { getTransformedValue, isQueryValid, mongoQueryToSource, sourceToMongoQuery } from './query'; +import {findMissingValues, getTransformedValue, isQueryValid, mongoQueryToSource, sourceToMongoQuery} from './query'; import { booleanOptions } from './selectOptions'; import { OPERATORS } from '../../../constants/operators'; import { fieldOptions } from '../../../../test/jest/data/entityType'; @@ -372,3 +372,91 @@ describe('getTransformedValue', () => { expect(actual).toEqual(expected); }); }); + +describe('findMissingValues', () => { + it('should return missing values from secondaryArray that are not in mainArray', () => { + const mainArray = [ + { value: 'value1' }, + { value: 'value2' }, + { value: 'value3' }, + ]; + + const secondaryArray = [ + { field: { current: 'value2' } }, + { field: { current: 'value4' } }, + { field: { current: 'value5' } }, + ]; + + const result = findMissingValues(mainArray, secondaryArray); + + expect(result).toEqual(['value4', 'value5']); + }); + + it('should return an empty array when all values are present in mainArray', () => { + const mainArray = [ + { value: 'value1' }, + { value: 'value2' }, + ]; + + const secondaryArray = [ + { field: { current: 'value1' } }, + { field: { current: 'value2' } }, + ]; + + const result = findMissingValues(mainArray, secondaryArray); + + expect(result).toEqual([]); + }); + + it('should handle cases where mainArray is empty', () => { + const mainArray = []; + + const secondaryArray = [ + { field: { current: 'value1' } }, + { field: { current: 'value2' } }, + ]; + + const result = findMissingValues(mainArray, secondaryArray); + + expect(result).toEqual(['value1', 'value2']); + }); + + it('should handle cases where secondaryArray is empty', () => { + const mainArray = [ + { value: 'value1' }, + { value: 'value2' }, + ]; + + const secondaryArray = []; + + const result = findMissingValues(mainArray, secondaryArray); + + expect(result).toEqual([]); + }); + + it('should handle cases where both arrays are empty', () => { + const mainArray = []; + const secondaryArray = []; + + const result = findMissingValues(mainArray, secondaryArray); + + expect(result).toEqual([]); + }); + + it('should ignore null or undefined values in secondaryArray', () => { + const mainArray = [ + { value: 'value1' }, + { value: 'value2' }, + ]; + + const secondaryArray = [ + { field: { current: 'value3' } }, + { field: { current: null } }, + { field: { current: undefined } }, + ]; + + const result = findMissingValues(mainArray, secondaryArray); + + expect(result).toEqual(['value3']); + }); +}); diff --git a/translations/ui-plugin-query-builder/en.json b/translations/ui-plugin-query-builder/en.json index e175289..595c4a8 100644 --- a/translations/ui-plugin-query-builder/en.json +++ b/translations/ui-plugin-query-builder/en.json @@ -35,5 +35,7 @@ "error.sww": "Something went wrong", "error.occurredMessage": "An error occurred.", + "warning.deletedField": "{value} in your query is unavailable. Please revise your query. ", + "ariaLabel.columnFilter": "Column filter input" }