From 0522f5edde5a01f1641b23ad340eede84aebc4e1 Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Wed, 8 May 2024 08:24:50 +0200 Subject: [PATCH 01/11] :bug: Fixed import bug in Storybook stories file --- .../registrations/zgw/ZGWOptionsFormFields.stories.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openforms/js/components/admin/form_design/registrations/zgw/ZGWOptionsFormFields.stories.js b/src/openforms/js/components/admin/form_design/registrations/zgw/ZGWOptionsFormFields.stories.js index c399ebd000..a1c9d33ecd 100644 --- a/src/openforms/js/components/admin/form_design/registrations/zgw/ZGWOptionsFormFields.stories.js +++ b/src/openforms/js/components/admin/form_design/registrations/zgw/ZGWOptionsFormFields.stories.js @@ -6,7 +6,7 @@ import Fieldset from 'components/admin/forms/Fieldset'; import FormRow from 'components/admin/forms/FormRow'; import ZGWFormFields from './ZGWOptionsFormFields'; -import VariablePropertyModal from './ZGWOptionsVariablesProperties'; +import {VariablePropertyModal} from './ZGWOptionsVariablesProperties'; const render = ({index, label, name, schema}) => { const [{formData}, updateArgs] = useArgs(); From c21fba3d32c0d4a5cf70a0772905cc821298d139 Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Wed, 8 May 2024 08:39:05 +0200 Subject: [PATCH 02/11] :white_check_mark: [#4269] Add story for complex input expressions --- .../actions/dmn/DMNActionConfig.stories.js | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/openforms/js/components/admin/form_design/logic/actions/dmn/DMNActionConfig.stories.js b/src/openforms/js/components/admin/form_design/logic/actions/dmn/DMNActionConfig.stories.js index 8889b365a8..ac7703b2aa 100644 --- a/src/openforms/js/components/admin/form_design/logic/actions/dmn/DMNActionConfig.stories.js +++ b/src/openforms/js/components/admin/form_design/logic/actions/dmn/DMNActionConfig.stories.js @@ -25,6 +25,7 @@ export default { {type: 'textfield', key: 'surname', name: 'Surname'}, {type: 'number', key: 'income', name: 'Income'}, {type: 'checkbox', key: 'canApply', name: 'Can apply?'}, + {type: 'postcode', key: 'postcode', name: 'Postcode'}, ], }, parameters: { @@ -40,6 +41,10 @@ export default { id: 'invoiceClassification', label: 'Invoice Classification', }, + { + id: 'withComplexExpressions', + label: 'Complex expression in inputs', + }, ], 'some-other-engine': [{id: 'some-definition-id', label: 'Some definition id'}], }), @@ -115,6 +120,30 @@ export default { }, ], }, + withComplexExpressions: { + inputs: [ + { + label: 'Sum of a and b', + id: '', + type_ref: 'integer', + expression: 'a + b', + }, + { + label: 'Numeric part postcode', + id: '', + type_ref: 'integer', + expression: 'number(substring(postcode, 1, 4))', + }, + ], + outputs: [ + { + id: 'OutputClause_1cthd0w', + label: 'Sole output', + type_ref: 'string', + name: 'result', + }, + ], + }, }), ], }, @@ -319,3 +348,19 @@ export const OnePluginAvailable = { await expect(pluginDropdown.value).toBe('camunda7'); }, }; + +export const ComplexInputExpressions = { + args: { + initialValues: { + pluginId: 'camunda7', + decisionDefinitionId: 'withComplexExpressions', + decisionDefinitionVersion: '1', + inputMapping: [ + {formVariable: 'postcode', dmnVariable: 'postcode'}, + {formVariable: 'income', dmnVariable: 'a'}, + {formVariable: 'income', dmnVariable: 'b'}, + ], + outputMapping: [], + }, + }, +}; From 76688e7c569f4e75dc17000ef6cb92ad01a58704 Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Wed, 8 May 2024 08:42:36 +0200 Subject: [PATCH 03/11] :heavy_plus_sign: [#4269] Add FEEL parser for DMN integration The input expressions require additional parsing to properly extract the required input variables for a decision table. --- package-lock.json | 100 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 101 insertions(+) diff --git a/package-lock.json b/package-lock.json index 88641a2476..b527db4ad3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "classnames": "^2.3.1", "copy-to-clipboard": "^3.3.1", "design-token-editor": "^0.6.0", + "feelin": "^3.1.0", "flatpickr": "^4.6.9", "formik": "^2.2.9", "formiojs": "~4.13.0", @@ -4398,6 +4399,27 @@ "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", "dev": true }, + "node_modules/@lezer/common": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz", + "integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.0.tgz", + "integrity": "sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz", + "integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, "node_modules/@mdx-js/react": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-2.3.0.tgz", @@ -16156,6 +16178,19 @@ "pend": "~1.2.0" } }, + "node_modules/feelin": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/feelin/-/feelin-3.1.0.tgz", + "integrity": "sha512-ITPATtpwDWeLr7FKEAai7mJPlIH0td+D58f61+ZFDOs6Gg+8mFIo1LlhltQOeLkmZlOdvC/RsovbZ7SqxUfoyQ==", + "dependencies": { + "@lezer/lr": "^1.3.9", + "lezer-feel": "^1.2.8", + "luxon": "^3.4.4" + }, + "engines": { + "node": "*" + } + }, "node_modules/fetch-ponyfill": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/fetch-ponyfill/-/fetch-ponyfill-7.1.0.tgz", @@ -20162,6 +20197,18 @@ "node": ">=6" } }, + "node_modules/lezer-feel": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/lezer-feel/-/lezer-feel-1.2.8.tgz", + "integrity": "sha512-CO5JEpwNhH1p8mmRRcqMjJrYxO3vNx0nEsF9Ak4OPa1pNHEqvJ2rwYwM9LjZ7jh/Sl5FxbTJT/teF9a+zWmflg==", + "dependencies": { + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.4.0" + }, + "engines": { + "node": "*" + } + }, "node_modules/lilconfig": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", @@ -20396,6 +20443,14 @@ "yallist": "^3.0.2" } }, + "node_modules/luxon": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "engines": { + "node": ">=12" + } + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", @@ -29412,6 +29467,27 @@ "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", "dev": true }, + "@lezer/common": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz", + "integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==" + }, + "@lezer/highlight": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.0.tgz", + "integrity": "sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==", + "requires": { + "@lezer/common": "^1.0.0" + } + }, + "@lezer/lr": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz", + "integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==", + "requires": { + "@lezer/common": "^1.0.0" + } + }, "@mdx-js/react": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-2.3.0.tgz", @@ -38007,6 +38083,16 @@ "pend": "~1.2.0" } }, + "feelin": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/feelin/-/feelin-3.1.0.tgz", + "integrity": "sha512-ITPATtpwDWeLr7FKEAai7mJPlIH0td+D58f61+ZFDOs6Gg+8mFIo1LlhltQOeLkmZlOdvC/RsovbZ7SqxUfoyQ==", + "requires": { + "@lezer/lr": "^1.3.9", + "lezer-feel": "^1.2.8", + "luxon": "^3.4.4" + } + }, "fetch-ponyfill": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/fetch-ponyfill/-/fetch-ponyfill-7.1.0.tgz", @@ -40976,6 +41062,15 @@ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true }, + "lezer-feel": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/lezer-feel/-/lezer-feel-1.2.8.tgz", + "integrity": "sha512-CO5JEpwNhH1p8mmRRcqMjJrYxO3vNx0nEsF9Ak4OPa1pNHEqvJ2rwYwM9LjZ7jh/Sl5FxbTJT/teF9a+zWmflg==", + "requires": { + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.4.0" + } + }, "lilconfig": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", @@ -41169,6 +41264,11 @@ "yallist": "^3.0.2" } }, + "luxon": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==" + }, "lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", diff --git a/package.json b/package.json index dde2b6de0c..e15ff028c3 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "classnames": "^2.3.1", "copy-to-clipboard": "^3.3.1", "design-token-editor": "^0.6.0", + "feelin": "^3.1.0", "flatpickr": "^4.6.9", "formik": "^2.2.9", "formiojs": "~4.13.0", From f6ea932c2526e299b1a49974620efd0ab5121987 Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Wed, 8 May 2024 16:42:59 +0200 Subject: [PATCH 04/11] :sparkles: [#4269] Add overview table of defined input clauses for selected table This should help form builders configure DMN tables to see which input expressions are expected by the decision engine. --- .../logic/actions/dmn/DMNParametersForm.js | 10 ++- .../logic/actions/dmn/InputsOverview.js | 71 +++++++++++++++++++ .../scss/components/admin/_ReactModal.scss | 7 ++ .../scss/components/admin/_mappings.scss | 9 ++- 4 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 src/openforms/js/components/admin/form_design/logic/actions/dmn/InputsOverview.js diff --git a/src/openforms/js/components/admin/form_design/logic/actions/dmn/DMNParametersForm.js b/src/openforms/js/components/admin/form_design/logic/actions/dmn/DMNParametersForm.js index 04bf2ab9b3..aa7e204dab 100644 --- a/src/openforms/js/components/admin/form_design/logic/actions/dmn/DMNParametersForm.js +++ b/src/openforms/js/components/admin/form_design/logic/actions/dmn/DMNParametersForm.js @@ -7,6 +7,7 @@ import {FormContext} from 'components/admin/form_design/Context'; import {DMN_DECISION_DEFINITIONS_PARAMS_LIST} from 'components/admin/form_design/constants'; import {get} from 'utils/fetch'; +import InputsOverview from './InputsOverview'; import VariableMapping from './VariableMapping'; const EMPTY_DMN_PARAMS = {inputs: [], outputs: []}; @@ -34,7 +35,8 @@ const DMNParametersForm = () => { const response = await get(DMN_DECISION_DEFINITIONS_PARAMS_LIST, queryParams); return { - inputs: response.data.inputs.map(inputParam => [inputParam.expression, inputParam.label]), + inputClauses: response.data.inputs, + inputs: inputs, outputs: response.data.outputs.map(outputParam => [outputParam.name, outputParam.label]), }; }, [pluginId, decisionDefinitionId, decisionDefinitionVersion]); @@ -43,8 +45,10 @@ const DMNParametersForm = () => { return (
+ +
-

+

{ />
-

+

( +
+

+ +

+

+ +

+ + + + + + + + + + + {inputClauses.map((inputClause, index) => ( + + + + + + ))} + +
+ + + + + +
{inputClause.label || '-'} + {inputClause.expression} + + {inputClause.typeRef || '-'} +
+
+); + +InputsOverview.propTypes = { + inputClauses: PropTypes.arrayOf( + PropTypes.shape({ + label: PropTypes.string, + expression: PropTypes.string.isRequired, + typeRef: PropTypes.string, + }) + ), +}; + +export default InputsOverview; diff --git a/src/openforms/scss/components/admin/_ReactModal.scss b/src/openforms/scss/components/admin/_ReactModal.scss index 7f36311952..836074839e 100644 --- a/src/openforms/scss/components/admin/_ReactModal.scss +++ b/src/openforms/scss/components/admin/_ReactModal.scss @@ -78,6 +78,13 @@ margin-block-end: 0; } + &__section-title { + font-weight: 300; + font-size: 1rem; + margin-block-start: 0; + margin-block-end: 0; + } + &__form { display: flex; flex-direction: column; diff --git a/src/openforms/scss/components/admin/_mappings.scss b/src/openforms/scss/components/admin/_mappings.scss index 505a398b62..a2582b082f 100644 --- a/src/openforms/scss/components/admin/_mappings.scss +++ b/src/openforms/scss/components/admin/_mappings.scss @@ -1,10 +1,17 @@ @use 'microscope-sass/lib/bem'; .mappings { + display: flex; + flex-direction: column; + gap: 20px; + + @include bem.element('info') { + padding-block-start: 20px; + } + @include bem.element('mapping') { display: flex; flex-direction: column; gap: 20px; - padding-block-start: 20px; } } From cb3993fd3f9c47ab01234010f3ed6163fd4ea6d9 Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Wed, 8 May 2024 16:48:49 +0200 Subject: [PATCH 05/11] :sparkles: [#4269] Process input expressions from the DMN engine Input expressions can be complex with the actual requested variable buried deep inside. We leverage the 'feelin' library to parse FEEL expressions so that we can extract the actual (likely) expected variables and combine this information with our retrieved input clauses. This result is used to build a list of DMN variables for input mapping - this list is ordered to put labeled literal variables at the top, followed by the extracted unlabeled variables, and finally the legal identifiers that are labeled but likely complex/edge cases. This implements the DMN 1.3 FEEL identifier specification to select candidates. --- .../logic/actions/dmn/DMNParametersForm.js | 92 ++++++++++++++++++- .../form_design/logic/actions/dmn/utils.js | 49 ++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 src/openforms/js/components/admin/form_design/logic/actions/dmn/utils.js diff --git a/src/openforms/js/components/admin/form_design/logic/actions/dmn/DMNParametersForm.js b/src/openforms/js/components/admin/form_design/logic/actions/dmn/DMNParametersForm.js index aa7e204dab..2f6c0d8381 100644 --- a/src/openforms/js/components/admin/form_design/logic/actions/dmn/DMNParametersForm.js +++ b/src/openforms/js/components/admin/form_design/logic/actions/dmn/DMNParametersForm.js @@ -1,3 +1,4 @@ +import {parseExpression} from 'feelin'; import {useFormikContext} from 'formik'; import React, {useContext, useState} from 'react'; import {FormattedMessage} from 'react-intl'; @@ -9,8 +10,95 @@ import {get} from 'utils/fetch'; import InputsOverview from './InputsOverview'; import VariableMapping from './VariableMapping'; +import {namePattern} from './utils'; -const EMPTY_DMN_PARAMS = {inputs: [], outputs: []}; +const EMPTY_DMN_PARAMS = { + inputClauses: [], + inputs: [], + outputs: [], +}; + +/** + * @typedef InputClause + * @type {object} + * @property {string} expression - the FEEL expression of the input clause + * @property {string} label - the human-readable input clause label + * + * @typedef {string} OptionValue - The value of a dropdown option. + * @typedef {string} OptionLabel - The label of a dropdown option. + * @typedef {[OptionValue, OptionLabel]} Option - A dropdown option. + */ + +/** + * Process the input parameters and their expressions. + * + * Each input parameter has a FEEL expression, which itself can be a 'complex' + * expression requiring individual variables, e.g. `a + b`. + * + * Note that expressions are ambiguous without context, `a+b` could mean that input + * variables `a` and `b` are required, but you could also provide a single input with + * the name `"a+b"` (this is valid FEEL!). + * + * @param {InputClause[]} params The input clauses extracted from the decision definition. + * @return {Option[]} An array of two-tuples (value, label). + */ +const processInputParams = params => { + const variableExpressionsWithLabels = params + // check each expression if it can possibly be a valid identifier itself. These + // should be the most common cases. + .filter(param => namePattern.test(param.expression)) + .map(param => [param.expression, param.label]); + + // for (simple) expressions, we can grab the explicit label from the input parameter + // if it's defined. These variables will come up again when we process each expression + // as a FEEL expression. + const expressionLabels = Object.fromEntries(variableExpressionsWithLabels); + + // process each expression individually and add the extract variables to the + // possible variables. This includes the most simple e + const extractedVariables = []; + for (const {expression} of params) { + // docs: https://lezer.codemirror.net/docs/ref/#common.Tree.iterate + const tree = parseExpression(expression); + tree.iterate({ + enter({name, from, to, node: {parent}}) { + if (name !== 'Identifier') return; + if (parent.name !== 'VariableName') return; + + // check if this var identifier is a function, we need to ignore those. + const isFunction = parent?.parent.name === 'FunctionInvocation'; + if (isFunction) return; + const varName = expression.substring(from, to); + if (extractedVariables.includes(varName)) return; + extractedVariables.push(varName); + }, + }); + } + + // We classify the input parameters in two buckets - explicit labels and parsed + // 'labels' (the same as the variable name, really). Explicit simple input vars with + // labels (and thus simple expressions) are favoured. + // + // It's possible an expression like `a+b` is in variableExpressionsWithLabels without + // being in extractedVariables - therefore we add those after all the common variable + // patterns are processed. + const labeledOptions = []; + const unlabeledOptions = []; + for (const varName of extractedVariables) { + const label = expressionLabels[varName]; + const target = label !== undefined ? labeledOptions : unlabeledOptions; + target.push([varName, label || varName]); + } + const possibleVariables = [...labeledOptions, ...unlabeledOptions]; + + // add weird-but-valid labeled expressions that were not picked up by the expression + // parsing (this parsing is context dependent and cases like `a+b` are ambiguous). + const weirdCases = variableExpressionsWithLabels.filter( + ([varName]) => !extractedVariables.includes(varName) + ); + + return possibleVariables.concat(weirdCases); +}; const DMNParametersForm = () => { const { @@ -34,6 +122,8 @@ const DMNParametersForm = () => { const response = await get(DMN_DECISION_DEFINITIONS_PARAMS_LIST, queryParams); + const inputs = processInputParams(response.data.inputs); + return { inputClauses: response.data.inputs, inputs: inputs, diff --git a/src/openforms/js/components/admin/form_design/logic/actions/dmn/utils.js b/src/openforms/js/components/admin/form_design/logic/actions/dmn/utils.js new file mode 100644 index 0000000000..af46b7b08d --- /dev/null +++ b/src/openforms/js/components/admin/form_design/logic/actions/dmn/utils.js @@ -0,0 +1,49 @@ +/** + * DMN utilities. + */ + +// Build a regex for variable name. Specification: https://www.omg.org/spec/DMN/1.3/PDF, +// section 10.3.1.2. + +const nameStartChars = [ + // a-z (case insensitive) and literal `?` and `_` characters + '?A-Za-z_', + // unicode ranges + '\\u00C0-\\u00D6', + '\\u00D8-\\u00F6', + '\\u00F8-\\u02FF', + '\\u0370-\\u037D', + '\\u037F-\\u1FFF', + '\\u200C-\\u200D', + '\\u2070-\\u218F', + '\\u2C00-\\u2FEF', + '\\u3001-\\uD7FF', + '\\uF900-\\uFDCF', + '\\uFDF0-\\uFFFD', + // additional plane, the basic plane ranges only to U+FFFF + '\\u{10000}-\\u{EFFFF}', +].join(''); + +const namePartChars = [ + nameStartChars, + '0-9', // digits + '\\u00B7', + '\\u0300-\\u036F', + '\\u203F-\\u2040', + '.', + '/', + '-', + '’', + '+', + '*', +].join(''); + +/** + * Test for if an expression is possibly a variable name itself. + * + * See the DMN 1.3 spec, sections 10.3.1.2 and 10.3.1.4 for the full grammar and rules. + * + * The regex needs to be unicode aware to support multi-plane unicode ranges (that's the + * \u{...} syntax above). + */ +export const namePattern = new RegExp(`^[${nameStartChars}][${namePartChars}]*$`, 'u'); From 64cd35e8bcdd9885315abf9294d0241b02902777 Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Wed, 8 May 2024 16:51:55 +0200 Subject: [PATCH 06/11] :children_crossing: [#4269] Cleanup and improve a11y Use the existing VariableSelection component to render a dropdown of available Open Forms variables. Added an accessible label to the dropdowns in the mapping tables. --- .../logic/actions/dmn/DMNParametersForm.js | 2 +- .../logic/actions/dmn/VariableMapping.js | 13 ++++++++++--- .../js/components/admin/forms/VariableSelection.js | 13 +++++++++++-- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/openforms/js/components/admin/form_design/logic/actions/dmn/DMNParametersForm.js b/src/openforms/js/components/admin/form_design/logic/actions/dmn/DMNParametersForm.js index 2f6c0d8381..5ac5244c1b 100644 --- a/src/openforms/js/components/admin/form_design/logic/actions/dmn/DMNParametersForm.js +++ b/src/openforms/js/components/admin/form_design/logic/actions/dmn/DMNParametersForm.js @@ -1,6 +1,6 @@ import {parseExpression} from 'feelin'; import {useFormikContext} from 'formik'; -import React, {useContext, useState} from 'react'; +import React, {useContext} from 'react'; import {FormattedMessage} from 'react-intl'; import {useAsync} from 'react-use'; diff --git a/src/openforms/js/components/admin/form_design/logic/actions/dmn/VariableMapping.js b/src/openforms/js/components/admin/form_design/logic/actions/dmn/VariableMapping.js index 789f1f7d66..cf6a6c824c 100644 --- a/src/openforms/js/components/admin/form_design/logic/actions/dmn/VariableMapping.js +++ b/src/openforms/js/components/admin/form_design/logic/actions/dmn/VariableMapping.js @@ -7,6 +7,7 @@ import DeleteIcon from 'components/admin/DeleteIcon'; import ButtonContainer from 'components/admin/forms/ButtonContainer'; import Field from 'components/admin/forms/Field'; import Select from 'components/admin/forms/Select'; +import VariableSelection from 'components/admin/forms/VariableSelection'; const VariableMapping = ({loading, mappingName, formVariables, dmnVariables}) => { const intl = useIntl(); @@ -48,11 +49,13 @@ const VariableMapping = ({loading, mappingName, formVariables, dmnVariables}) => name={`${mappingName}.${index}.formVariable`} htmlFor={`${mappingName}.${index}.formVariable`} > - +