Skip to content

Commit

Permalink
♻️ [#4606] Refactor ZGW options components structure
Browse files Browse the repository at this point in the history
Refactored the components and hooks so we can lift up the retrieval of
available catalogues in a ZGW API group.

The parent component is now responsible for fetching the available
catalogues and relays the loading/error state from useAsync. It also
encapsulated the derived catalogueUrl when a valid value is available
and selected, which other components need to look up related objects
(like case types, document types, products and the future role types).

I've opted to pass down the error into a component that just throws
for the existing error boundary behaviour and location, as it's
important that you can still change the API group to trigger a new
attempt and we want to keep the code nicely organised.

Finally, some more fieldsets were abstracted into their own components
for readability reasons.
  • Loading branch information
sergei-maertens committed Nov 28, 2024
1 parent f0aa235 commit 1540ab4
Show file tree
Hide file tree
Showing 15 changed files with 358 additions and 247 deletions.
12 changes: 6 additions & 6 deletions src/openforms/js/compiled-lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2517,6 +2517,12 @@
"value": "Add variable"
}
],
"LbiXCA": [
{
"type": 0,
"value": "Something went wrong while retrieving the available products defined in the selected case. Please check that the services in the selected API group are configured correctly."
}
],
"LeVpdf": [
{
"type": 0,
Expand Down Expand Up @@ -5337,12 +5343,6 @@
"value": "Add item"
}
],
"ln2iyc": [
{
"type": 0,
"value": "Something went wrong while retrieving the available catalogues or products defined in the selected case. Please check that the services in the selected API group are configured correctly."
}
],
"ln72CJ": [
{
"type": 0,
Expand Down
18 changes: 6 additions & 12 deletions src/openforms/js/compiled-lang/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -2538,6 +2538,12 @@
"value": "Variabele toevoegen"
}
],
"LbiXCA": [
{
"type": 0,
"value": "Er ging iets fout bij het ophalen van de beschikbare producten binnen het geselecteerde zaaktype. Controleer of de services in de geselecteerde API-groep goed ingesteld zijn."
}
],
"LeVpdf": [
{
"type": 0,
Expand Down Expand Up @@ -5109,12 +5115,6 @@
"value": "optie 2: € 15,99"
}
],
"jtWzSW": [
{
"type": 0,
"value": "optie 2: € 15,99"
}
],
"jy1jTd": [
{
"offset": 0,
Expand Down Expand Up @@ -5365,12 +5365,6 @@
"value": "Item toevoegen"
}
],
"ln2iyc": [
{
"type": 0,
"value": "Er ging iets fout bij het ophalen van de beschikbare catalogi of geselecteerde zaak producten. Controleer of de services in de geselecteerde API-groep goed ingesteld zijn."
}
],
"ln72CJ": [
{
"type": 0,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import {useFormikContext} from 'formik';
import PropTypes from 'prop-types';
import {FormattedMessage, useIntl} from 'react-intl';
import {useAsync, usePrevious, useUpdateEffect} from 'react-use';
import {FormattedMessage} from 'react-intl';

import useConfirm from 'components/admin/form_design/useConfirm';
import Fieldset from 'components/admin/forms/Fieldset';
import {getCatalogueOption} from 'components/admin/forms/zgw';
import {CatalogueSelectOptions} from 'components/admin/forms/zgw';
import ErrorBoundary from 'components/errors/ErrorBoundary';

import {CaseTypeSelect, CatalogueSelect, DocumentTypeSelect, ZGWAPIGroup} from './fields';
import {getCatalogues} from './utils';

// Components

const BasicOptionsFieldset = ({apiGroupChoices}) => {
const BasicOptionsFieldset = ({
apiGroupChoices,
loadingCatalogues,
catalogueOptionGroups = [],
cataloguesError = undefined,
catalogueUrl = '',
}) => {
const {
values: {
caseTypeIdentification,
Expand Down Expand Up @@ -61,7 +65,10 @@ const BasicOptionsFieldset = ({apiGroupChoices}) => {
/>
}
>
<CatalogiApiFields />
<MaybeThrowError error={cataloguesError} />
<CatalogueSelect loading={loadingCatalogues} optionGroups={catalogueOptionGroups} />
<CaseTypeSelect catalogueUrl={catalogueUrl} />
<DocumentTypeSelect catalogueUrl={catalogueUrl} />
</ErrorBoundary>
<ConfirmationModal
{...confirmationModalProps}
Expand All @@ -86,74 +93,23 @@ BasicOptionsFieldset.propTypes = {
])
)
).isRequired,
loadingCatalogues: PropTypes.bool.isRequired,
catalogueOptionGroups: CatalogueSelectOptions.isRequired,
cataloguesError: PropTypes.any,
catalogueUrl: PropTypes.string,
};

const CatalogiApiFields = () => {
const {
values: {
zgwApiGroup = null,
catalogue = undefined,
caseTypeIdentification = '',
documentTypeDescription = '',
productUrl = '',
},
setFieldValue,
} = useFormikContext();

const previousCatalogue = usePrevious(catalogue);
const previousCaseTypeIdentification = usePrevious(caseTypeIdentification);

// fetch available catalogues and re-use the result
const {
loading: loadingCatalogues,
value: catalogueOptionGroups = [],
error: cataloguesError,
} = useAsync(async () => {
if (!zgwApiGroup) return [];
return await getCatalogues(zgwApiGroup);
}, [zgwApiGroup]);
if (cataloguesError) throw cataloguesError;

const catalogueValue = getCatalogueOption(catalogueOptionGroups, catalogue || {});
const catalogueUrl = catalogueValue?.url;

// Synchronize dependent fields when dependencies change.
// 1. Clear case type when catalogue changes.
useUpdateEffect(() => {
const catalogueChanged = catalogue !== previousCatalogue;
if (previousCatalogue && catalogueChanged && caseTypeIdentification) {
setFieldValue('caseTypeIdentification', '');
}
}, [setFieldValue, previousCatalogue, catalogue, caseTypeIdentification]);

// 2. Clear document type when case type changes
useUpdateEffect(() => {
const caseTypeChanged = caseTypeIdentification !== previousCaseTypeIdentification;
if (previousCaseTypeIdentification && caseTypeChanged && documentTypeDescription) {
setFieldValue('documentTypeDescription', '');
}
}, [
setFieldValue,
previousCaseTypeIdentification,
caseTypeIdentification,
documentTypeDescription,
]);

// 3. Clear selected product when case type changes
useUpdateEffect(() => {
const caseTypeChanged = caseTypeIdentification !== previousCaseTypeIdentification;
if (previousCaseTypeIdentification && caseTypeChanged && productUrl) {
setFieldValue('productUrl', '');
}
}, [setFieldValue, previousCaseTypeIdentification, caseTypeIdentification, productUrl]);
/**
* Component that only throws an error if it's not undefined, intended to trigger a
* parent error boundary.
*/
const MaybeThrowError = ({error = undefined}) => {
if (error) throw error;
return null;
};

return (
<>
<CatalogueSelect loading={loadingCatalogues} optionGroups={catalogueOptionGroups} />
<CaseTypeSelect catalogueUrl={catalogueUrl} />
<DocumentTypeSelect catalogueUrl={catalogueUrl} />
</>
);
MaybeThrowError.propTypes = {
error: PropTypes.any,
};

export default BasicOptionsFieldset;
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {FormattedMessage} from 'react-intl';

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

import {LegacyCaseType, LegacyDocumentType} from './fields';

/**
* @deprecated
*/
const LegacyOptionsFieldset = () => (
<Fieldset
title={
<FormattedMessage
description="ZGw APIs registration: legacy configuration options fieldset title"
defaultMessage="Legacy configuration"
/>
}
fieldNames={['zaaktype', 'informatieobjecttype']}
collapsible
>
<div className="description">
<FormattedMessage
description="ZGW APIs legacy config options informative message"
defaultMessage={`The configuration options here are legacy options. They
will continue working, but you should upgrade to the new configuration
options above. If a new configuration option is specified, the matching
legacy option will be ignored.`}
/>
</div>
<LegacyCaseType />
<LegacyDocumentType />
</Fieldset>
);

export default LegacyOptionsFieldset;
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {useFormikContext} from 'formik';
import PropTypes from 'prop-types';
import {FormattedMessage} from 'react-intl';

import {ContentJSON} from 'components/admin/form_design/registrations/objectsapi/LegacyConfigFields';
import Fieldset from 'components/admin/forms/Fieldset';
import {ObjectsAPIGroup} from 'components/admin/forms/objects_api';

import {ObjectType, ObjectTypeVersion} from './fields';

/**
* Callback to invoke when the API group changes - used to reset the dependent fields.
*/
const onApiGroupChange = prevValues => ({
...prevValues,
objecttype: '',
objecttypeVersion: undefined,
});

/**
* Configuration fields related to the Objects API (template based) integration.
*/
const ObjectsAPIOptionsFieldset = ({objectsApiGroupChoices}) => {
const {
values: {objecttype = ''},
} = useFormikContext();
return (
<Fieldset
title={
<FormattedMessage
description="ZGW APIs registration: Objects API fieldset title"
defaultMessage="Objects API integration"
/>
}
collapsible
fieldNames={['objecttype', 'objecttypeVersion', 'contentJson']}
>
<ObjectsAPIGroup
apiGroupChoices={objectsApiGroupChoices}
onApiGroupChange={onApiGroupChange}
required={!!objecttype}
isClearable
/>
<ObjectType />
<ObjectTypeVersion />
<ContentJSON />
</Fieldset>
);
};

ObjectsAPIOptionsFieldset.propTypes = {
objectsApiGroupChoices: PropTypes.arrayOf(
PropTypes.arrayOf(
PropTypes.oneOfType([
PropTypes.number, // value
PropTypes.string, // label
])
)
),
};

export default ObjectsAPIOptionsFieldset;
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import {useFormikContext} from 'formik';
import PropTypes from 'prop-types';
import {FormattedMessage} from 'react-intl';
import {useAsync} from 'react-use';

import Fieldset from 'components/admin/forms/Fieldset';
import {getCatalogueOption} from 'components/admin/forms/zgw';
import ErrorBoundary from 'components/errors/ErrorBoundary';

import {ConfidentialityLevel, MedewerkerRoltype, OrganisationRSIN, ProductSelect} from './fields';
import {getCatalogues} from './utils';

const OptionalOptionsFieldset = ({confidentialityLevelChoices}) => {
const OptionalOptionsFieldset = ({confidentialityLevelChoices, catalogueUrl}) => {
return (
<Fieldset
title={
Expand Down Expand Up @@ -42,38 +38,22 @@ const OptionalOptionsFieldset = ({confidentialityLevelChoices}) => {
<FormattedMessage
description="ZGW APIs registrations options: case product error"
defaultMessage={`Something went wrong while retrieving the available
catalogues or products defined in the selected case. Please
check that the services in the selected API group are configured correctly.`}
products defined in the selected case. Please check that the services in
the selected API group are configured correctly.`}
/>
}
>
<CatalogiApiField />
<ProductSelect catalogueUrl={catalogueUrl} />
</ErrorBoundary>
</Fieldset>
);
};

const CatalogiApiField = () => {
const {
values: {zgwApiGroup = null, catalogue = undefined},
} = useFormikContext();

// fetch available catalogues and re-use the result
const {value: catalogueOptionGroups = [], error: cataloguesError} = useAsync(async () => {
if (!zgwApiGroup) return [];
return await getCatalogues(zgwApiGroup);
}, [zgwApiGroup]);
if (cataloguesError) throw cataloguesError;

const catalogueValue = getCatalogueOption(catalogueOptionGroups, catalogue || {});
const catalogueUrl = catalogueValue?.url;
return <ProductSelect catalogueUrl={catalogueUrl} />;
};

OptionalOptionsFieldset.propTypes = {
confidentialityLevelChoices: PropTypes.arrayOf(
PropTypes.arrayOf(PropTypes.string) // value & label are both string
).isRequired,
catalogueUrl: PropTypes.string,
};

export default OptionalOptionsFieldset;
Loading

0 comments on commit 1540ab4

Please sign in to comment.