Skip to content

Commit

Permalink
fix: validate values assigned from the rules engine
Browse files Browse the repository at this point in the history
  • Loading branch information
simonadomnisoru committed Apr 18, 2024
1 parent 295166f commit 108a237
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type FieldUI = {
errorType?: ?string,
errorData?: ErrorData,
validatingMessage?: ?string,
pendingValidation?: ?boolean,
};

type RenderDividerFn = (index: number, fieldsCount: number, field: FieldConfig) => React.Node;
Expand Down Expand Up @@ -75,6 +76,17 @@ type Props = {
onUpdateField: (value: any, uiState: FieldUI, fieldId: string, formBuilderId: string, promiseForIsValidating: string) => void,
onUpdateFieldUIOnly: (uiState: FieldUI, fieldId: string, formBuilderId: string) => void,
onFieldsValidated: ?(fieldsUI: { [id: string]: FieldUI }, formBuilderId: string, uidsForIsValidating: Array<string>) => void,
onFieldValidated: ?(
fieldUI: {
fieldId: string,
valid?: ?boolean,
errorMessage?: ?string | Array<string>,
errorType?: ?string,
errorData?: ErrorData,
},
formBuilderId: string,
uidForIsValidating: string,
) => void,
querySingleResource: QuerySingleResource,
validationAttempted?: ?boolean,
validateIfNoUIData?: ?boolean,
Expand Down Expand Up @@ -144,6 +156,9 @@ export class FormBuilder extends React.Component<Props> {

return {
valid: true,
errorMessage: null,
errorType: null,
errorData: null,
};
}

Expand Down Expand Up @@ -254,6 +269,69 @@ export class FormBuilder extends React.Component<Props> {
);
}

static executeValidateField(
props: Props,
fieldsValidatingPromiseContainer: FieldsValidatingPromiseContainer,
field: FieldConfig,
) {
const {
id,
values,
onGetValidationContext,
onIsValidating,
} = props;
const validationContext = onGetValidationContext && onGetValidationContext();
const validationPromise = (async () => {
const fieldId = field.id;
const fieldValidatingPromiseContainer = fieldsValidatingPromiseContainer[fieldId] || {};
fieldsValidatingPromiseContainer[fieldId] = fieldValidatingPromiseContainer;

if (!fieldValidatingPromiseContainer.validatingCompleteUid) {
fieldValidatingPromiseContainer.validatingCompleteUid = uuid();
}
fieldValidatingPromiseContainer.cancelableValidatingPromise &&
fieldValidatingPromiseContainer.cancelableValidatingPromise.cancel();

const handleIsValidatingInternal = (message: ?string, promise: Promise<any>) => {
fieldValidatingPromiseContainer.cancelableValidatingPromise = makeCancelablePromise(promise);
onIsValidating && onIsValidating(
field.id,
id,
fieldValidatingPromiseContainer.validatingCompleteUid,
message,
null,
);

return fieldValidatingPromiseContainer.cancelableValidatingPromise.promise;
};

let validationData;
try {
validationData = await FormBuilder.validateField(
field,
values[field.id],
validationContext,
handleIsValidatingInternal,
);
} catch (reason) {
if (reason && isObject(reason) && reason.isCanceled) {
validationData = null;
} else {
validationData = {
valid: false,
errorMessage: [i18n.t('error encountered during field validation')],
errorType: i18n.t('error'),
};
log.error({ reason, field });
}
}

return { fieldId: field.id, ...validationData };
});

return validationPromise;
}

fieldInstances: Map<string, any>;
asyncUIState: { [id: string]: FieldUI };
fieldsValidatingPromiseContainer: FieldsValidatingPromiseContainer;
Expand Down Expand Up @@ -300,6 +378,43 @@ export class FormBuilder extends React.Component<Props> {
return remainingCompleteUids;
}

validateField(
props: Props,
field: FieldConfig,
) {
const { cancelableValidatingPromise } = this.fieldsValidatingPromiseContainer[field.id] || {};
cancelableValidatingPromise && cancelableValidatingPromise.cancel();

const promiseValidateField = FormBuilder.executeValidateField(
props,
this.fieldsValidatingPromiseContainer,
field,
);

promiseValidateField()
.then((validationContainer) => {
props.onFieldValidated &&
this.fieldsValidatingPromiseContainer[field.id]?.validatingCompleteUid &&
props.onFieldValidated(
validationContainer,
props.id,
this.fieldsValidatingPromiseContainer[field.id].validatingCompleteUid,
);

if (!this.commitUpdateTriggeredForFields[field.id]) {
this.fieldsValidatingPromiseContainer[field.id] = null;
}
})
.catch((reason) => {
if (!reason || !isObject(reason) || !reason.isCanceled) {
log.error({
reason,
message: 'formBuilder validate field failed',
});
}
});
}

validateAllFields(
props: Props,
) {
Expand Down Expand Up @@ -571,6 +686,7 @@ export class FormBuilder extends React.Component<Props> {
fieldsUI,
validationAttempted,
id,
onFieldValidated,
onFieldsValidated,
onUpdateField,
onUpdateFieldAsync,
Expand Down Expand Up @@ -601,6 +717,11 @@ export class FormBuilder extends React.Component<Props> {
asyncProps.onCommitAsync = (callback: Function) => this.handleCommitAsync(field.id, props.label, callback);
asyncProps.asyncUIState = this.asyncUIState[field.id];
}
if (fieldUI.pendingValidation) {
const asyncStateToAdd = { ...fieldUI, pendingValidation: false };
this.handleUpdateAsyncState(field.id, asyncStateToAdd);
this.validateField(this.props, field);
}

const errorMessage = onPostProcessErrorMessage && fieldUI.errorMessage ?
onPostProcessErrorMessage({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import i18n from '@dhis2/d2-i18n';
import { actionCreator } from '../../../actions/actions.utils';

export const actionTypes = {
FIELD_VALIDATED: 'FieldValidated',
FIELDS_VALIDATED: 'FieldsValidated',
FIELD_IS_VALIDATING: 'FieldIsValidating',
START_UPDATE_FIELD_ASYNC: 'StartUpdateFieldAsync',
Expand Down Expand Up @@ -38,6 +39,13 @@ export const fieldsValidated = (
actionCreator(actionTypes.FIELDS_VALIDATED)(
{ fieldsUI, formBuilderId, formId, validatingUids });

export const fieldValidated = (
fieldUI: Object,
formBuilderId: string,
formId: string,
validatingUid: string,
) => actionCreator(actionTypes.FIELD_VALIDATED)({ fieldUI, formBuilderId, formId, validatingUid });

export const startUpdateFieldAsync = (
elementId: string,
fieldLabel: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
import * as React from 'react';
import { connect } from 'react-redux';
import { v4 as uuid } from 'uuid';
import { fieldIsValidating, fieldsValidated, startUpdateFieldAsync } from './actions';
import { fieldIsValidating, fieldsValidated, fieldValidated, startUpdateFieldAsync } from './actions';

type Props = {
id: string,
onIsValidating: Function,
onFieldsValidated: Function,
onFieldValidated: Function,
onUpdateFieldAsyncInner: Function,
onUpdateFieldAsync: ?Function,
};
Expand All @@ -27,6 +28,12 @@ const getAsyncHandler = (InnerComponent: React.ComponentType<any>) =>
this.props.onFieldsValidated(...args, id);
}

// $FlowFixMe[missing-annot] automated comment
handleFieldValidated = (...args) => {
const { id } = this.props;
this.props.onFieldValidated(...args, id);
}

// $FlowFixMe[missing-annot] automated comment
handleUpdateFieldAsyncInner = (...args) => {
const { onUpdateFieldAsyncInner, onUpdateFieldAsync } = this.props;
Expand All @@ -37,6 +44,7 @@ const getAsyncHandler = (InnerComponent: React.ComponentType<any>) =>
const {
onIsValidating,
onFieldsValidated,
onFieldValidated,
onUpdateFieldAsyncInner,
onUpdateFieldAsync,
...passOnProps } = this.props;
Expand All @@ -45,6 +53,7 @@ const getAsyncHandler = (InnerComponent: React.ComponentType<any>) =>
<InnerComponent
onIsValidating={this.handleIsValidating}
onFieldsValidated={this.handleFieldsValidated}
onFieldValidated={this.handleFieldValidated}
onUpdateFieldAsync={this.handleUpdateFieldAsyncInner}
{...passOnProps}
/>
Expand Down Expand Up @@ -75,6 +84,15 @@ const mapDispatchToProps = (dispatch: ReduxDispatch) => ({
const action = fieldsValidated(fieldsUI, formBuilderId, formId, validatingUids);
dispatch(action);
},
onFieldValidated: (
fieldUI: Object,
formBuilderId: string,
validatingUid: string,
formId: string,
) => {
const action = fieldValidated(fieldUI, formBuilderId, formId, validatingUid);
dispatch(action);
},
onUpdateFieldAsyncInner: (
fieldId: string,
fieldLabel: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,14 @@ export const dataEntriesInProgressListDesc = createReducerDescription({
[formId]: updatedList,
};
},
[formAsyncActionTypes.FIELD_VALIDATED]: (state, action) => {
const { formId, validatingUid } = action.payload;
const updatedList = (state[formId] || []).filter(item => item !== validatingUid);
return {
...state,
[formId]: updatedList,
};
},
[actionTypes.UPDATE_FORM_FIELD]: (state, action) => {
const { formId, updateCompleteUid } = action.payload;
const updatedList = (state[formId] || []).filter(item => item !== updateCompleteUid);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ export const formsSectionsFieldsUIDesc = createReducerDescription({

return newState;
},
[formAsyncActionTypes.FIELD_VALIDATED]: (state, action) => {
const newState = { ...state };
const { formId, fieldUI } = action.payload;
const { fieldId, ...restPayload } = fieldUI;
const newValues = { ...newState[formId][fieldId], ...restPayload, validatingMessage: null };

newState[formId] = { ...newState[formId], [fieldId]: newValues };
return newState;
},
[formAsyncActionTypes.FIELDS_VALIDATED]: (state, action) => {
const newState = { ...state };
const payload = action.payload;
Expand Down Expand Up @@ -216,13 +225,11 @@ export const formsSectionsFieldsUIDesc = createReducerDescription({
const updatedFields = Object.keys(assignEffects).reduce((acc, id) => {
if (formSectionFields[id]) {
acc[id] = {
valid: true,
errorData: undefined,
errorMessage: undefined,
errorType: undefined,
...state[formId][id],
modified: true,
touched: true,
validatingMessage: null,
pendingValidation: true,
};
}
return acc;
Expand Down

0 comments on commit 108a237

Please sign in to comment.