From a5b6f107871300670af423d5e7e1c42e006a6c9c Mon Sep 17 00:00:00 2001 From: robinvandermolen Date: Thu, 16 Jan 2025 09:40:11 +0100 Subject: [PATCH 1/9] :sparkles: [#4871] Objects API variable mapping errors --- .../components/admin/forms/VariableMapping.js | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/openforms/js/components/admin/forms/VariableMapping.js b/src/openforms/js/components/admin/forms/VariableMapping.js index 61bb4e8760..9b38ea5379 100644 --- a/src/openforms/js/components/admin/forms/VariableMapping.js +++ b/src/openforms/js/components/admin/forms/VariableMapping.js @@ -1,12 +1,13 @@ import {FieldArray, useFormikContext} from 'formik'; import get from 'lodash/get'; import PropTypes from 'prop-types'; -import React from 'react'; +import React, {useContext} from 'react'; import {FormattedMessage, useIntl} from 'react-intl'; import ButtonContainer from 'components/admin/forms/ButtonContainer'; import Field from 'components/admin/forms/Field'; import Select from 'components/admin/forms/Select'; +import {ValidationErrorContext} from 'components/admin/forms/ValidationErrors'; import VariableSelection from 'components/admin/forms/VariableSelection'; import {DeleteIcon, WarningIcon} from 'components/admin/icons'; @@ -47,6 +48,7 @@ const VariableMappingRow = ({ includeStaticVariables = false, alreadyMapped = [], rowCheck = undefined, + errors = undefined, }) => { const intl = useIntl(); const {getFieldProps, setFieldValue} = useFormikContext(); @@ -63,12 +65,14 @@ const VariableMappingRow = ({ ); const mapping = getFieldProps(prefix).value; - const errors = rowCheck?.(intl, mapping).join(', ') ?? ''; + const rowErrors = rowCheck?.(intl, mapping).join(', ') ?? ''; + const variableError = errors?.[variableName]; + const propertyError = errors?.[propertyName]; return ( - + {directionIcon}} - + [choice.id, choice.label])} - {...formik.getFieldProps('pluginId')} - onChange={(...args) => { - // Otherwise the field is set as 'touched' only on the blur event - formik.setFieldTouched('pluginId'); - formik.handleChange(...args); - }} - /> - - - - - - } - > + +
+ onSave(values)} + validate={validate} + > + {formik => ( +
- -
-
- + + } + errors={ + formik.touched.pluginId && formik.errors.pluginId + ? [ERRORS[formik.errors.pluginId]] + : [] + } + > + -
- - )} - -
+ + } + > +
+
+ +
+
+ +
+
+ + +
+
+ +
+ + )} + + +
); }; DMNActionConfig.propTypes = { initialValues: inputValuesType, onSave: PropTypes.func.isRequired, + errors: ActionConfigError, }; export default DMNActionConfig; diff --git a/src/openforms/js/components/admin/form_design/logic/actions/types.js b/src/openforms/js/components/admin/form_design/logic/actions/types.js index abd128493d..e46a3e5bc9 100644 --- a/src/openforms/js/components/admin/form_design/logic/actions/types.js +++ b/src/openforms/js/components/admin/form_design/logic/actions/types.js @@ -25,6 +25,21 @@ const Action = PropTypes.shape({ formStepUuid: PropTypes.string, }); +const ActionConfigMappingError = PropTypes.arrayOf( + PropTypes.shape({ + dmnVariable: PropTypes.string, + formVariable: PropTypes.string, + }) +); + +const ActionConfigError = PropTypes.oneOfType([ + PropTypes.string, + PropTypes.shape({ + inputMapping: ActionConfigMappingError, + outputMapping: ActionConfigMappingError, + }), +]); + const ActionError = PropTypes.shape({ action: PropTypes.shape({ state: PropTypes.string, @@ -34,10 +49,11 @@ const ActionError = PropTypes.shape({ value: PropTypes.string, }), value: PropTypes.string, + config: ActionConfigError, }), component: PropTypes.string, formStep: PropTypes.string, formStepUuid: PropTypes.string, }); -export {jsonLogicVar, Action, ActionError}; +export {jsonLogicVar, Action, ActionError, ActionConfigError}; From 6c963371b4febc9341ee7280805fcda65057117a Mon Sep 17 00:00:00 2001 From: robinvandermolen Date: Wed, 22 Jan 2025 15:26:07 +0100 Subject: [PATCH 8/9] :globe_with_meridians: [#4871] Translation for DMN config error message --- src/openforms/js/compiled-lang/en.json | 6 ++++++ src/openforms/js/compiled-lang/nl.json | 6 ++++++ src/openforms/js/lang/en.json | 5 +++++ src/openforms/js/lang/nl.json | 5 +++++ 4 files changed, 22 insertions(+) diff --git a/src/openforms/js/compiled-lang/en.json b/src/openforms/js/compiled-lang/en.json index 9f47e5745e..17bb7f831d 100644 --- a/src/openforms/js/compiled-lang/en.json +++ b/src/openforms/js/compiled-lang/en.json @@ -4201,6 +4201,12 @@ "value": "Changing the data type requires the initial value to be changed. This will reset the initial value back to the empty value. Are you sure that you want to do this?" } ], + "ag/AZx": [ + { + "type": 0, + "value": "There are errors in the DMN configuration." + } + ], "aqYeqv": [ { "type": 0, diff --git a/src/openforms/js/compiled-lang/nl.json b/src/openforms/js/compiled-lang/nl.json index 90ffdd12fb..fde943f9f8 100644 --- a/src/openforms/js/compiled-lang/nl.json +++ b/src/openforms/js/compiled-lang/nl.json @@ -4215,6 +4215,12 @@ "value": "Het veranderen van het datatype vereist een verandering aan de beginwaarde. Dit zal de beginwaarde terugbrengen naar de standaardwaarde. Weet je zeker dat je dit wilt doen?" } ], + "ag/AZx": [ + { + "type": 0, + "value": "De DMN-instellingen zijn niet geldig." + } + ], "aqYeqv": [ { "type": 0, diff --git a/src/openforms/js/lang/en.json b/src/openforms/js/lang/en.json index 3dffd05340..e162827b17 100644 --- a/src/openforms/js/lang/en.json +++ b/src/openforms/js/lang/en.json @@ -2009,6 +2009,11 @@ "description": "Changing user variable data type and transforming initial value confirmation message", "originalDefault": "Changing the data type requires the initial value to be changed. This will reset the initial value back to the empty value. Are you sure that you want to do this?" }, + "ag/AZx": { + "defaultMessage": "There are errors in the DMN configuration.", + "description": "DMN evaluation configuration errors message", + "originalDefault": "There are errors in the DMN configuration." + }, "auZOcD": { "defaultMessage": "Mapping expression", "description": "Service fetch configuration modal form mapping expression field label", diff --git a/src/openforms/js/lang/nl.json b/src/openforms/js/lang/nl.json index 01b0e1cb0a..c288d645fd 100644 --- a/src/openforms/js/lang/nl.json +++ b/src/openforms/js/lang/nl.json @@ -2027,6 +2027,11 @@ "description": "Changing user variable data type and transforming initial value confirmation message", "originalDefault": "Changing the data type requires the initial value to be changed. This will reset the initial value back to the empty value. Are you sure that you want to do this?" }, + "ag/AZx": { + "defaultMessage": "De DMN-instellingen zijn niet geldig.", + "description": "DMN evaluation configuration errors message", + "originalDefault": "There are errors in the DMN configuration." + }, "auZOcD": { "defaultMessage": "Mappingexpressie", "description": "Service fetch configuration modal form mapping expression field label", From 653eca2fda72d9b430bc2e9852136ab56d1627a1 Mon Sep 17 00:00:00 2001 From: robinvandermolen Date: Wed, 22 Jan 2025 17:17:02 +0100 Subject: [PATCH 9/9] :white_check_mark: [#4871] Storybook tests for variableMapping and evaluate DMN with errors --- .../logic/actions/Action.stories.js | 94 +++++++++++++++++++ .../admin/forms/VariableMapping.stories.js | 43 ++++++++- 2 files changed, 135 insertions(+), 2 deletions(-) diff --git a/src/openforms/js/components/admin/form_design/logic/actions/Action.stories.js b/src/openforms/js/components/admin/form_design/logic/actions/Action.stories.js index 051a7a1aed..6d17a59815 100644 --- a/src/openforms/js/components/admin/form_design/logic/actions/Action.stories.js +++ b/src/openforms/js/components/admin/form_design/logic/actions/Action.stories.js @@ -1,4 +1,5 @@ import {useArgs} from '@storybook/preview-api'; +import {expect, userEvent, waitFor, within} from '@storybook/test'; import {produce} from 'immer'; import set from 'lodash/set'; @@ -197,3 +198,96 @@ export const EvaluateDMN = { }, }, }; + +export const EvaluateDMNWithInitialErrors = { + render, + name: 'Evaluate DMN with initial errors', + args: { + prefixText: 'Action', + + action: { + component: '', + variable: 'bar', + formStep: '', + formStepUuid: '', + + action: { + config: { + pluginId: '', + decisionDefinitionId: '', + }, + type: 'evaluate-dmn', + value: '', + }, + }, + errors: { + action: { + config: { + pluginId: 'This field is required.', + decisionDefinitionId: 'This field is required.', + }, + }, + }, + availableDMNPlugins: [ + {id: 'camunda7', label: 'Camunda 7'}, + {id: 'some-other-engine', label: 'Some other engine'}, + ], + availableFormVariables: [ + {type: 'textfield', key: 'name', name: 'Name'}, + {type: 'textfield', key: 'surname', name: 'Surname'}, + {type: 'number', key: 'income', name: 'Income'}, + {type: 'checkbox', key: 'canApply', name: 'Can apply?'}, + ], + }, + decorators: [FormDecorator], + + parameters: { + msw: { + handlers: [ + mockDMNDecisionDefinitionsGet({ + camunda7: [ + { + id: 'approve-payment', + label: 'Approve payment', + }, + { + id: 'invoiceClassification', + label: 'Invoice Classification', + }, + ], + 'some-other-engine': [{id: 'some-definition-id', label: 'Some definition id'}], + }), + mockDMNDecisionDefinitionVersionsGet, + ], + }, + }, + play: async ({canvasElement, step}) => { + const canvas = within(canvasElement); + + step('Verify that global DMN config error is shown', () => { + expect( + canvas.getByRole('listitem', {text: 'De DMN-instellingen zijn niet geldig.'}) + ).toBeVisible(); + }); + + step('Open configuration modal', async () => { + await userEvent.click(canvas.getByRole('button', {name: 'Instellen'})); + + const dialog = within(canvas.getByRole('dialog')); + + const pluginDropdown = dialog.getByLabelText('Plugin'); + const decisionDefDropdown = dialog.getByLabelText('Beslisdefinitie-ID'); + + // Mark dropdowns as touched + await userEvent.click(pluginDropdown); + await userEvent.click(decisionDefDropdown); + await userEvent.tab(); + + await waitFor(async () => { + const errorMessages = await dialog.getAllByRole('listitem'); + + await expect(errorMessages.length).toBe(2); + }); + }); + }, +}; diff --git a/src/openforms/js/components/admin/forms/VariableMapping.stories.js b/src/openforms/js/components/admin/forms/VariableMapping.stories.js index b6800d5c49..774d4784a0 100644 --- a/src/openforms/js/components/admin/forms/VariableMapping.stories.js +++ b/src/openforms/js/components/admin/forms/VariableMapping.stories.js @@ -1,7 +1,11 @@ import {expect, fn, userEvent, within} from '@storybook/test'; import selectEvent from 'react-select-event'; -import {FormDecorator, FormikDecorator} from 'components/admin/form_design/story-decorators'; +import { + FormDecorator, + FormikDecorator, + ValidationErrorsDecorator, +} from 'components/admin/form_design/story-decorators'; import {VARIABLE_SOURCES} from 'components/admin/form_design/variables/constants'; import {findReactSelectMenu} from 'utils/storybookTestHelpers'; @@ -10,7 +14,7 @@ import VariableMapping, {serializeValue} from './VariableMapping'; export default { title: 'Form design/VariableMapping', component: VariableMapping, - decorators: [FormikDecorator, FormDecorator], + decorators: [FormikDecorator, ValidationErrorsDecorator, FormDecorator], parameters: { formik: { @@ -224,3 +228,38 @@ export const OmitAlreadyMappedValues = { }); }, }; + +export const WithValidationErrors = { + render: args => ( + <> + + + + ), + + args: { + propertyChoices: [ + [['nested', 'property'], 'Nested > property'], + [['otherProperty'], 'Other property'], + ], + }, + + parameters: { + formik: { + initialValues: { + mapping: [{formVariable: 'key2'}, {formVariable: 'key2', property: ['otherProperty']}, {}], + }, + onSubmit: fn(), + }, + validationErrors: [ + [ + 'mapping', + [ + {property: 'This field may not be blank.'}, + undefined, + {formVariable: 'This field may not be blank.', property: 'This field may not be blank.'}, + ], + ], + ], + }, +};