From 3c7f980ebac7ff648f29e0a7e03875665cf6066c Mon Sep 17 00:00:00 2001 From: UladzislauKutarkin <72550466+UladzislauKutarkin@users.noreply.github.com> Date: Tue, 24 Dec 2024 11:04:08 +0400 Subject: [PATCH] UIPQB-162: Errors when query includes a modified custom field (#200) --- CHANGELOG.md | 1 + .../RepeatableFields/RepeatableFields.js | 34 ++++++- .../QueryBuilder/helpers/query.js | 19 ++++ .../QueryBuilder/helpers/query.test.js | 90 ++++++++++++++++++- translations/ui-plugin-query-builder/en.json | 2 + 5 files changed, 141 insertions(+), 5 deletions(-) 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 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 d29cc45..21dba0f 100644 --- a/src/QueryBuilder/QueryBuilder/helpers/query.js +++ b/src/QueryBuilder/QueryBuilder/helpers/query.js @@ -279,3 +279,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 1451062..0e8a0ef 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" }