Skip to content

Commit

Permalink
✨ [#4608] Modal for Objects API prefill options
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenbal committed Sep 23, 2024
1 parent 70771b7 commit a8f7deb
Show file tree
Hide file tree
Showing 15 changed files with 555 additions and 214 deletions.
2 changes: 2 additions & 0 deletions src/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9669,7 +9669,9 @@ components:
title: Required authentication attribute
description: The authentication attribute required for this plugin to lookup
remote data.
extraData: {}
required:
- extraData
- id
- label
- requiresAuth
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ const processInputParams = params => {
};

const DMNParametersForm = () => {
const intl = useIntl();
const {values, setValues} = useFormikContext();
const {pluginId, decisionDefinitionId, decisionDefinitionVersion, inputMapping, outputMapping} =
values;
Expand Down Expand Up @@ -172,6 +173,15 @@ const DMNParametersForm = () => {
dmnParams,
]);

const dmnVariableColumnLabel = intl.formatMessage({
description: 'DMN variable label',
defaultMessage: 'DMN variable',
});
const dmnVariableSelectAriaLabel = intl.formatMessage({
description: 'Accessible label for DMN variable dropdown',
defaultMessage: 'DMN variable',
});

return (
<div className="logic-dmn">
<div className="logic-dmn__mapping-config">
Expand All @@ -182,7 +192,11 @@ const DMNParametersForm = () => {
<VariableMapping
loading={loading}
mappingName="inputMapping"
dmnVariables={dmnParams.inputs}
targets={dmnParams.inputs}
targetsFieldName="dmnVariable"
targetsColumnLabel={dmnVariableColumnLabel}
selectAriaLabel={dmnVariableSelectAriaLabel}
cssBlockName="logic-dmn"
alreadyMapped={inputMapping.map(mapping => mapping.dmnVariable)}
includeStaticVariables
/>
Expand All @@ -195,7 +209,11 @@ const DMNParametersForm = () => {
<VariableMapping
loading={loading}
mappingName="outputMapping"
dmnVariables={dmnParams.outputs}
targets={dmnParams.outputs}
targetsFieldName="dmnVariable"
targetsColumnLabel={dmnVariableColumnLabel}
selectAriaLabel={dmnVariableSelectAriaLabel}
cssBlockName="logic-dmn"
alreadyMapped={outputMapping.map(mapping => mapping.dmnVariable)}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {FieldArray, useFormikContext} from 'formik';
import get from 'lodash/get';
import PropTypes from 'prop-types';
import React from 'react';
import {FormattedMessage, useIntl} from 'react-intl';
Expand All @@ -15,8 +16,10 @@ const VariableMappingRow = ({
loading,
prefix,
onRemove,
targets,
targetsFieldName,
selectAriaLabel,
includeStaticVariables = false,
dmnVariables,
alreadyMapped = [],
}) => {
const intl = useIntl();
Expand All @@ -27,10 +30,9 @@ const VariableMappingRow = ({
defaultMessage: 'Are you sure that you want to remove this mapping?',
});

const dmnVariableProps = getFieldProps(`${prefix}.dmnVariable`);

const dmnVariableChoices = dmnVariables.filter(
([value]) => value === dmnVariableProps.value || !alreadyMapped.includes(value)
const targetsProps = getFieldProps(`${prefix}.${targetsFieldName}`);
const targetsChoices = targets.filter(
([value]) => value === targetsProps.value || !alreadyMapped.includes(value)
);

const mapping = getFieldProps(prefix).value;
Expand All @@ -51,17 +53,14 @@ const VariableMappingRow = ({
</Field>
</td>
<td>
<Field htmlFor={`${prefix}.dmnVariable`} name={`${prefix}.dmnVariable`}>
<Field htmlFor={`${prefix}.${targetsFieldName}`} name={`${prefix}.${targetsFieldName}`}>
<Select
id={`${prefix}.dmnVariable`}
id={`${prefix}.${targetsFieldName}`}
allowBlank
disabled={loading}
choices={dmnVariableChoices}
{...dmnVariableProps}
aria-label={intl.formatMessage({
description: 'Accessible label for DMN variable dropdown',
defaultMessage: 'DMN variable',
})}
choices={targetsChoices}
{...targetsProps}
aria-label={selectAriaLabel}
/>
</Field>
</td>
Expand All @@ -76,7 +75,9 @@ const VariableMappingRow = ({
VariableMappingRow.propTypes = {
loading: PropTypes.bool.isRequired,
prefix: PropTypes.string.isRequired,
dmnVariables: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)).isRequired,
targets: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)).isRequired,
targetsFieldName: PropTypes.string.isRequired,
selectAriaLabel: PropTypes.string.isRequired,
onRemove: PropTypes.func.isRequired,
includeStaticVariables: PropTypes.bool,
alreadyMapped: PropTypes.arrayOf(PropTypes.string),
Expand All @@ -85,7 +86,11 @@ VariableMappingRow.propTypes = {
const VariableMapping = ({
loading,
mappingName,
dmnVariables,
targets,
targetsFieldName,
targetsColumnLabel,
selectAriaLabel,
cssBlockName,
includeStaticVariables = false,
alreadyMapped = [],
}) => {
Expand All @@ -95,7 +100,7 @@ const VariableMapping = ({
<FieldArray
name={mappingName}
render={arrayHelpers => (
<div className="logic-dmn__mapping-table">
<div className={`${cssBlockName}__mapping-table`}>
<table>
<thead>
<tr>
Expand All @@ -105,33 +110,32 @@ const VariableMapping = ({
description="Open Forms variable label"
/>
</th>
<th>
<FormattedMessage
defaultMessage="DMN variable"
description="DMN variable label"
/>
</th>
<th>{targetsColumnLabel}</th>
<th></th>
</tr>
</thead>
<tbody>
{values[mappingName].map((_, index) => (
{get(values, mappingName).map((_, index) => (
<VariableMappingRow
key={index}
prefix={`${mappingName}.${index}`}
onRemove={() => arrayHelpers.remove(index)}
loading={loading}
includeStaticVariables={includeStaticVariables}
dmnVariables={dmnVariables}
targets={targets}
targetsFieldName={targetsFieldName}
selectAriaLabel={selectAriaLabel}
alreadyMapped={alreadyMapped}
/>
))}
</tbody>
</table>
<ButtonContainer
onClick={() =>
arrayHelpers.insert(values[mappingName].length, {formVariable: '', dmnVariable: ''})
}
onClick={() => {
const initial = {formVariable: ''};
initial[targetsFieldName] = '';
arrayHelpers.insert(get(values, mappingName).length, initial);
}}
>
<FormattedMessage description="Add variable button" defaultMessage="Add variable" />
</ButtonContainer>
Expand All @@ -145,7 +149,11 @@ VariableMapping.propTypes = {
loading: PropTypes.bool,
mappingName: PropTypes.string,
includeStaticVariables: PropTypes.bool,
dmnVariables: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
targets: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
targetsFieldName: PropTypes.string,
targetsColumnLabel: PropTypes.string.isRequired,
selectAriaLabel: PropTypes.string.isRequired,
cssBlockName: PropTypes.string,
alreadyMapped: PropTypes.arrayOf(PropTypes.string),
};

Expand Down
10 changes: 10 additions & 0 deletions src/openforms/js/components/admin/form_design/mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,13 @@ export const mockPrefillAttributesGet = pluginAttributes =>
const attributeList = pluginAttributes[plugin] || [];
return res(ctx.json(attributeList));
});

export const mockObjectsAPIPrefillAttributesGet = pluginAttributes =>
rest.get(
`${API_BASE_URL}/api/v2/prefill/plugins/objects-api/objecttypes/:uuid/versions/:version/properties`,
(req, res, ctx) => {
const {uuid, version} = req.params;
const attributeList = pluginAttributes[uuid][version] || [];
return res(ctx.json(attributeList));
}
);
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {useField, useFormikContext} from 'formik';
import _ from 'lodash';
import PropTypes from 'prop-types';
import {FormattedMessage} from 'react-intl';
import {usePrevious, useUpdateEffect} from 'react-use';
Expand All @@ -20,13 +21,15 @@ const getAvailableObjectTypes = async apiGroupID => {
return response.data;
};

const ObjectTypeSelect = ({onChangeCheck}) => {
const [fieldProps, , fieldHelpers] = useField('objecttype');
const ObjectTypeSelect = ({onChangeCheck, prefix = undefined}) => {
const namePrefix = prefix ? `${prefix}.` : '';
const [fieldProps, , fieldHelpers] = useField(`${namePrefix}objecttype`);
const {
values: {objectsApiGroup = null},
values,
setFieldValue,
initialValues: {objecttype: initialObjecttype},
} = useFormikContext();
const objectsApiGroup = _.get(values, `${namePrefix}objectsApiGroup`, null);
const {value} = fieldProps;
const {setValue} = fieldHelpers;

Expand All @@ -48,21 +51,21 @@ const ObjectTypeSelect = ({onChangeCheck}) => {
]);
const options = choices.map(([value, label]) => ({value, label}));

useSynchronizeSelect('objecttype', loading, choices);
useSynchronizeSelect(`${namePrefix}objecttype`, loading, choices);

const previousValue = usePrevious(value);

// when a different object type is selected, ensure that the version is reset
useUpdateEffect(() => {
if (loading) return;
if (value === initialObjecttype || value === previousValue) return;
setFieldValue('objecttypeVersion', undefined); // clears the value
setFieldValue(`${namePrefix}objecttypeVersion`, undefined); // clears the value
}, [loading, value]);

return (
<FormRow>
<Field
name="objecttype"
name={`${namePrefix}objecttype`}
required
label={
<FormattedMessage
Expand All @@ -79,7 +82,7 @@ const ObjectTypeSelect = ({onChangeCheck}) => {
noManageChildProps
>
<ReactSelect
name="objecttype"
name={`${namePrefix}objecttype`}
options={options}
isLoading={loading}
isDisabled={!objectsApiGroup}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {useFormikContext} from 'formik';
import _ from 'lodash';
import {FormattedMessage} from 'react-intl';
import useAsync from 'react-use/esm/useAsync';

Expand All @@ -24,10 +25,11 @@ const getAvailableVersions = async (uuid, apiGroupID) => {
return versions.sort((v1, v2) => v2.version - v1.version);
};

const ObjectTypeVersionSelect = () => {
const {
values: {objectsApiGroup = null, objecttype = ''},
} = useFormikContext();
const ObjectTypeVersionSelect = ({prefix = undefined}) => {
const namePrefix = prefix ? `${prefix}.` : '';
const {values} = useFormikContext();
const objectsApiGroup = _.get(values, `${namePrefix}objectsApiGroup`, null);
const objecttype = _.get(values, `${namePrefix}objecttype`, '');

const {
loading,
Expand All @@ -43,13 +45,13 @@ const ObjectTypeVersionSelect = () => {
? []
: versions.map(version => [version.version, `${version.version} (${version.status})`]);

useSynchronizeSelect('objecttypeVersion', loading, choices);
useSynchronizeSelect(`${namePrefix}objecttypeVersion`, loading, choices);

const options = choices.map(([value, label]) => ({value, label}));
return (
<FormRow>
<Field
name="objecttypeVersion"
name={`${namePrefix}objecttypeVersion`}
required
label={
<FormattedMessage
Expand All @@ -60,7 +62,7 @@ const ObjectTypeVersionSelect = () => {
noManageChildProps
>
<ReactSelect
name="objecttypeVersion"
name={`${namePrefix}objecttypeVersion`}
required
options={options}
isLoading={loading}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {useField, useFormikContext} from 'formik';
import _ from 'lodash';
import PropTypes from 'prop-types';
import {FormattedMessage} from 'react-intl';
import {useUpdateEffect} from 'react-use';
Expand All @@ -7,26 +8,30 @@ import Field from 'components/admin/forms/Field';
import FormRow from 'components/admin/forms/FormRow';
import ReactSelect from 'components/admin/forms/ReactSelect';

const ObjectsAPIGroup = ({apiGroupChoices, onChangeCheck}) => {
const [{onChange: onChangeFormik, ...fieldProps}, , {setValue}] = useField('objectsApiGroup');
const ObjectsAPIGroup = ({apiGroupChoices, onChangeCheck, prefix = undefined}) => {
const namePrefix = prefix ? `${prefix}.` : '';
const [{onChange: onChangeFormik, ...fieldProps}, , {setValue}] = useField(
`${namePrefix}objectsApiGroup`
);
const {setValues} = useFormikContext();
const {value} = fieldProps;

// reset the objecttype specific-configuration whenever the API group changes
useUpdateEffect(() => {
setValues(prevValues => ({
...prevValues,
objecttype: '',
objecttypeVersion: undefined,
variablesMapping: [],
}));
setValues(prevValues => {
const newValues = {...prevValues};
_.set(newValues, `${namePrefix}objecttype`, '');
_.set(newValues, `${namePrefix}objecttypeVersion`, undefined);
_.set(newValues, `${namePrefix}variablesMapping`, []);
return newValues;
});
}, [setValues, value]);

const options = apiGroupChoices.map(([value, label]) => ({value, label}));
return (
<FormRow>
<Field
name="objectsApiGroup"
name={`${namePrefix}objectsApiGroup`}
required
label={
<FormattedMessage
Expand All @@ -43,7 +48,7 @@ const ObjectsAPIGroup = ({apiGroupChoices, onChangeCheck}) => {
noManageChildProps
>
<ReactSelect
name="objectsApiGroup"
name={`${namePrefix}objectsApiGroup`}
options={options}
required
onChange={selectedOption => {
Expand Down
Loading

0 comments on commit a8f7deb

Please sign in to comment.