Skip to content

Commit 8a47c04

Browse files
vaszigsergei-maertens
authored andcommitted
[#5074] Started with transforming the data for the Objects API in the variables tab
1 parent 4cb4b65 commit 8a47c04

File tree

9 files changed

+112
-44
lines changed

9 files changed

+112
-44
lines changed

src/openforms/api/utils.py

+7
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ def underscore_to_camel(input_: str | int) -> str:
2323
return re.sub(camelize_re, _underscore_to_camel, input_)
2424

2525

26+
def camel_to_underscore(input: str) -> str:
27+
"""
28+
Convert a string from camelCase to snake_case
29+
"""
30+
return re.sub(r"(?<!^)(?=[A-Z])", "_", input).lower()
31+
32+
2633
def get_from_serializer_data_or_instance(
2734
field: str, data: Mapping, serializer: Serializer
2835
) -> Any:

src/openforms/js/components/admin/form_design/registrations/objectsapi/AddressNlObjectsApiVariableConfigurationEditor.js

+1-23
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@ import {FormattedMessage} from 'react-intl';
55
import {useAsync, useToggle} from 'react-use';
66

77
import {APIContext} from 'components/admin/form_design/Context';
8-
import {REGISTRATION_OBJECTS_TARGET_PATHS} from 'components/admin/form_design/constants';
98
import Field from 'components/admin/forms/Field';
109
import Fieldset from 'components/admin/forms/Fieldset';
1110
import FormRow from 'components/admin/forms/FormRow';
1211
import {Checkbox} from 'components/admin/forms/Inputs';
1312
import {TargetPathSelect} from 'components/admin/forms/objects_api';
1413
import ErrorMessage from 'components/errors/ErrorMessage';
15-
import {post} from 'utils/fetch';
1614

1715
import {MappedVariableTargetPathSelect} from './GenericObjectsApiVariableConfigurationEditor';
16+
import {fetchTargetPaths} from './utils';
1817

1918
const ADDRESSNL_NESTED_PROPERTIES = {
2019
postcode: {type: 'string'},
@@ -25,27 +24,6 @@ const ADDRESSNL_NESTED_PROPERTIES = {
2524
streetName: {type: 'string'},
2625
};
2726

28-
const fetchTargetPaths = async (
29-
csrftoken,
30-
objectsApiGroup,
31-
objecttype,
32-
objecttypeVersion,
33-
schemaType
34-
) => {
35-
const response = await post(REGISTRATION_OBJECTS_TARGET_PATHS, csrftoken, {
36-
objectsApiGroup,
37-
objecttype,
38-
objecttypeVersion,
39-
variableJsonSchema: schemaType,
40-
});
41-
42-
if (!response.ok) {
43-
throw new Error(`Error when loading target paths for type: ${schemaType}`);
44-
}
45-
46-
return response.data;
47-
};
48-
4927
export const AddressNlEditor = ({
5028
variable,
5129
components,

src/openforms/js/components/admin/form_design/registrations/objectsapi/GenericObjectsApiVariableConfigurationEditor.js

+47-19
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@ import {FormattedMessage} from 'react-intl';
66
import {useAsync, useToggle} from 'react-use';
77

88
import {APIContext} from 'components/admin/form_design/Context';
9-
import {REGISTRATION_OBJECTS_TARGET_PATHS} from 'components/admin/form_design/constants';
109
import Field from 'components/admin/forms/Field';
1110
import FormRow from 'components/admin/forms/FormRow';
1211
import {Checkbox} from 'components/admin/forms/Inputs';
1312
import {TargetPathSelect} from 'components/admin/forms/objects_api';
1413
import ErrorMessage from 'components/errors/ErrorMessage';
15-
import {post} from 'utils/fetch';
1614

1715
import {asJsonSchema} from './utils';
16+
import {CUSTOM_COMPONENTS_SCHEMA, fetchTargetPaths} from './utils';
1817

1918
/**
2019
* Hack-ish way to manage the variablesMapping state for one particular entry.
@@ -83,6 +82,7 @@ export const GenericEditor = ({
8382
components,
8483
namePrefix,
8584
isGeometry,
85+
transformToList,
8686
index,
8787
mappedVariable,
8888
objecttype,
@@ -91,29 +91,34 @@ export const GenericEditor = ({
9191
}) => {
9292
const {csrftoken} = useContext(APIContext);
9393
const [jsonSchemaVisible, toggleJsonSchemaVisible] = useToggle(false);
94-
const {setFieldValue} = useFormikContext();
94+
const {values, setFieldValue} = useFormikContext();
9595

96+
const componentType = components[variable?.key]?.type;
97+
98+
// Load all the possible target paths in parallel depending on if the data should be
99+
// transformed or not
96100
const {
97101
loading,
98102
value: targetPaths,
99103
error,
100-
} = useAsync(
101-
async () => {
102-
const response = await post(REGISTRATION_OBJECTS_TARGET_PATHS, csrftoken, {
103-
objectsApiGroup,
104-
objecttype,
105-
objecttypeVersion,
106-
variableJsonSchema: asJsonSchema(variable, components),
107-
});
108-
if (!response.ok) {
109-
throw new Error('Error when loading target paths');
110-
}
104+
} = useAsync(async () => {
105+
const promises = transformToList?.[variable.key]
106+
? CUSTOM_COMPONENTS_SCHEMA[componentType].map(type =>
107+
fetchTargetPaths(csrftoken, objectsApiGroup, objecttype, objecttypeVersion, type)
108+
)
109+
: [
110+
fetchTargetPaths(
111+
csrftoken,
112+
objectsApiGroup,
113+
objecttype,
114+
objecttypeVersion,
115+
asJsonSchema(variable, components)
116+
),
117+
];
111118

112-
return response.data;
113-
},
114-
// Load only once:
115-
[]
116-
);
119+
const results = await Promise.all(promises);
120+
return results.flat(1);
121+
}, [transformToList?.[variable.key]]);
117122

118123
const getTargetPath = pathSegment => targetPaths.find(t => isEqual(t.targetPath, pathSegment));
119124

@@ -153,6 +158,29 @@ export const GenericEditor = ({
153158
/>
154159
</Field>
155160
</FormRow>
161+
{componentType in CUSTOM_COMPONENTS_SCHEMA && (
162+
<FormRow>
163+
<Field name={`transformToList.${variable.key}`}>
164+
<Checkbox
165+
name="transformToListCheckbox"
166+
label={
167+
<FormattedMessage
168+
defaultMessage="Transform to list"
169+
description="'Transform to list' checkbox label"
170+
/>
171+
}
172+
helpText={
173+
<FormattedMessage
174+
description="'Transform to list' checkbox help text"
175+
defaultMessage="Whether to transform the data of the component to a list or not (depends on the component type)"
176+
/>
177+
}
178+
checked={values.transformToList?.[variable.key] || false}
179+
onChange={e => setFieldValue(`transformToList.${variable.key}`, e.target.checked)}
180+
/>
181+
</Field>
182+
</FormRow>
183+
)}
156184
<FormRow>
157185
<Field
158186
name={`${namePrefix}.targetPath`}

src/openforms/js/components/admin/form_design/registrations/objectsapi/ObjectsApiVariableConfigurationEditor.js

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const ObjectsApiVariableConfigurationEditor = ({variable}) => {
4545
objecttype,
4646
objecttypeVersion,
4747
geometryVariableKey,
48+
transformToList,
4849
variablesMapping,
4950
version,
5051
} = backendOptions;
@@ -99,6 +100,7 @@ const ObjectsApiVariableConfigurationEditor = ({variable}) => {
99100
components={components}
100101
namePrefix={namePrefix}
101102
isGeometry={isGeometry}
103+
transformToList={transformToList}
102104
index={index}
103105
mappedVariable={mappedVariable}
104106
objecttype={objecttype}

src/openforms/js/components/admin/form_design/registrations/objectsapi/utils.js

+27-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import {REGISTRATION_OBJECTS_TARGET_PATHS} from 'components/admin/form_design/constants';
2+
import {post} from 'utils/fetch';
3+
14
const VARIABLE_TYPE_MAP = {
25
boolean: 'boolean',
36
int: 'integer',
@@ -13,6 +16,8 @@ const FORMAT_TYPE_MAP = {
1316
date: 'date',
1417
};
1518

19+
const CUSTOM_COMPONENTS_SCHEMA = {selectboxes: [{type: 'array'}]};
20+
1621
/**
1722
* Return a JSON Schema definition matching the provided variable.
1823
* @param {Object} variable - The current variable
@@ -57,4 +62,25 @@ const asJsonSchema = (variable, components) => {
5762
};
5863
};
5964

60-
export {asJsonSchema};
65+
const fetchTargetPaths = async (
66+
csrftoken,
67+
objectsApiGroup,
68+
objecttype,
69+
objecttypeVersion,
70+
schemaType
71+
) => {
72+
const response = await post(REGISTRATION_OBJECTS_TARGET_PATHS, csrftoken, {
73+
objectsApiGroup,
74+
objecttype,
75+
objecttypeVersion,
76+
variableJsonSchema: schemaType,
77+
});
78+
79+
if (!response.ok) {
80+
throw new Error(`Error when loading target paths for type: ${schemaType}`);
81+
}
82+
83+
return response.data;
84+
};
85+
86+
export {asJsonSchema, fetchTargetPaths, CUSTOM_COMPONENTS_SCHEMA};

src/openforms/registrations/contrib/objects_api/config.py

+9
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,15 @@ class ObjectsAPIOptionsSerializer(JsonSchemaSerializerMixin, serializers.Seriali
256256
required=False,
257257
allow_blank=True,
258258
)
259+
transform_to_list = JSONFieldWithSchema(
260+
label=_("transform to list"),
261+
default=dict,
262+
required=False,
263+
help_text=_(
264+
"The components which need special handling concerning the shape of the data "
265+
"and need to be transformed to list."
266+
),
267+
)
259268

260269
def validate(self, attrs: RegistrationOptions) -> RegistrationOptions:
261270
v1_only_fields = {

src/openforms/registrations/contrib/objects_api/handlers/v2.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from glom import Assign, Path, glom
1111

12-
from openforms.api.utils import underscore_to_camel
12+
from openforms.api.utils import camel_to_underscore, underscore_to_camel
1313
from openforms.formio.service import FormioData
1414
from openforms.formio.typing import Component, EditGridComponent
1515
from openforms.formio.typing.vanilla import FileComponent
@@ -58,6 +58,7 @@ def process_mapped_variable(
5858
value: (
5959
JSONValue | date | datetime
6060
), # can't narrow it down yet, as the type depends on the component type
61+
transform_to_list: JSONObject | None = None,
6162
component: Component | None = None,
6263
attachment_urls: dict[str, list[str]] | None = None,
6364
) -> AssignmentSpec | Sequence[AssignmentSpec]:
@@ -73,6 +74,8 @@ def process_mapped_variable(
7374
:arg value: The raw value of the form variable for the submission being processed.
7475
The type/shape of the value depends on the variable/component data type being
7576
processed and even the component configuration (such as multiple True/False).
77+
:arg transform_to_list: The component's transforma data options. Some of the components
78+
need special handling concerning the shape of the data.
7679
:arg component: If the variable corresponds to a Formio component, the component
7780
definition is provided, otherwise ``None``.
7881
:arg attachment_urls: The registration plugin uploads attachments to a Documents API
@@ -139,6 +142,16 @@ def process_mapped_variable(
139142
attachment_urls=attachment_urls,
140143
key_prefix=variable_key,
141144
)
145+
case {"type": "selectboxes"}:
146+
assert isinstance(value, dict)
147+
if (
148+
transform_to_list
149+
and (key := camel_to_underscore(variable_key)) in transform_to_list
150+
and transform_to_list[key]
151+
):
152+
153+
value = [option for option, is_selected in value.items() if is_selected]
154+
142155
# not a component or standard behaviour where no transformation is necessary
143156
case None | _:
144157
pass

src/openforms/registrations/contrib/objects_api/submission_registration.py

+2
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,7 @@ def get_record_data(
521521
urls_map = self.get_attachment_urls_by_key(submission)
522522

523523
variables_mapping = options["variables_mapping"]
524+
transform_to_list = options.get("transform_to_list")
524525

525526
# collect all the assignments to be done to the object
526527
assignment_specs: list[AssignmentSpec] = []
@@ -571,6 +572,7 @@ def get_record_data(
571572
value=value,
572573
component=component,
573574
attachment_urls=urls_map,
575+
transform_to_list=transform_to_list,
574576
)
575577
if isinstance(assignment_spec, AssignmentSpec):
576578
assignment_specs.append(assignment_spec)

src/openforms/registrations/contrib/objects_api/typing.py

+3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from typing import TYPE_CHECKING, Literal, NotRequired, Required, TypedDict
44
from uuid import UUID
55

6+
from openforms.typing import JSONObject
7+
68
if TYPE_CHECKING:
79
from .models import ObjectsAPIGroupConfig
810

@@ -70,6 +72,7 @@ class RegistrationOptionsV2(_BaseRegistrationOptions, total=False):
7072
version: Required[Literal[2]]
7173
variables_mapping: Required[list[ObjecttypeVariableMapping]]
7274
geometry_variable_key: str
75+
transform_to_list: NotRequired[JSONObject]
7376

7477

7578
type RegistrationOptions = RegistrationOptionsV1 | RegistrationOptionsV2

0 commit comments

Comments
 (0)