diff --git a/CHANGELOG.md b/CHANGELOG.md index d22d50d..815d20b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,10 @@ * [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. * [UIPQB-175](https://folio-org.atlassian.net/browse/UIPQB-175) Displays the "Smth went wrong" page, when the user clicks on "Select operator" dropdown and selects any of them, if there are deleted custom fields. + ## [1.2.6](https://github.com/folio-org/ui-plugin-query-builder/tree/v1.2.6) (2024-12-11) * [UIPQB-128](https://folio-org.atlassian.net/browse/UIPQB-128) Invalid fields handling > Errors when query includes a deleted custom field. diff --git a/src/QueryBuilder/QueryBuilder/QueryBuilderModal/RepeatableFields/RepeatableFields.js b/src/QueryBuilder/QueryBuilder/QueryBuilderModal/RepeatableFields/RepeatableFields.js index fea5be3..21d4247 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,10 @@ import { Row, getFirstFocusable, } from '@folio/stripes/components'; +import { useShowCallout } from '@folio/stripes-acq-components'; 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 +24,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); @@ -85,6 +89,7 @@ export const RepeatableFields = ({ source, setSource, getParamsSource, columns } }, }; } + if (isOperator) { return { [COLUMN_KEYS.VALUE]: { @@ -114,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: 'warning', + message: ( + + ), + timeout: 0, + }); + } + }, []); + return ( <> @@ -202,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 0786b6f..5dbfd1b 100644 --- a/src/QueryBuilder/QueryBuilder/helpers/query.js +++ b/src/QueryBuilder/QueryBuilder/helpers/query.js @@ -273,3 +273,22 @@ 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 e77eafc..f8f2f50 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'; @@ -367,3 +367,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" }