Skip to content

Commit

Permalink
✨ [#4608] Modal for Objects API prefill options
Browse files Browse the repository at this point in the history
this is the initial implementation, it is not yet fully functional, since it relies on backend changes that are not merged yet.
It will be connected with the backend in #4693
  • Loading branch information
stevenbal authored and robinmolen committed Oct 15, 2024
1 parent b261061 commit 98a1287
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 98a1287

Please sign in to comment.