Skip to content

Commit

Permalink
✨[#3598] Add typecheck to "action sets the value"
Browse files Browse the repository at this point in the history
Closes #3598
  • Loading branch information
CharString committed Dec 6, 2023
1 parent 3986a89 commit 999ab14
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 64 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {createTypeCheck} from '@open-formulieren/formio-builder';
import get from 'lodash/get';
import PropTypes from 'prop-types';
import React, {useContext} from 'react';
Expand Down Expand Up @@ -69,7 +70,16 @@ const FormStepDefinition = ({
});
};

const {translationEnabled, formSteps, registrationBackends} = useContext(FormContext);
const {
translationEnabled,
formSteps,
registrationBackends,
formVariables,
staticVariables,
components,
} = useContext(FormContext);

const validateLogic = createTypeCheck({formVariables, staticVariables, components});

// A 'total configuration': merging all the configurations from the different steps, so that we can figure out if
// a key is unique across steps
Expand Down Expand Up @@ -408,6 +418,7 @@ const FormStepDefinition = ({
onComponentMutated={onComponentMutated.bind(null, url || generatedId)}
componentTranslations={componentTranslations}
componentNamespace={componentNamespace}
validateLogic={validateLogic}
registrationBackendInfo={registrationBackends}
{...props}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,15 @@ WrapperArrayInput.propTypes = {
};

const WrappedJsonWidget = ({name, value, onChange}) => {
return <JsonWidget name={name} logic={value} onChange={onChange} cols={25} />;
return (
<JsonWidget
name={name}
logic={value}
onChange={onChange}
cols={25}
validateJsonLogic={() => ''} // not passing in logic, but any JSON
/>
);
};

WrappedJsonWidget.propTypes = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {createTypeCheck} from '@open-formulieren/formio-builder';
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, {useContext, useState} from 'react';
Expand Down Expand Up @@ -89,16 +90,27 @@ const ActionProperty = ({action, errors, onChange}) => {
);
};

const ActionVariableValue = ({action, errors, onChange}) => (
<>
<DSLEditorNode errors={errors.variable}>
<VariableSelection name="variable" onChange={onChange} value={action.variable} />
</DSLEditorNode>
<DSLEditorNode errors={errors.action?.value}>
<JsonWidget name="action.value" logic={action.action.value} onChange={onChange} />
</DSLEditorNode>
</>
);
const ActionVariableValue = ({action, errors, onChange}) => {
const formContext = useContext(FormContext);
const infer = createTypeCheck(formContext);
// check the expression and the picked variable are of the same type
const validateJsonLogic = expression => infer({'===': [{var: action.variable}, expression]});
return (
<>
<DSLEditorNode errors={errors.variable}>
<VariableSelection name="variable" onChange={onChange} value={action.variable} />
</DSLEditorNode>
<DSLEditorNode errors={errors.action?.value}>
<JsonWidget
name="action.value"
logic={action.action.value}
onChange={onChange}
validateJsonLogic={validateJsonLogic}
/>
</DSLEditorNode>
</>
);
};

const ActionFetchFromService = ({action, errors, onChange}) => {
const intl = useIntl();
Expand Down
44 changes: 12 additions & 32 deletions src/openforms/js/components/admin/forms/JsonWidget.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {createTypeCheck} from '@open-formulieren/formio-builder';
import classNames from 'classnames';
import jsonLogic from 'json-logic-js';
import PropTypes from 'prop-types';
import React, {useEffect, useState} from 'react';
import {useIntl} from 'react-intl';
Expand All @@ -13,29 +13,14 @@ const jsonFormat = value => {
return JSON.stringify(value, null, 2);
};

const isJsonLogic = jsonExpression => {
// jsonLogic accepts primitives
if (
jsonExpression == null || // typeof null -> 'object'
typeof jsonExpression === 'string' ||
typeof jsonExpression === 'boolean' ||
typeof jsonExpression === 'number'
) {
return true;
}

if (Array.isArray(jsonExpression)) {
for (const item of jsonExpression) {
const isValid = isJsonLogic(item);
if (!isValid) return false;
}
return true;
}

return jsonLogic.is_logic(jsonExpression);
};

const JsonWidget = ({name, logic, onChange, cols = 60, isExpanded = true}) => {
const JsonWidget = ({
name,
logic,
onChange,
cols = 60,
isExpanded = true,
validateJsonLogic = createTypeCheck(),
}) => {
const intl = useIntl();
const [jsonError, setJsonError] = useState('');
const [editorValue, setEditorValue] = useState(jsonFormat(logic));
Expand All @@ -48,10 +33,6 @@ const JsonWidget = ({name, logic, onChange, cols = 60, isExpanded = true}) => {
description: 'Advanced logic rule invalid json message',
defaultMessage: 'Invalid JSON syntax',
});
const invalidLogicMessage = intl.formatMessage({
description: 'Advanced logic rule invalid JSON-logic message',
defaultMessage: 'Invalid JSON logic expression',
});

const onJsonChange = event => {
const newValue = event.target.value;
Expand All @@ -71,10 +52,8 @@ const JsonWidget = ({name, logic, onChange, cols = 60, isExpanded = true}) => {
}
}

if (!isJsonLogic(updatedJson)) {
setJsonError(invalidLogicMessage);
return;
}
setJsonError(validateJsonLogic(updatedJson));
if (jsonError) return;

const fakeEvent = {target: {name: name, value: updatedJson}};
onChange(fakeEvent);
Expand Down Expand Up @@ -103,6 +82,7 @@ JsonWidget.propTypes = {
onChange: PropTypes.func.isRequired,
cols: PropTypes.number,
isExpanded: PropTypes.bool,
validateJsonLogic: PropTypes.func,
};

export default JsonWidget;
27 changes: 10 additions & 17 deletions src/openforms/js/components/formio_builder/builder.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {infer} from '@open-formulieren/infernologic';
import cloneDeep from 'lodash/cloneDeep';
import set from 'lodash/set';
import PropTypes from 'prop-types';
Expand All @@ -18,21 +17,6 @@ const getBuilderOptions = () => {
const maxFileUploadSize = jsonScriptToVar('setting-MAX_FILE_UPLOAD_SIZE');
const formFieldsRequiredDefault = jsonScriptToVar('config-REQUIRED_DEFAULT');

/**
* For use in a FormIO custom validator.
*
*
* @param {object | array | number | string} logic - JsonLogic expression
* @param {object | array | number | string} expected - example expected result value [["label", "value"]]
* @returns {boolean | string} - OK or an error message
*/
const validateLogic = (logic, expected) => {
// We don't evaluate logic, we just look at types.
// "===" forces the logic expression align with the expectancy
const result = infer({'===': [logic, expected]}, {});
return result.startsWith('result type:') || result;
};

return {
builder: {
basic: false,
Expand Down Expand Up @@ -267,7 +251,6 @@ const getBuilderOptions = () => {
evalContext: {
serverUploadLimit: maxFileUploadSize,
requiredDefault: formFieldsRequiredDefault,
validateLogic,
},
editors: {
ckeditor: {
Expand Down Expand Up @@ -325,6 +308,7 @@ const FormIOBuilder = ({
configuration,
onChange,
onComponentMutated,
validateLogic,
componentTranslations = {}, // mapping of language code to (mapping of literal -> translation)
componentNamespace = {},
registrationBackendInfo = [],
Expand Down Expand Up @@ -372,6 +356,15 @@ const FormIOBuilder = ({
set(builderOptions, 'openForms.componentNamespace', componentNamespaceRef.current);
set(builderOptions, 'openForms.featureFlags', featureFlags);
set(builderOptions, 'openForms.registrationBackendInfoRef', registrationBackendInfoRef);
// FormIO texteditor validator expects true if valid or string with error message.
// const validateLogic = (expression, expected) => {
// const error = validateLogicProp(expression, expected);
// console.log(expression);
// console.log(expected);
// console.log(error);
// return error === '' || error;
// };
set(builderOptions, 'evalContext.validateLogic', validateLogic);

// if an update must be forced, we mutate the ref state to point to the new
// configuration, which causes the form builder to re-render the new configuration.
Expand Down
6 changes: 3 additions & 3 deletions src/openforms/submissions/models/submission_value_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ def to_python(self) -> Any:
return self.value

if self.value and data_type == FormVariableDataTypes.date:
date = format_date_value(self.value)
date = format_date_value(str(self.value))
naive_date = parse_date(date)
if naive_date is not None:
aware_date = timezone.make_aware(datetime.combine(naive_date, time.min))
Expand All @@ -366,7 +366,7 @@ def to_python(self) -> Any:
raise ValueError(f"Could not parse date '{self.value}'") # pragma: nocover

if self.value and data_type == FormVariableDataTypes.datetime:
maybe_naive_datetime = parse_datetime(self.value)
maybe_naive_datetime = parse_datetime(str(self.value))
if maybe_naive_datetime is not None:
if timezone.is_aware(maybe_naive_datetime):
return maybe_naive_datetime
Expand All @@ -377,7 +377,7 @@ def to_python(self) -> Any:
) # pragma: nocover

if self.value and data_type == FormVariableDataTypes.time:
value = parse_time(self.value)
value = parse_time(str(self.value))
if value is not None:
return value
raise ValueError(f"Could not parse time '{self.value}'") # pragma: nocover
Expand Down

0 comments on commit 999ab14

Please sign in to comment.