Skip to content

Commit

Permalink
✨ [#5012] Add metadata to JSON dump configuration options
Browse files Browse the repository at this point in the history
Also add support for an extra static variables registry in generate_json_schema
  • Loading branch information
viktorvanwijk committed Feb 5, 2025
1 parent 67a1ce0 commit d7ea400
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 44 deletions.
22 changes: 19 additions & 3 deletions src/openforms/forms/json_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,50 @@
from openforms.typing import JSONObject
from openforms.variables.service import get_static_variables

from ..plugins.registry import BaseRegistry
from ..variables.base import BaseStaticVariable
from .models import Form, FormVariable


def _iter_form_variables(form: Form) -> Iterator[FormVariable]:
def _iter_form_variables(
form: Form,
additional_variables_registry: BaseRegistry[BaseStaticVariable] | None = None,
) -> Iterator[FormVariable]:
"""Iterate over static variables and all form variables.
:param form: Form
:param additional_variables_registry: Optional registry of static variables.
"""
# Static variables are always available
yield from get_static_variables()
# If the optional variables registry is passed
if additional_variables_registry is not None:
yield from get_static_variables(
variables_registry=additional_variables_registry
)
# Handle from variables holding dynamic data (component and user defined)
yield from form.formvariable_set.all()


def generate_json_schema(form: Form, limit_to_variables: Sequence[str]) -> JSONObject:
def generate_json_schema(
form: Form,
limit_to_variables: Sequence[str],
additional_variables_registry: BaseRegistry[BaseStaticVariable] | None = None,
) -> JSONObject:
"""Generate a JSON schema from a form, for the specified variables.
Note: this schema is an informative description of the variables and should not be
used as validation.
:param form: The form to generate JSON schema for.
:param limit_to_variables: Variables that will be included in the schema.
:param additional_variables_registry: Optional extra registry of static variables.
:returns: A JSON schema representing the form variables.
"""
requested_variables_schema = {
key: variable.as_json_schema()
for variable in _iter_form_variables(form)
for variable in _iter_form_variables(form, additional_variables_registry)
if (key := variable.key) in limit_to_variables
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from 'components/admin/forms/ValidationErrors';
import {getChoicesFromSchema} from 'utils/json-schema';

import {Path, ServiceSelect, Variables} from './fields';
import {MetadataVariables, Path, ServiceSelect, Variables} from './fields';

const JSONDumpOptionsForm = ({name, label, schema, formData, onChange}) => {
const validationErrors = useContext(ValidationErrorContext);
Expand All @@ -38,6 +38,8 @@ const JSONDumpOptionsForm = ({name, label, schema, formData, onChange}) => {
service: null,
path: '',
variables: [],
fixedMetadataVariables: [],
additionalMetadataVariables: [],
...formData,
}}
onSubmit={values => onChange({formData: values})}
Expand All @@ -48,6 +50,7 @@ const JSONDumpOptionsForm = ({name, label, schema, formData, onChange}) => {
<ServiceSelect options={serviceOptions} />
<Path />
<Variables />
<MetadataVariables />
</Fieldset>
</ValidationErrorsProvider>
</ModalOptionsConfiguration>
Expand All @@ -69,6 +72,8 @@ JSONDumpOptionsForm.propTypes = {
service: PropTypes.number,
path: PropTypes.string,
variables: PropTypes.arrayOf(PropTypes.string),
fixedMetadataVariables: PropTypes.arrayOf(PropTypes.string),
additionalMetadataVariables: PropTypes.arrayOf(PropTypes.string),
}),
onChange: PropTypes.func.isRequired,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
import PropTypes from 'prop-types';
import React from 'react';
import {FormattedMessage} from 'react-intl';

import {IconNo, IconYes} from 'components/admin/BooleanIcons';

const JSONDumpSummaryHandler = ({variable, backendOptions}) => {
const isIncluded = backendOptions.variables.includes(variable.key);
const isIncludedInVariables = backendOptions.variables.includes(variable.key);
const isIncludedInMetadata =
backendOptions.fixedMetadataVariables.includes(variable.key) ||
backendOptions.additionalMetadataVariables.includes(variable.key);

return isIncluded ? <IconYes /> : <IconNo />;
return (
<>
<FormattedMessage
description="Label indicating the 'values' part of the data"
defaultMessage="Values: "
/>
{isIncludedInVariables ? <IconYes /> : <IconNo />}
&nbsp;
<FormattedMessage
description="Label indicating the 'metadata' part of the data"
defaultMessage="Metadata:"
/>
{isIncludedInMetadata ? <IconYes /> : <IconNo />}
</>
);
};

JSONDumpSummaryHandler.propTypes = {
Expand All @@ -15,6 +33,8 @@ JSONDumpSummaryHandler.propTypes = {
}).isRequired,
backendOptions: PropTypes.shape({
variables: PropTypes.arrayOf(PropTypes.string).isRequired,
fixedMetadataVariables: PropTypes.arrayOf(PropTypes.string).isRequired,
additionalMetadataVariables: PropTypes.arrayOf(PropTypes.string).isRequired,
}).isRequired,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,82 @@ import PropTypes from 'prop-types';
import React from 'react';
import {FormattedMessage} from 'react-intl';

import {VARIABLE_SOURCES} from 'components/admin/form_design/variables/constants';
import {getVariableSource} from 'components/admin/form_design/variables/utils';
import Field from 'components/admin/forms/Field';
import FormRow from 'components/admin/forms/FormRow';
import {Checkbox} from 'components/admin/forms/Inputs';

const JSONDumpVariableConfigurationEditor = ({variable}) => {
const {
values: {variables = []},
values: {variables = [], additionalMetadataVariables = [], fixedMetadataVariables = []},
setFieldValue,
} = useFormikContext();
const isIncluded = variables.includes(variable.key);
const isRequiredInMetadata = fixedMetadataVariables.includes(variable.key);
const isInAdditionalMetadata = additionalMetadataVariables.includes(variable.key);

return (
<FormRow>
<Field name="includeVariable">
<Checkbox
name="includeVariableCheckbox"
label={
<FormattedMessage
description="'Include variable' checkbox label"
defaultMessage="Include variable"
/>
}
helpText={
<FormattedMessage
description="'Include variable' checkbox help text"
defaultMessage="Whether to include this variable in the data to be sent."
/>
}
checked={isIncluded}
onChange={event => {
const shouldBeIncluded = event.target.checked;
const newVariables = shouldBeIncluded
? [...variables, variable.key] // add the variable to the array
: variables.filter(key => key !== variable.key); // remove the variable from the array
setFieldValue('variables', newVariables);
}}
/>
</Field>
</FormRow>
<>
<FormRow>
<Field name="includeVariable">
<Checkbox
name="includeVariableCheckbox"
label={
<FormattedMessage
description="'Include variable' checkbox label"
defaultMessage="Include variable"
/>
}
helpText={
<FormattedMessage
description="'Include variable' checkbox help text"
defaultMessage="Whether to include this variable in the data to be sent."
/>
}
checked={isIncluded}
onChange={event => {
const shouldBeIncluded = event.target.checked;
const newVariables = shouldBeIncluded
? [...variables, variable.key] // add the variable to the array
: variables.filter(key => key !== variable.key); // remove the variable from the array
setFieldValue('variables', newVariables);
}}
/>
</Field>
</FormRow>

<FormRow>
<Field name="includeVariableInMetadata">
<Checkbox
name="includeVariableInMetadataCheckbox"
label={
<FormattedMessage
description="'Include variable in metadata' checkbox label"
defaultMessage="Include variable in metadata"
/>
}
helpText={
<FormattedMessage
description="'Include variable in metadata' checkbox help text"
defaultMessage="Whether to include this variable in the metadata to be sent."
/>
}
checked={isInAdditionalMetadata || isRequiredInMetadata}
onChange={event => {
const shouldBeIncluded = event.target.checked;
const newVariables = shouldBeIncluded
? [...additionalMetadataVariables, variable.key] // add the variable to the array
: additionalMetadataVariables.filter(key => key !== variable.key); // remove the variable from the array
setFieldValue('additionalMetadataVariables', newVariables);
}}
disabled={
isRequiredInMetadata || getVariableSource(variable) !== VARIABLE_SOURCES.static
} // disable if it is required or the variable is not a static variable
/>
</Field>
</FormRow>
</>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {useField} from 'formik';
import React from 'react';
import {FormattedMessage} from 'react-intl';

import {VARIABLE_SOURCES} from 'components/admin/form_design/variables/constants';
import {getVariableSource} from 'components/admin/form_design/variables/utils';
import Field from 'components/admin/forms/Field';
import FormRow from 'components/admin/forms/FormRow';
import VariableSelection from 'components/admin/forms/VariableSelection';

const MetadataVariables = () => {
const [fieldProps] = useField('additionalMetadataVariables');
const [, {value}] = useField('fixedMetadataVariables');

return (
<FormRow>
<Field
name="additionalMetadataVariables"
label={
<FormattedMessage
description="JSON registration options 'additionalMetadataVariables' label"
defaultMessage="Additional metadata variables"
/>
}
helpText={
<FormattedMessage
description="JSON registration options 'additionalMetadataVariables' helpText"
defaultMessage="Which additional variables to include in the metadata (the following are already included by default: {alreadyIncluded})"
values={{alreadyIncluded: value.join(', ')}}
/>
}
noManageChildProps
>
<VariableSelection
{...fieldProps}
isMulti
closeMenuOnSelect={false}
includeStaticVariables
filter={variable =>
getVariableSource(variable) === VARIABLE_SOURCES.static && !value.includes(variable.key)
} // Only show static variables and variables which are not already required
/>
</Field>
</FormRow>
);
};

export default MetadataVariables;
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import MetadataVariables from './MetadataVariables';
import Path from './Path';
import ServiceSelect from './ServiceSelect';
import Variables from './Variables';

export {Path, ServiceSelect, Variables};
export {MetadataVariables, Path, ServiceSelect, Variables};
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ export {
getFormVariables,
getComponentDatatype,
checkForDuplicateKeys,
getVariableSource,
getVariableSourceLabel,
groupVariablesBySource,
variableHasErrors,
Expand Down
26 changes: 26 additions & 0 deletions src/openforms/registrations/contrib/json_dump/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,30 @@ class JSONDumpOptionsSerializer(JsonSchemaSerializerMixin, serializers.Serialize
required=True,
min_length=1,
)
fixed_metadata_variables = serializers.ListField(
child=FormioVariableKeyField(),
default=[
"public_reference",
"form_name",
"form_version",
"form_id",
"registration_timestamp",
"auth_type",
],
label=_("Fixed metadata variable key list"),
help_text=_(
"A list of required variables to use in the metadata. These include "
"the registration variables of the JSON dump plugin"
),
required=False,
)
additional_metadata_variables = serializers.ListField(
child=FormioVariableKeyField(),
label=_("Additional metadata variable key list"),
help_text=_("A list of additional variables to use in the metadata"),
required=False,
default=list,
)


class JSONDumpOptions(TypedDict):
Expand All @@ -60,3 +84,5 @@ class JSONDumpOptions(TypedDict):
service: Service
path: str
variables: list[str]
fixed_metadata_variables: list[str]
additional_metadata_variables: list[str]
Loading

0 comments on commit d7ea400

Please sign in to comment.