Skip to content

Commit

Permalink
🚚 [#4608] Split 'default' prefill fields to separate files
Browse files Browse the repository at this point in the history
Once we start re-using bits and pieces and have plugin-specific code/
configuration, it's better to stick to the one-component-per-file
approach since (some of) the components are now re-usable and semi-
public API.
  • Loading branch information
sergei-maertens committed Oct 8, 2024
1 parent 1f3b42e commit 5fc46a3
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 123 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {useField, useFormikContext} from 'formik';
import PropTypes from 'prop-types';

import Select from 'components/admin/forms/Select';

const AttributeField = ({prefillAttributes}) => {
const [fieldProps] = useField('attribute');
const {
values: {plugin},
} = useFormikContext();

return (
<Select
allowBlank
choices={prefillAttributes}
id="id_attribute"
disabled={!plugin}
{...fieldProps}
/>
);
};

AttributeField.propTypes = {
prefillAttributes: PropTypes.arrayOf(PropTypes.string).isRequired,
};

export default AttributeField;
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import {useFormikContext} from 'formik';
import PropTypes from 'prop-types';
import {FormattedMessage} from 'react-intl';
import useAsync from 'react-use/esm/useAsync';

import Field from 'components/admin/forms/Field';
import Fieldset from 'components/admin/forms/Fieldset';
import FormRow from 'components/admin/forms/FormRow';
import {LOADING_OPTION} from 'components/admin/forms/Select';
import {get} from 'utils/fetch';

import AttributeField from './AttributeField';
import IdentifierRoleField from './IdentifierRoleField';
import PluginField from './PluginField';

// Load the possible prefill attributes
// XXX: this would benefit from client-side caching
const getAttributes = async plugin => {
if (!plugin) return [];

const endpoint = `/api/v2/prefill/plugins/${plugin}/attributes`;
// XXX: clean up error handling here at some point...
const response = await get(endpoint);
if (!response.ok) throw response.data;
return response.data.map(attribute => [attribute.id, attribute.label]);
};

/**
* Default (legacy) prefill configuration - after selecting the plugin, the user
* selects which attribute to use to grab the prefill value from.
*/
const DefaultFields = ({errors}) => {
const {
values: {plugin = ''},
} = useFormikContext();
const {
loading,
value = [],
error,
} = useAsync(async () => {
try {
return await getAttributes(plugin);
} catch (e) {
throw e;
}
}, [plugin]);

// throw errors to the nearest error boundary
if (error) throw error;
const prefillAttributes = loading ? LOADING_OPTION : value;

return (
<Fieldset>
<FormRow>
<Field
name="plugin"
label={
<FormattedMessage description="Variable prefill plugin label" defaultMessage="Plugin" />
}
errors={errors.plugin}
>
<PluginField />
</Field>
</FormRow>

<FormRow>
<Field
name="attribute"
label={
<FormattedMessage
description="Variable prefill attribute label"
defaultMessage="Attribute"
/>
}
errors={errors.attribute}
>
<AttributeField prefillAttributes={prefillAttributes} />
</Field>
</FormRow>

<FormRow>
<Field
name="identifierRole"
label={
<FormattedMessage
description="Variable prefill identifier role label"
defaultMessage="Identifier role"
/>
}
errors={errors.identifierRole}
>
<IdentifierRoleField />
</Field>
</FormRow>
</Fieldset>
);
};

const ErrorsType = PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.array])),
PropTypes.string,
]);

DefaultFields.propTypes = {
errors: PropTypes.shape({
plugin: ErrorsType,
attribute: ErrorsType,
identifierRole: ErrorsType,
}).isRequired,
};

export default DefaultFields;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {useField} from 'formik';

import Select from 'components/admin/forms/Select';

import {IDENTIFIER_ROLE_CHOICES} from '../constants';

const IdentifierRoleField = () => {
const [fieldProps] = useField('identifierRole');
const choices = Object.entries(IDENTIFIER_ROLE_CHOICES);
return (
<Select
choices={choices}
id="id_identifierRole"
translateChoices
capfirstChoices
{...fieldProps}
/>
);
};

export default IdentifierRoleField;
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {useField, useFormikContext} from 'formik';
import {useContext} from 'react';
import useUpdateEffect from 'react-use/esm/useUpdateEffect';

import {FormContext} from 'components/admin/form_design/Context';
import Select from 'components/admin/forms/Select';

const PluginField = () => {
const [fieldProps] = useField('plugin');
const {setFieldValue} = useFormikContext();
const {
plugins: {availablePrefillPlugins},
} = useContext(FormContext);

const {value} = fieldProps;

// reset the attribute value whenever the plugin changes
useUpdateEffect(() => {
setFieldValue('attribute', '');
}, [setFieldValue, value]);

const choices = availablePrefillPlugins.map(plugin => [plugin.id, plugin.label]);
return <Select allowBlank choices={choices} id="id_plugin" {...fieldProps} />;
};

export default PluginField;
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import {Formik, useField, useFormikContext} from 'formik';
import {Formik, useFormikContext} from 'formik';
import PropTypes from 'prop-types';
import React, {useContext} from 'react';
import {FormattedMessage, useIntl} from 'react-intl';
import useAsync from 'react-use/esm/useAsync';
import useUpdateEffect from 'react-use/esm/useUpdateEffect';

import {FormContext} from 'components/admin/form_design/Context';
import {SubmitAction} from 'components/admin/forms/ActionButton';
import Field from 'components/admin/forms/Field';
import Fieldset from 'components/admin/forms/Fieldset';
import FormRow from 'components/admin/forms/FormRow';
import Select, {LOADING_OPTION} from 'components/admin/forms/Select';
import {LOADING_OPTION} from 'components/admin/forms/Select';
import SubmitRow from 'components/admin/forms/SubmitRow';
import VariableMapping from 'components/admin/forms/VariableMapping';
import {
Expand All @@ -22,7 +21,8 @@ import {FAIcon} from 'components/admin/icons';
import ErrorBoundary from 'components/errors/ErrorBoundary';
import {get} from 'utils/fetch';

import {IDENTIFIER_ROLE_CHOICES} from '../constants';
import DefaultFields from './DefaultFields';
import PluginField from './PluginField';

const PrefillConfigurationForm = ({
onSubmit,
Expand Down Expand Up @@ -87,124 +87,6 @@ PrefillConfigurationForm.propTypes = {
}).isRequired,
};

const PluginField = () => {
const [fieldProps] = useField('plugin');
const {setFieldValue} = useFormikContext();
const {
plugins: {availablePrefillPlugins},
} = useContext(FormContext);

const {value} = fieldProps;

// reset the attribute value whenever the plugin changes
useUpdateEffect(() => {
setFieldValue('attribute', '');
}, [setFieldValue, value]);

const choices = availablePrefillPlugins.map(plugin => [plugin.id, plugin.label]);
return <Select allowBlank choices={choices} id="id_plugin" {...fieldProps} />;
};

const AttributeField = ({prefillAttributes}) => {
const [fieldProps] = useField('attribute');
const {
values: {plugin},
} = useFormikContext();

return (
<Select
allowBlank
choices={prefillAttributes}
id="id_attribute"
disabled={!plugin}
{...fieldProps}
/>
);
};

const IdentifierRoleField = () => {
const [fieldProps] = useField('identifierRole');
const choices = Object.entries(IDENTIFIER_ROLE_CHOICES);
return (
<Select
choices={choices}
id="id_identifierRole"
translateChoices
capfirstChoices
{...fieldProps}
/>
);
};

const PrefillFields = ({values, errors}) => {
// Load the possible prefill attributes
// XXX: this would benefit from client-side caching
const plugin = values.plugin;
const {
loading,
value = [],
error,
} = useAsync(async () => {
if (!plugin) return [];

const endpoint = `/api/v2/prefill/plugins/${plugin}/attributes`;
// XXX: clean up error handling here at some point...
const response = await get(endpoint);
if (!response.ok) throw response.data;
return response.data.map(attribute => [attribute.id, attribute.label]);
}, [plugin]);

// throw errors to the nearest error boundary
if (error) throw error;
const prefillAttributes = loading ? LOADING_OPTION : value;

return (
<Fieldset>
<FormRow>
<Field
name="plugin"
label={
<FormattedMessage description="Variable prefill plugin label" defaultMessage="Plugin" />
}
errors={errors.plugin}
>
<PluginField />
</Field>
</FormRow>

<FormRow>
<Field
name="attribute"
label={
<FormattedMessage
description="Variable prefill attribute label"
defaultMessage="Attribute"
/>
}
errors={errors.attribute}
>
<AttributeField prefillAttributes={prefillAttributes} />
</Field>
</FormRow>

<FormRow>
<Field
name="identifierRole"
label={
<FormattedMessage
description="Variable prefill identifier role label"
defaultMessage="Identifier role"
/>
}
errors={errors.identifierRole}
>
<IdentifierRoleField />
</Field>
</FormRow>
</Fieldset>
);
};

/**
* Callback to invoke when the API group changes - used to reset the dependent fields.
*/
Expand Down Expand Up @@ -372,7 +254,7 @@ const ObjectsAPIPrefillFields = ({values, errors}) => {

const PLUGIN_COMPONENT_MAPPING = {
objects_api: ObjectsAPIPrefillFields,
default: PrefillFields,
default: DefaultFields,
};

export default PrefillConfigurationForm;

0 comments on commit 5fc46a3

Please sign in to comment.