From 3d237e05491bc46c5a2342ce5ff425557365e520 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Thu, 29 Aug 2024 16:22:55 +0200 Subject: [PATCH 01/22] fix: validate the assigned values from rules engine --- .../D2Form/field/validators/getValidators.js | 5 +- .../D2Form/field/validators/index.js | 2 + .../field/validators/validateAssignEffects.js | 92 +++++++++++ .../capture-core/components/D2Form/index.js | 2 + .../EnrollmentDataEntry.component.js | 29 +++- .../EnrollmentDataEntry.container.js | 11 +- .../actions/enrollment.actionBatchs.js | 86 ++++++++-- .../Enrollment/actions/enrollment.actions.js | 3 +- .../Enrollment/actions/open.actionBatchs.js | 12 +- .../Enrollment/epics/enrollment.epics.js | 155 +++++++++++------- .../EnrollmentRegistrationEntry.epics.js | 7 +- .../epics/newEventDataEntry.epics.js | 63 +++++-- .../DataEntry/helpers/getRulesActions.js | 14 +- .../DataEntryWrapper/useRulesEngine.js | 19 ++- .../DataEntry/epics/dataEntryRules.epics.js | 64 ++++++-- .../DataEntry/helpers/getRulesActions.js | 14 +- .../Validated/useLifecycle.js | 32 ++-- .../DataEntry/editEventDataEntry.actions.js | 26 ++- .../epics/editEventDataEntry.epics.js | 48 +++++- .../editEventDataEntry.epics.js | 35 ++-- .../DataEntry/DataEntry.container.js | 34 ++-- .../ProgramRules/getRulesActionsForTEI.js | 34 +++- .../DataEntry/dataEntry.actions.js | 14 +- .../DataEntry/hooks/useLifecycle.js | 38 +++-- .../descriptions/form.reducerDescription.js | 31 ++-- 25 files changed, 646 insertions(+), 224 deletions(-) create mode 100644 src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js diff --git a/src/core_modules/capture-core/components/D2Form/field/validators/getValidators.js b/src/core_modules/capture-core/components/D2Form/field/validators/getValidators.js index be3c6b8e16..b05b037bfc 100644 --- a/src/core_modules/capture-core/components/D2Form/field/validators/getValidators.js +++ b/src/core_modules/capture-core/components/D2Form/field/validators/getValidators.js @@ -30,7 +30,10 @@ import { dataElementTypes, type DateDataElement, type DataElement } from '../../ import { validatorTypes } from './constants'; import type { QuerySingleResource } from '../../../../utils/api/api.types'; -type Validator = (value: any) => Promise | boolean | { valid: boolean, errorMessage?: any}; +type Validator = ( + value: any, + contextProps: ?Object, +) => Promise | boolean | { valid: boolean, errorMessage?: any, data?: any }; export type ValidatorContainer = { validator: Validator, diff --git a/src/core_modules/capture-core/components/D2Form/field/validators/index.js b/src/core_modules/capture-core/components/D2Form/field/validators/index.js index bc50f272c3..f21eb5e94e 100644 --- a/src/core_modules/capture-core/components/D2Form/field/validators/index.js +++ b/src/core_modules/capture-core/components/D2Form/field/validators/index.js @@ -1,2 +1,4 @@ // @flow export { getValidators } from './getValidators'; +export { validateAssignEffects } from './validateAssignEffects'; +export type { AssignOutputEffectWithValidations } from './validateAssignEffects'; diff --git a/src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js b/src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js new file mode 100644 index 0000000000..e1babbeb2b --- /dev/null +++ b/src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js @@ -0,0 +1,92 @@ +// @flow +import { errorCreator } from 'capture-core-utils'; +import { effectActions } from '@dhis2/rules-engine-javascript'; +import log from 'loglevel'; +import type { AssignOutputEffect } from '@dhis2/rules-engine-javascript'; +import { type DataElement } from '../../../../metaData'; +import type { QuerySingleResource } from '../../../../utils/api'; +import { getValidators } from './getValidators'; + +export type AssignOutputEffectWithValidations = { + [metaDataId: string]: Array, +}; + +const getValidatorsResult = async (validators, value, validationContext) => + validators.reduce(async (passPromise, currentValidator) => { + const pass = await passPromise; + if (pass === true) { + if (!currentValidator) { + return true; + } + const result: Object = currentValidator.validator(value, validationContext); + + if (result === true || (result && result.valid)) { + return true; + } + return { + message: result.errorMessage || currentValidator.message, + type: currentValidator.type, + data: result.data, + }; + } + return pass; + }, Promise.resolve(true)); + +export const validateAssignEffects = async ({ + dataElements, + effects, + querySingleResource, + onGetValidationContext, +}: { + dataElements: Array, + effects: Object, + querySingleResource: QuerySingleResource, + onGetValidationContext?: () => Object, +}): Promise => { + const assignEffects: { [metaDataId: string]: Array } = effects[effectActions.ASSIGN_VALUE]; + if (!assignEffects) { + return effects; + } + + const assignEffectsWithValidations = await dataElements.reduce(async (passPromise, metaData: DataElement) => { + const acc = await passPromise; + if (assignEffects[metaData.id]) { + const effectsForId = assignEffects[metaData.id]; + const lastEffect = effectsForId.length - 1; + const value = effectsForId[lastEffect].value; + const validators = getValidators(metaData, querySingleResource); + const validationContext = onGetValidationContext && onGetValidationContext(); + + try { + const validatorResult = await getValidatorsResult(validators, value, validationContext); + const effectWithValidation = validatorResult === true + ? { + ...effectsForId[lastEffect], + valid: true, + } + : { + ...effectsForId[lastEffect], + valid: false, + errorMessage: validatorResult.message, + errorType: validatorResult.type, + errorData: validatorResult.data, + }; + + acc[metaData.id] = [...effectsForId.slice(0, lastEffect - 1), effectWithValidation]; + return acc; + } catch (error) { + log.error( + errorCreator('an error occured while validating the assigned program rule effect')({ + metaData, + lastEffect, + error, + }), + ); + return acc; + } + } + return acc; + }, Promise.resolve({})); + + return { ...effects, [effectActions.ASSIGN_VALUE]: assignEffectsWithValidations }; +}; diff --git a/src/core_modules/capture-core/components/D2Form/index.js b/src/core_modules/capture-core/components/D2Form/index.js index ac6560e0f2..ac4d45758a 100644 --- a/src/core_modules/capture-core/components/D2Form/index.js +++ b/src/core_modules/capture-core/components/D2Form/index.js @@ -1,3 +1,5 @@ // @flow export { asyncHandlerActionTypes, asyncUpdateFieldEpic } from './asyncHandlerHOC'; export { D2Form } from './D2Form.container'; +export { validateAssignEffects } from './field/validators'; +export type { AssignOutputEffectWithValidations } from './field/validators'; diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.component.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.component.js index 9a902bab07..916207dcc1 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.component.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.component.js @@ -419,17 +419,38 @@ export class EnrollmentDataEntryComponent extends React.Component) => { const { programId, orgUnit, firstStageMetaData, formFoundation } = this.props; - this.props.onUpdateField(...args, programId, orgUnit, firstStageMetaData?.stage, formFoundation); - } + this.props.onUpdateField( + ...args, + programId, + orgUnit, + firstStageMetaData?.stage, + formFoundation, + this.getValidationContext, + ); + }; handleUpdateDataEntryField = (...args: Array) => { const { programId, orgUnit, firstStageMetaData, formFoundation } = this.props; - this.props.onUpdateDataEntryField(...args, programId, orgUnit, firstStageMetaData?.stage, formFoundation); + this.props.onUpdateDataEntryField( + ...args, + programId, + orgUnit, + firstStageMetaData?.stage, + formFoundation, + this.getValidationContext, + ); } handleStartAsyncUpdateField = (...args: Array) => { const { programId, orgUnit, firstStageMetaData, formFoundation } = this.props; - this.props.onStartAsyncUpdateField(...args, programId, orgUnit, firstStageMetaData?.stage, formFoundation); + this.props.onStartAsyncUpdateField( + ...args, + programId, + orgUnit, + firstStageMetaData?.stage, + formFoundation, + this.getValidationContext, + ); } render() { diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.container.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.container.js index 6df22c0f34..4bf992d94a 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.container.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.container.js @@ -14,8 +14,11 @@ const mapDispatchToProps = (dispatch: ReduxDispatch) => ({ orgUnit: OrgUnit, stage?: ProgramStage, formFoundation: RenderFoundation, + onGetValidationContext: () => Object, ) => { - dispatch(updateDataEntryFieldBatch(innerAction, programId, orgUnit, stage, formFoundation)); + dispatch( + updateDataEntryFieldBatch(innerAction, programId, orgUnit, stage, formFoundation, onGetValidationContext), + ); }, onUpdateField: ( innerAction: ReduxAction, @@ -23,8 +26,9 @@ const mapDispatchToProps = (dispatch: ReduxDispatch) => ({ orgUnit: OrgUnit, stage?: ProgramStage, formFoundation: RenderFoundation, + onGetValidationContext: () => Object, ) => { - dispatch(updateFieldBatch(innerAction, programId, orgUnit, stage, formFoundation)); + dispatch(updateFieldBatch(innerAction, programId, orgUnit, stage, formFoundation, onGetValidationContext)); }, onStartAsyncUpdateField: ( innerAction: ReduxAction, @@ -34,9 +38,10 @@ const mapDispatchToProps = (dispatch: ReduxDispatch) => ({ orgUnit: OrgUnit, stage?: ProgramStage, formFoundation: RenderFoundation, + onGetValidationContext: () => Object, ) => { const onAsyncUpdateSuccess = (successInnerAction: ReduxAction) => - asyncUpdateSuccessBatch(successInnerAction, dataEntryId, itemId, programId, orgUnit, stage, formFoundation); + asyncUpdateSuccessBatch(successInnerAction, dataEntryId, itemId, programId, orgUnit, stage, formFoundation, onGetValidationContext); const onAsyncUpdateError = (errorInnerAction: ReduxAction) => errorInnerAction; dispatch(startAsyncUpdateFieldForNewEnrollment(innerAction, onAsyncUpdateSuccess, onAsyncUpdateError)); diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actionBatchs.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actionBatchs.js index f84452c7fe..79746d77c2 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actionBatchs.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actionBatchs.js @@ -11,6 +11,8 @@ import { rulesExecutedPostUpdateField } from '../../../DataEntry/actions/dataEnt import { TrackerProgram, RenderFoundation, ProgramStage } from '../../../../metaData'; import { startRunRulesPostUpdateField } from '../../../DataEntry'; import { startRunRulesOnUpdateForNewEnrollment } from './enrollment.actions'; +import { validateAssignEffects } from '../../../D2Form'; +import type { QuerySingleResource } from '../../../../utils/api'; export const batchActionTypes = { RULES_EXECUTED_POST_UPDATE_FIELD_FOR_ENROLLMENT: 'RulesExecutedPostUpdateFieldForEnrollment', @@ -18,7 +20,7 @@ export const batchActionTypes = { UPDATE_DATA_ENTRY_FIELD_NEW_ENROLLMENT_ACTION_BATCH: 'UpdateDataEntryFieldNewEnrollmentActionBatch', }; -export const runRulesOnUpdateFieldBatch = ({ +export const runRulesOnUpdateFieldBatch = async ({ program, formId, dataEntryId, @@ -31,6 +33,8 @@ export const runRulesOnUpdateFieldBatch = ({ stage, formFoundation, currentEvent, + querySingleResource, + onGetValidationContext, }: { program: TrackerProgram, formId: string, @@ -41,9 +45,11 @@ export const runRulesOnUpdateFieldBatch = ({ attributeValues?: TEIValues, extraActions: Array>, uid: string, - stage?: ProgramStage, + stage: ProgramStage, formFoundation?: RenderFoundation, currentEvent?: {[id: string]: any}, + querySingleResource: QuerySingleResource, + onGetValidationContext: () => Object, }) => { const effects = getApplicableRuleEffectsForTrackerProgram({ program, @@ -54,8 +60,16 @@ export const runRulesOnUpdateFieldBatch = ({ attributeValues, formFoundation, }); + + const effectsWithValidations = await validateAssignEffects({ + dataElements: formFoundation ? formFoundation.getElements() : program.attributes, + effects, + querySingleResource, + onGetValidationContext, + }); + return batchActions([ - updateRulesEffects(effects, formId), + updateRulesEffects(effectsWithValidations, formId), rulesExecutedPostUpdateField(dataEntryId, itemId, uid), ...extraActions, ], batchActionTypes.RULES_EXECUTED_POST_UPDATE_FIELD_FOR_ENROLLMENT); @@ -67,15 +81,27 @@ export const updateDataEntryFieldBatch = ( orgUnit: OrgUnit, stage?: ProgramStage, formFoundation: RenderFoundation, + onGetValidationContext: () => Object, ) => { const { dataEntryId, itemId } = innerAction.payload; const uid = uuid(); - return batchActions([ - innerAction, - startRunRulesPostUpdateField(dataEntryId, itemId, uid), - startRunRulesOnUpdateForNewEnrollment(innerAction.payload, uid, programId, orgUnit, stage, formFoundation), - ], batchActionTypes.UPDATE_DATA_ENTRY_FIELD_NEW_ENROLLMENT_ACTION_BATCH); + return batchActions( + [ + innerAction, + startRunRulesPostUpdateField(dataEntryId, itemId, uid), + startRunRulesOnUpdateForNewEnrollment( + innerAction.payload, + uid, + programId, + orgUnit, + stage, + formFoundation, + onGetValidationContext, + ), + ], + batchActionTypes.UPDATE_DATA_ENTRY_FIELD_NEW_ENROLLMENT_ACTION_BATCH, + ); }; export const updateFieldBatch = ( @@ -84,15 +110,27 @@ export const updateFieldBatch = ( orgUnit: OrgUnit, stage?: ProgramStage, formFoundation: RenderFoundation, + onGetValidationContext: () => Object, ) => { const { dataEntryId, itemId } = innerAction.payload; const uid = uuid(); - return batchActions([ - innerAction, - startRunRulesPostUpdateField(dataEntryId, itemId, uid), - startRunRulesOnUpdateForNewEnrollment(innerAction.payload, uid, programId, orgUnit, stage, formFoundation), - ], batchActionTypes.UPDATE_FIELD_NEW_ENROLLMENT_ACTION_BATCH); + return batchActions( + [ + innerAction, + startRunRulesPostUpdateField(dataEntryId, itemId, uid), + startRunRulesOnUpdateForNewEnrollment( + innerAction.payload, + uid, + programId, + orgUnit, + stage, + formFoundation, + onGetValidationContext, + ), + ], + batchActionTypes.UPDATE_FIELD_NEW_ENROLLMENT_ACTION_BATCH, + ); }; export const asyncUpdateSuccessBatch = ( @@ -103,12 +141,24 @@ export const asyncUpdateSuccessBatch = ( orgUnit: OrgUnit, stage?: ProgramStage, formFoundation: RenderFoundation, + onGetValidationContext: () => Object, ) => { const uid = uuid(); - return batchActions([ - innerAction, - startRunRulesPostUpdateField(dataEntryId, itemId, uid), - startRunRulesOnUpdateForNewEnrollment({ ...innerAction.payload, dataEntryId, itemId }, uid, programId, orgUnit, stage, formFoundation), - ], batchActionTypes.UPDATE_FIELD_NEW_ENROLLMENT_ACTION_BATCH); + return batchActions( + [ + innerAction, + startRunRulesPostUpdateField(dataEntryId, itemId, uid), + startRunRulesOnUpdateForNewEnrollment( + { ...innerAction.payload, dataEntryId, itemId }, + uid, + programId, + orgUnit, + stage, + formFoundation, + onGetValidationContext, + ), + ], + batchActionTypes.UPDATE_FIELD_NEW_ENROLLMENT_ACTION_BATCH, + ); }; diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actions.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actions.js index 68d8a8cb7e..68dec4ccea 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actions.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actions.js @@ -14,9 +14,10 @@ export const startRunRulesOnUpdateForNewEnrollment = ( orgUnit: OrgUnit, stage?: ProgramStage, formFoundation: RenderFoundation, + onGetValidationContext: () => Object, ) => actionCreator(actionTypes.START_RUN_RULES_ON_UPDATE)( - { innerPayload: payload, uid, programId, orgUnit, stage, formFoundation }); + { innerPayload: payload, uid, programId, orgUnit, stage, formFoundation, onGetValidationContext }); export const startAsyncUpdateFieldForNewEnrollment = ( innerAction: ReduxAction, diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.js index b6dcff586f..cd41155381 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.js @@ -16,6 +16,8 @@ import { convertDateObjectToDateFormatString } from '../../../../utils/converter import { addFormData } from '../../../D2Form/actions/form.actions'; import type { ProgramCategory } from '../../../WidgetEventSchedule/CategoryOptions/CategoryOptions.types'; import { getDataEntryPropsToInclude } from '../EnrollmentWithFirstStageDataEntry'; +import { validateAssignEffects } from '../../../D2Form'; +import type { QuerySingleResource } from '../../../../utils/api'; const itemId = 'newEnrollment'; @@ -56,6 +58,7 @@ export const openDataEntryForNewEnrollmentBatchAsync = async ({ firstStage, programCategory, formFoundation, + querySingleResource, }: { program: TrackerProgram, orgUnit: OrgUnit, @@ -67,6 +70,7 @@ export const openDataEntryForNewEnrollmentBatchAsync = async ({ firstStage?: ProgramStage, programCategory?: ProgramCategory, formFoundation: RenderFoundation, + querySingleResource: QuerySingleResource, }) => { const formId = getDataEntryKey(dataEntryId, itemId); const addFormDataActions = addFormData(`${dataEntryId}-${itemId}`, formValues); @@ -101,13 +105,19 @@ export const openDataEntryForNewEnrollmentBatchAsync = async ({ formFoundation, }); + const effectsWithValidations = await validateAssignEffects({ + dataElements: formFoundation.getElements(), + effects, + querySingleResource, + }); + return batchActions([ openDataEntryForNewEnrollment( dataEntryId, ), ...dataEntryActions, addFormDataActions, - updateRulesEffects(effects, formId), + updateRulesEffects(effectsWithValidations, formId), ...extraActions, ], batchActionTypes.OPEN_DATA_ENTRY_FOR_NEW_ENROLLMENT_BATCH); }; diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/epics/enrollment.epics.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/epics/enrollment.epics.js index 9bc4da634a..881c5aa85d 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/epics/enrollment.epics.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/epics/enrollment.epics.js @@ -1,7 +1,8 @@ // @flow import type { OrgUnit } from '@dhis2/rules-engine-javascript'; import { ofType } from 'redux-observable'; -import { map } from 'rxjs/operators'; +import { from } from 'rxjs'; +import { map, switchMap } from 'rxjs/operators'; import { batchActionTypes, runRulesOnUpdateFieldBatch } from '../actions/enrollment.actionBatchs'; import { actionTypes } from '../actions/enrollment.actions'; import { getTrackerProgramThrowIfNotFound, ProgramStage, RenderFoundation, Section } from '../../../../metaData'; @@ -16,7 +17,7 @@ type Context = { uid: string, programId: string, orgUnit: OrgUnit, - stage?: ProgramStage, + stage: ProgramStage, formFoundation: RenderFoundation, } @@ -38,46 +39,62 @@ const splitCurrentClientMainData = (stage, currentClientMainData) => { }, { currentEnrollmentValues: {}, currentEventMainData: {} }); }; -const runRulesOnEnrollmentUpdate = - (store: ReduxStore, context: Context, fieldData?: ?FieldData, searchActions?: any = []) => { - const state = store.value; - const { programId, dataEntryId, itemId, orgUnit, uid, stage, formFoundation } = context; - const formId = getDataEntryKey(dataEntryId, itemId); - const program = getTrackerProgramThrowIfNotFound(programId); - const currentFormData = state.formsValues[formId] || {}; - const convertedValues = formFoundation.convertAndGroupBySection(currentFormData, convertFormToClient); - const attributeValues = convertedValues[Section.groups.ENROLLMENT]; - const currentEventValues = convertedValues[Section.groups.EVENT] || {}; - const currentClientMainData = - getCurrentClientMainData(state, itemId, dataEntryId, formFoundation) || {}; - const { currentEnrollmentValues, currentEventMainData } - = splitCurrentClientMainData(state, currentClientMainData); - const currentEvent = stage - ? { ...currentEventValues, ...currentEventMainData, programStageId: stage.id } : undefined; - - return runRulesOnUpdateFieldBatch({ - program, - formId, - dataEntryId, - itemId, - orgUnit, - enrollmentData: currentEnrollmentValues, - attributeValues, - currentEvent, - extraActions: searchActions, - uid, - stage, - formFoundation: stage ? formFoundation : undefined, - }); - }; +const runRulesOnEnrollmentUpdate = ({ + store, + context, + searchActions = [], + querySingleResource, + onGetValidationContext, +}: { + store: ReduxStore, + context: Context, + searchActions?: any, + querySingleResource: any, + onGetValidationContext: () => Object, +}) => { + const state = store.value; + const { programId, dataEntryId, itemId, orgUnit, uid, stage, formFoundation } = context; + const formId = getDataEntryKey(dataEntryId, itemId); + const program = getTrackerProgramThrowIfNotFound(programId); + const currentFormData = state.formsValues[formId] || {}; + const convertedValues = formFoundation.convertAndGroupBySection(currentFormData, convertFormToClient); + const attributeValues = convertedValues[Section.groups.ENROLLMENT]; + const currentEventValues = convertedValues[Section.groups.EVENT] || {}; + const currentClientMainData = getCurrentClientMainData(state, itemId, dataEntryId, formFoundation) || {}; + const { currentEnrollmentValues, currentEventMainData } = splitCurrentClientMainData(state, currentClientMainData); + const currentEvent = stage + ? { ...currentEventValues, ...currentEventMainData, programStageId: stage.id } + : undefined; + const runRulesOnUpdateFieldBatchPromise = runRulesOnUpdateFieldBatch({ + program, + formId, + dataEntryId, + itemId, + orgUnit, + enrollmentData: currentEnrollmentValues, + attributeValues, + currentEvent, + extraActions: searchActions, + uid, + stage, + formFoundation: stage ? formFoundation : undefined, + querySingleResource, + onGetValidationContext, + }); + return from(runRulesOnUpdateFieldBatchPromise); +}; -export const runRulesOnEnrollmentDataEntryFieldUpdateEpic = (action$: InputObservable, store: ReduxStore) => +export const runRulesOnEnrollmentDataEntryFieldUpdateEpic = ( + action$: InputObservable, + store: ReduxStore, + { querySingleResource }: ApiUtils, +) => action$.pipe( ofType(batchActionTypes.UPDATE_DATA_ENTRY_FIELD_NEW_ENROLLMENT_ACTION_BATCH), map(actionBatch => actionBatch.payload.find(action => action.type === actionTypes.START_RUN_RULES_ON_UPDATE)), - map((action) => { + switchMap((action) => { const { uid, programId, @@ -85,6 +102,7 @@ export const runRulesOnEnrollmentDataEntryFieldUpdateEpic = (action$: InputObser innerPayload, stage, formFoundation, + onGetValidationContext, } = action.payload; const { @@ -92,24 +110,42 @@ export const runRulesOnEnrollmentDataEntryFieldUpdateEpic = (action$: InputObser itemId, } = innerPayload; - return runRulesOnEnrollmentUpdate(store, { - dataEntryId, - itemId, - uid, - programId, - orgUnit, - stage, - formFoundation, + return runRulesOnEnrollmentUpdate({ + store, + context: { + dataEntryId, + itemId, + uid, + programId, + orgUnit, + stage, + formFoundation, + }, + querySingleResource, + onGetValidationContext, }); })); -export const runRulesOnEnrollmentFieldUpdateEpic = (action$: InputObservable, store: ReduxStore) => +export const runRulesOnEnrollmentFieldUpdateEpic = ( + action$: InputObservable, + store: ReduxStore, + { querySingleResource }: ApiUtils, +) => action$.pipe( ofType(batchActionTypes.UPDATE_FIELD_NEW_ENROLLMENT_ACTION_BATCH), map(actionBatch => actionBatch.payload.find(action => action.type === actionTypes.START_RUN_RULES_ON_UPDATE)), - map((action) => { - const { innerPayload: payload, searchActions, uid, programId, orgUnit, stage, formFoundation } = action.payload; + switchMap((action) => { + const { + innerPayload: payload, + searchActions, + uid, + programId, + orgUnit, + stage, + formFoundation, + onGetValidationContext, + } = action.payload; const { dataEntryId, itemId, elementId, value, uiState } = payload; const fieldData: FieldData = { @@ -118,14 +154,21 @@ export const runRulesOnEnrollmentFieldUpdateEpic = (action$: InputObservable, st valid: uiState.valid, }; - return runRulesOnEnrollmentUpdate(store, { - programId, - orgUnit, - dataEntryId, - itemId, - uid, - stage, - formFoundation, - }, fieldData, searchActions); + return runRulesOnEnrollmentUpdate({ + store, + context: { + programId, + orgUnit, + dataEntryId, + itemId, + uid, + stage, + formFoundation, + }, + fieldData, + searchActions, + querySingleResource, + onGetValidationContext, + }); }), ); diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.epics.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.epics.js index 44e25f9bc4..48ba34c309 100644 --- a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.epics.js +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.epics.js @@ -11,7 +11,11 @@ import { getTrackerProgramThrowIfNotFound } from '../../../metaData/helpers'; import { openDataEntryFailed } from '../../Pages/NewRelationship/RegisterTei/DataEntry/RegisterTeiDataEntry.actions'; import type { TrackerProgram } from '../../../metaData/Program'; -export const startNewEnrollmentDataEntrySelfInitialisationEpic = (action$: InputObservable) => +export const startNewEnrollmentDataEntrySelfInitialisationEpic = ( + action$: InputObservable, + store: ReduxStore, + { querySingleResource }: ApiUtils, +) => action$.pipe( ofType(enrollmentRegistrationEntryActionTypes.TRACKER_PROGRAM_REGISTRATION_ENTRY_INITIALISATION_START), pluck('payload'), @@ -45,6 +49,7 @@ export const startNewEnrollmentDataEntrySelfInitialisationEpic = (action$: Input firstStage, programCategory, formFoundation, + querySingleResource, }); return from(openEnrollmentPromise); diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/newEventDataEntry.epics.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/newEventDataEntry.epics.js index 0da25fbb44..eebbad9cd5 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/newEventDataEntry.epics.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/newEventDataEntry.epics.js @@ -1,6 +1,7 @@ // @flow import { ofType } from 'redux-observable'; -import { map, filter } from 'rxjs/operators'; +import { from } from 'rxjs'; +import { map, filter, switchMap } from 'rxjs/operators'; import { batchActions } from 'redux-batched-actions'; import { type OrgUnit } from '@dhis2/rules-engine-javascript'; import { rulesExecutedPostUpdateField } from '../../../../../DataEntry/actions/dataEntry.actions'; @@ -37,6 +38,8 @@ import { actionTypes as crossPageActionTypes } from '../../../../../Pages/action import { lockedSelectorActionTypes } from '../../../../../LockedSelector/LockedSelector.actions'; import { newPageActionTypes } from '../../../../../Pages/New/NewPage.actions'; import { programCollection } from '../../../../../../metaDataMemoryStores'; +import { validateAssignEffects } from '../../../../../D2Form'; +import type { QuerySingleResource } from '../../../../../../utils/api'; export const resetDataEntryForNewEventEpic = (action$: InputObservable) => action$.pipe( @@ -121,14 +124,23 @@ export const resetRecentlyAddedEventsWhenNewEventInDataEntryEpic = (action$: Inp })); -const runRulesForNewSingleEvent = ( +const runRulesForNewSingleEvent = async ({ + store, + dataEntryId, + itemId, + uid, + orgUnit, + fieldData, + querySingleResource, +}: { store: ReduxStore, dataEntryId: string, itemId: string, uid: string, orgUnit: OrgUnit, fieldData?: ?FieldData, -) => { + querySingleResource: QuerySingleResource, +}) => { const state = store.value; const formId = getDataEntryKey(dataEntryId, itemId); @@ -147,35 +159,66 @@ const runRulesForNewSingleEvent = ( currentEvent, }); + const effectsWithValidations = await validateAssignEffects({ + dataElements: foundation.getElements(), + effects, + querySingleResource, + }); + return batchActions([ - updateRulesEffects(effects, formId), + updateRulesEffects(effectsWithValidations, formId), rulesExecutedPostUpdateField(dataEntryId, itemId, uid), ], batchActionTypes.RULES_EFFECTS_ACTIONS_BATCH, ); }; -export const runRulesOnUpdateDataEntryFieldForSingleEventEpic = (action$: InputObservable, store: ReduxStore) => +export const runRulesOnUpdateDataEntryFieldForSingleEventEpic = ( + action$: InputObservable, + store: ReduxStore, + { querySingleResource }: ApiUtils, +) => action$.pipe( ofType(batchActionTypes.UPDATE_DATA_ENTRY_FIELD_NEW_SINGLE_EVENT_ACTION_BATCH), map(actionBatch => actionBatch.payload.find(action => action.type === newEventDataEntryActionTypes.START_RUN_RULES_ON_UPDATE)), - map((action) => { + switchMap((action) => { const { dataEntryId, itemId, uid, orgUnit } = action.payload; - return runRulesForNewSingleEvent(store, dataEntryId, itemId, uid, orgUnit); + const runRulesForNewSingleEventPromise = runRulesForNewSingleEvent({ + store, + dataEntryId, + itemId, + uid, + orgUnit, + querySingleResource, + }); + return from(runRulesForNewSingleEventPromise); })); -export const runRulesOnUpdateFieldForSingleEventEpic = (action$: InputObservable, store: ReduxStore) => +export const runRulesOnUpdateFieldForSingleEventEpic = ( + action$: InputObservable, + store: ReduxStore, + { querySingleResource }: ApiUtils, +) => action$.pipe( ofType(batchActionTypes.UPDATE_FIELD_NEW_SINGLE_EVENT_ACTION_BATCH), map(actionBatch => actionBatch.payload.find(action => action.type === newEventDataEntryActionTypes.START_RUN_RULES_ON_UPDATE)), - map((action) => { + switchMap((action) => { const { dataEntryId, itemId, uid, orgUnit, elementId, value, uiState } = action.payload; const fieldData: FieldData = { elementId, value, valid: uiState.valid, }; - return runRulesForNewSingleEvent(store, dataEntryId, itemId, uid, orgUnit, fieldData); + const runRulesForNewSingleEventPromise = runRulesForNewSingleEvent({ + store, + dataEntryId, + itemId, + uid, + orgUnit, + fieldData, + querySingleResource, + }); + return from(runRulesForNewSingleEventPromise); })); diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/helpers/getRulesActions.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/helpers/getRulesActions.js index ce36f91b0d..d129ca4d24 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/helpers/getRulesActions.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/helpers/getRulesActions.js @@ -8,17 +8,21 @@ import { } from '../../../../../../rules'; import type { RenderFoundation, EventProgram } from '../../../../../../metaData'; import { dataEntryId, itemId, formId } from './constants'; +import { validateAssignEffects } from '../../../../../D2Form'; +import type { QuerySingleResource } from '../../../../../../utils/api'; -export const getRulesActions = ({ +export const getRulesActions = async ({ state, // temporary program, formFoundation, orgUnit, + querySingleResource, }: { state: ReduxState, program: EventProgram, formFoundation: RenderFoundation, orgUnit: OrgUnit, + querySingleResource: QuerySingleResource, }) => { const formValuesClient = getCurrentClientValues(state, formFoundation, formId); const dataEntryValuesClient = getCurrentClientMainData(state, itemId, dataEntryId, formFoundation); @@ -30,5 +34,11 @@ export const getRulesActions = ({ currentEvent, }); - return updateRulesEffects(effects, formId); + const effectsWithValidations = await validateAssignEffects({ + dataElements: formFoundation.getElements(), + effects, + querySingleResource, + }); + + return updateRulesEffects(effectsWithValidations, formId); }; diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/useRulesEngine.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/useRulesEngine.js index 61e646ec29..f59400ebcc 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/useRulesEngine.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/useRulesEngine.js @@ -1,6 +1,8 @@ // @flow import { useEffect, useRef, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import { useDataEngine } from '@dhis2/app-runtime'; +import { makeQuerySingleResource } from 'capture-core/utils/api'; import { batchActions } from 'redux-batched-actions'; import type { OrgUnit } from '@dhis2/rules-engine-javascript'; import { getEventProgramThrowIfNotFound } from '../../../../metaData'; @@ -16,6 +18,7 @@ export const useRulesEngine = ({ orgUnit: ?OrgUnit, formFoundation: ?RenderFoundation, }) => { + const dataEngine = useDataEngine(); const dispatch = useDispatch(); const program = useMemo(() => programId && getEventProgramThrowIfNotFound(programId), [programId]); const orgUnitRef = useRef(); @@ -26,14 +29,14 @@ export const useRulesEngine = ({ const state = useSelector(stateArg => stateArg); useEffect(() => { if (orgUnit && program && !!formFoundation) { - dispatch(batchActions([ - getRulesActions({ - state, - program, - orgUnit, - formFoundation, - }), - ])); + const querySingleResource = makeQuerySingleResource(dataEngine.query.bind(dataEngine)); + getRulesActions({ + state, + program, + orgUnit, + formFoundation, + querySingleResource, + }).then(rulesActions => dispatch(batchActions([rulesActions]))); orgUnitRef.current = orgUnit; } // Ignoring state (due to various reasons, bottom line being that field updates are handled in epic) diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/epics/dataEntryRules.epics.js b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/epics/dataEntryRules.epics.js index 73e0160c03..fe7a69ca2b 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/epics/dataEntryRules.epics.js +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/epics/dataEntryRules.epics.js @@ -1,6 +1,7 @@ // @flow import { ofType } from 'redux-observable'; -import { map } from 'rxjs/operators'; +import { map, switchMap } from 'rxjs/operators'; +import { from } from 'rxjs'; import { batchActions } from 'redux-batched-actions'; import type { OrgUnit } from '@dhis2/rules-engine-javascript'; import i18n from '@dhis2/d2-i18n'; @@ -20,17 +21,29 @@ import { import { getDataEntryKey } from '../../../DataEntry/common/getDataEntryKey'; import type { RulesExecutionDependenciesClientFormatted } from '../../common.types'; import { getLocationQuery } from '../../../../utils/routing'; +import { validateAssignEffects } from '../../../D2Form'; +import type { QuerySingleResource } from '../../../../utils/api'; -const runRulesForNewEvent = ( +const runRulesForNewEvent = async ({ + store, + dataEntryId, + itemId, + uid, + orgUnit, + rulesExecutionDependenciesClientFormatted, + fieldData, + querySingleResource, +}: { store: ReduxStore, dataEntryId: string, itemId: string, uid: string, orgUnit: OrgUnit, - history: Object, - { events, attributeValues, enrollmentData }: RulesExecutionDependenciesClientFormatted, + rulesExecutionDependenciesClientFormatted: RulesExecutionDependenciesClientFormatted, fieldData?: ?FieldData, -) => { + querySingleResource: QuerySingleResource, +}) => { + const { events, attributeValues, enrollmentData } = rulesExecutionDependenciesClientFormatted; const state = store.value; const formId = getDataEntryKey(dataEntryId, itemId); const { programId, stageId } = getLocationQuery(); @@ -47,7 +60,7 @@ const runRulesForNewEvent = ( const currentEventMainData = getCurrentClientMainData(state, itemId, dataEntryId, foundation); const currentEvent = { ...currentEventValues, ...currentEventMainData, programStageId }; - const ruleEffects = getApplicableRuleEffectsForTrackerProgram({ + const effects = getApplicableRuleEffectsForTrackerProgram({ program, stage, orgUnit, @@ -57,40 +70,55 @@ const runRulesForNewEvent = ( enrollmentData, }); + const effectsWithValidations = await validateAssignEffects({ + dataElements: foundation.getElements(), + effects, + querySingleResource, + }); + return batchActions([ - updateRulesEffects(ruleEffects, formId), + updateRulesEffects(effectsWithValidations, formId), rulesExecutedPostUpdateField(dataEntryId, itemId, uid), ], newEventWidgetDataEntryBatchActionTypes.RULES_EFFECTS_ACTIONS_BATCH, ); }; -export const runRulesOnUpdateDataEntryFieldForNewEnrollmentEventEpic = (action$: InputObservable, store: ReduxStore, { history }: ApiUtils) => +export const runRulesOnUpdateDataEntryFieldForNewEnrollmentEventEpic = ( + action$: InputObservable, + store: ReduxStore, + { querySingleResource }: ApiUtils, +) => action$.pipe( ofType(newEventWidgetDataEntryBatchActionTypes.UPDATE_DATA_ENTRY_FIELD_ADD_EVENT_ACTION_BATCH), map(actionBatch => actionBatch.payload .find(action => action.type === newEventWidgetDataEntryActionTypes.RULES_ON_UPDATE_EXECUTE)), - map((action) => { + switchMap((action) => { const { dataEntryId, itemId, uid, orgUnit, rulesExecutionDependenciesClientFormatted } = action.payload; - return runRulesForNewEvent( + const runRulesForNewEventPromise = runRulesForNewEvent({ store, dataEntryId, itemId, uid, orgUnit, - history, rulesExecutionDependenciesClientFormatted, - ); + querySingleResource, + }); + return from(runRulesForNewEventPromise); })); -export const runRulesOnUpdateFieldForNewEnrollmentEventEpic = (action$: InputObservable, store: ReduxStore, { history }: ApiUtils) => +export const runRulesOnUpdateFieldForNewEnrollmentEventEpic = ( + action$: InputObservable, + store: ReduxStore, + { querySingleResource }: ApiUtils, +) => action$.pipe( ofType(newEventWidgetDataEntryBatchActionTypes.FIELD_UPDATE_BATCH), map(actionBatch => actionBatch.payload .find(action => action.type === newEventWidgetDataEntryActionTypes.RULES_ON_UPDATE_EXECUTE)), - map((action) => { + switchMap((action) => { const { dataEntryId, itemId, @@ -107,14 +135,16 @@ export const runRulesOnUpdateFieldForNewEnrollmentEventEpic = (action$: InputObs value, valid: uiState.valid, }; - return runRulesForNewEvent( + + const runRulesForNewEventPromise = runRulesForNewEvent({ store, dataEntryId, itemId, uid, orgUnit, - history, rulesExecutionDependenciesClientFormatted, fieldData, - ); + querySingleResource, + }); + return from(runRulesForNewEventPromise); })); diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/helpers/getRulesActions.js b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/helpers/getRulesActions.js index d8aaa50247..7fd5123258 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/helpers/getRulesActions.js +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/helpers/getRulesActions.js @@ -9,8 +9,10 @@ import { } from '../../../../rules'; import type { RenderFoundation, TrackerProgram, ProgramStage } from '../../../../metaData'; import type { EnrollmentEvents, AttributeValuesClientFormatted, EnrollmentData } from '../../common.types'; +import { validateAssignEffects } from '../../../D2Form'; +import type { QuerySingleResource } from '../../../../utils/api'; -export const getRulesActions = ({ +export const getRulesActions = async ({ state, // temporary program, stage, @@ -21,6 +23,7 @@ export const getRulesActions = ({ eventsRulesDependency, attributesValuesRulesDependency, enrollmentDataRulesDependency, + querySingleResource, }: { state: ReduxState, program: TrackerProgram, @@ -32,6 +35,7 @@ export const getRulesActions = ({ eventsRulesDependency: EnrollmentEvents, attributesValuesRulesDependency: AttributeValuesClientFormatted, enrollmentDataRulesDependency: EnrollmentData, + querySingleResource: QuerySingleResource, }) => { const formId = getDataEntryKey(dataEntryId, itemId); @@ -49,5 +53,11 @@ export const getRulesActions = ({ enrollmentData: enrollmentDataRulesDependency, }); - return updateRulesEffects(effects, formId); + const effectsWithValidations = await validateAssignEffects({ + dataElements: formFoundation.getElements(), + effects, + querySingleResource, + }); + + return updateRulesEffects(effectsWithValidations, formId); }; diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/useLifecycle.js b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/useLifecycle.js index bf41bf3e53..6fcd31e0e3 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/useLifecycle.js +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/useLifecycle.js @@ -1,6 +1,8 @@ // @flow import { useEffect, useRef, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import { useDataEngine } from '@dhis2/app-runtime'; +import { makeQuerySingleResource } from 'capture-core/utils/api'; import { batchActions } from 'redux-batched-actions'; import type { OrgUnit } from '@dhis2/rules-engine-javascript'; import { getOpenDataEntryActions, getRulesActions } from '../DataEntry'; @@ -29,6 +31,7 @@ export const useLifecycle = ({ itemId: string, rulesExecutionDependenciesClientFormatted: RulesExecutionDependenciesClientFormatted, }) => { + const dataEngine = useDataEngine(); const dispatch = useDispatch(); const [rulesExecutionTrigger, setRulesExecutionTrigger] = useState(1); @@ -62,20 +65,21 @@ export const useLifecycle = ({ delayRulesExecutionRef.current = false; setRulesExecutionTrigger(-rulesExecutionTrigger); } else { - dispatch(batchActions([ - getRulesActions({ - state, - program, - stage, - formFoundation, - dataEntryId, - itemId, - orgUnit, - eventsRulesDependency, - attributesValuesRulesDependency, - enrollmentDataRulesDependency, - }), - ])); + const querySingleResource = makeQuerySingleResource(dataEngine.query.bind(dataEngine)); + + getRulesActions({ + state, + program, + stage, + formFoundation, + dataEntryId, + itemId, + orgUnit, + eventsRulesDependency, + attributesValuesRulesDependency, + enrollmentDataRulesDependency, + querySingleResource, + }).then(rulesActions => dispatch(batchActions([rulesActions]))); eventsRef.current = eventsRulesDependency; attributesRef.current = attributesValuesRulesDependency; enrollmentDataRef.current = enrollmentDataRulesDependency; diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/editEventDataEntry.actions.js b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/editEventDataEntry.actions.js index 503a47524d..f39254fc87 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/editEventDataEntry.actions.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/editEventDataEntry.actions.js @@ -28,6 +28,8 @@ import { getEnrollmentForRulesEngine, getAttributeValuesForRulesEngine } from '. import type { EnrollmentData, AttributeValue } from '../../Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData'; import { prepareEnrollmentEventsForRulesEngine } from '../../../events/prepareEnrollmentEvents'; import type { ProgramCategory } from '../../WidgetEventSchedule/CategoryOptions/CategoryOptions.types'; +import { validateAssignEffects } from '../../D2Form'; +import type { QuerySingleResource } from '../../../utils/api'; export const batchActionTypes = { UPDATE_DATA_ENTRY_FIELD_EDIT_SINGLE_EVENT_ACTION_BATCH: 'UpdateDataEntryFieldForEditSingleEventActionsBatch', @@ -70,7 +72,7 @@ function getLoadActions( ]; } -export const openEventForEditInDataEntry = ({ +export const openEventForEditInDataEntry = async ({ loadedValues: { eventContainer, dataEntryValues, @@ -84,6 +86,7 @@ export const openEventForEditInDataEntry = ({ dataEntryId, dataEntryKey, programCategory, + querySingleResource, }: { loadedValues: { eventContainer: Object, @@ -97,7 +100,8 @@ export const openEventForEditInDataEntry = ({ dataEntryKey: string, enrollment?: EnrollmentData, attributeValues?: Array, - programCategory?: ProgramCategory + programCategory?: ProgramCategory, + querySingleResource: QuerySingleResource, }) => { const dataEntryPropsToInclude = [ { @@ -146,13 +150,13 @@ export const openEventForEditInDataEntry = ({ }, ); const currentEvent = { ...eventContainer.event, ...eventContainer.values }; + const stage = getStageFromEvent(eventContainer.event)?.stage; + if (!stage) { + throw Error(i18n.t('stage not found in rules execution')); + } - let effects; + let effects: Object = {}; if (program instanceof TrackerProgram) { - const stage = getStageFromEvent(eventContainer.event)?.stage; - if (!stage) { - throw Error(i18n.t('stage not found in rules execution')); - } // TODO: Add attributeValues & enrollmentData effects = getApplicableRuleEffectsForTrackerProgram({ program, @@ -173,9 +177,15 @@ export const openEventForEditInDataEntry = ({ }); } + const effectsWithValidations = await validateAssignEffects({ + dataElements: stage.stageForm.getElements(), + effects, + querySingleResource, + }); + return [ ...dataEntryActions, - updateRulesEffects(effects, formId), + updateRulesEffects(effectsWithValidations, formId), actionCreator(actionTypes.OPEN_EVENT_FOR_EDIT_IN_DATA_ENTRY)(), ]; }; diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/epics/editEventDataEntry.epics.js b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/epics/editEventDataEntry.epics.js index 69d7a4ab24..101e95b615 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/epics/editEventDataEntry.epics.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/epics/editEventDataEntry.epics.js @@ -1,7 +1,8 @@ // @flow import i18n from '@dhis2/d2-i18n'; +import { from } from 'rxjs'; import { ofType } from 'redux-observable'; -import { map } from 'rxjs/operators'; +import { map, switchMap } from 'rxjs/operators'; import { batchActions } from 'redux-batched-actions'; import type { OrgUnit } from '@dhis2/rules-engine-javascript'; import { rulesExecutedPostUpdateField } from '../../../DataEntry/actions/dataEntry.actions'; @@ -23,8 +24,10 @@ import { EventProgram, TrackerProgram } from '../../../../metaData/Program'; import { getDataEntryKey } from '../../../DataEntry/common/getDataEntryKey'; import { prepareEnrollmentEventsForRulesEngine } from '../../../../events/prepareEnrollmentEvents'; import { getEnrollmentForRulesEngine, getAttributeValuesForRulesEngine } from '../../helpers'; +import { validateAssignEffects } from '../../../D2Form'; +import type { QuerySingleResource } from '../../../../utils/api'; -const runRulesForEditSingleEvent = ({ +const runRulesForEditSingleEvent = async ({ store, dataEntryId, itemId, @@ -32,6 +35,7 @@ const runRulesForEditSingleEvent = ({ orgUnit, fieldData, programId, + querySingleResource, }: { store: ReduxStore, dataEntryId: string, @@ -40,6 +44,7 @@ const runRulesForEditSingleEvent = ({ programId: string, orgUnit: OrgUnit, fieldData?: ?FieldData, + querySingleResource: QuerySingleResource }) => { const state = store.value; const formId = getDataEntryKey(dataEntryId, itemId); @@ -84,33 +89,56 @@ const runRulesForEditSingleEvent = ({ }); } + const effectsWithValidations = await validateAssignEffects({ + dataElements: foundation.getElements(), + effects, + querySingleResource, + }); + return batchActions([ - updateRulesEffects(effects, formId), + updateRulesEffects(effectsWithValidations, formId), rulesExecutedPostUpdateField(dataEntryId, itemId, uid), ], editEventDataEntryBatchActionTypes.RULES_EFFECTS_ACTIONS_BATCH); }; -export const runRulesOnUpdateDataEntryFieldForEditSingleEventEpic = (action$: InputObservable, store: ReduxStore) => +export const runRulesOnUpdateDataEntryFieldForEditSingleEventEpic = ( + action$: InputObservable, + store: ReduxStore, + { querySingleResource }: ApiUtils, +) => // $FlowSuppress action$.pipe( ofType(editEventDataEntryBatchActionTypes.UPDATE_DATA_ENTRY_FIELD_EDIT_SINGLE_EVENT_ACTION_BATCH), map(actionBatch => actionBatch.payload.find(action => action.type === editEventDataEntryActionTypes.START_RUN_RULES_ON_UPDATE), ), - map((action) => { + switchMap((action) => { const { dataEntryId, itemId, uid, orgUnit, programId } = action.payload; - return runRulesForEditSingleEvent({ store, dataEntryId, itemId, uid, orgUnit, programId }); + const runRulesForEditSingleEventPromise = runRulesForEditSingleEvent({ + store, + dataEntryId, + itemId, + uid, + orgUnit, + programId, + querySingleResource, + }); + return from(runRulesForEditSingleEventPromise); })); -export const runRulesOnUpdateFieldForEditSingleEventEpic = (action$: InputObservable, store: ReduxStore) => +export const runRulesOnUpdateFieldForEditSingleEventEpic = ( + action$: InputObservable, + store: ReduxStore, + { querySingleResource }: ApiUtils, +) => // $FlowSuppress action$.pipe( ofType(editEventDataEntryBatchActionTypes.UPDATE_FIELD_EDIT_SINGLE_EVENT_ACTION_BATCH), map(actionBatch => actionBatch.payload.find(action => action.type === editEventDataEntryActionTypes.START_RUN_RULES_ON_UPDATE), ), - map((action) => { + switchMap((action) => { const { elementId, value, @@ -126,7 +154,7 @@ export const runRulesOnUpdateFieldForEditSingleEventEpic = (action$: InputObserv value, valid: uiState.valid, }; - return runRulesForEditSingleEvent({ + const runRulesForEditSingleEventPromise = runRulesForEditSingleEvent({ store, dataEntryId, itemId, @@ -134,6 +162,8 @@ export const runRulesOnUpdateFieldForEditSingleEventEpic = (action$: InputObserv orgUnit, fieldData, programId, + querySingleResource, }); + return from(runRulesForEditSingleEventPromise); })); diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/editEventDataEntry.epics.js b/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/editEventDataEntry.epics.js index 217e656bf4..5e5524f645 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/editEventDataEntry.epics.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/editEventDataEntry.epics.js @@ -1,6 +1,6 @@ // @flow import { ofType } from 'redux-observable'; -import { map, filter, flatMap } from 'rxjs/operators'; +import { map, filter, flatMap, switchMap } from 'rxjs/operators'; import { batchActions } from 'redux-batched-actions'; import { dataEntryKeys, dataEntryIds } from 'capture-core/constants'; import moment from 'moment'; @@ -45,16 +45,17 @@ const getDataEntryId = (event): string => ( : dataEntryIds.SINGLE_EVENT ); -export const loadEditEventDataEntryEpic = (action$: InputObservable, store: ReduxStore) => +export const loadEditEventDataEntryEpic = (action$: InputObservable, store: ReduxStore, { querySingleResource }: ApiUtils) => action$.pipe( ofType(eventDetailsActionTypes.START_SHOW_EDIT_EVENT_DATA_ENTRY, widgetEventEditActionTypes.START_SHOW_EDIT_EVENT_DATA_ENTRY), - map((action) => { + switchMap((action) => { const state = store.value; const loadedValues = state.viewEventPage.loadedValues; const eventContainer = loadedValues.eventContainer; const metadataContainer = getProgramAndStageFromEvent(eventContainer.event); if (metadataContainer.error) { - return prerequisitesErrorLoadingEditEventDataEntry(metadataContainer.error); + prerequisitesErrorLoadingEditEventDataEntry(metadataContainer.error); + return EMPTY; } const program = metadataContainer.program; @@ -62,20 +63,18 @@ export const loadEditEventDataEntryEpic = (action$: InputObservable, store: Redu const { orgUnit, programCategory } = action.payload; const { enrollment, attributeValues } = state.enrollmentDomain; - return batchActions([ - showEditEventDataEntry(), - ...openEventForEditInDataEntry({ - loadedValues, - orgUnit, - foundation, - program, - enrollment, - attributeValues, - dataEntryId: getDataEntryId(eventContainer.event), - dataEntryKey: dataEntryKeys.EDIT, - programCategory, - }), - ]); + return openEventForEditInDataEntry({ + loadedValues, + orgUnit, + foundation, + program, + enrollment, + attributeValues, + dataEntryId: getDataEntryId(eventContainer.event), + dataEntryKey: dataEntryKeys.EDIT, + programCategory, + querySingleResource, + }).then(actions => batchActions([showEditEventDataEntry(), ...actions])); })); export const saveEditedEventEpic = (action$: InputObservable, store: ReduxStore, { serverVersion: { minor } }: ApiUtils) => diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.js index 34a6eba39f..19a2d2dc28 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.js @@ -1,6 +1,8 @@ // @flow import React, { useState, useCallback } from 'react'; import { useDispatch } from 'react-redux'; +import { useDataEngine } from '@dhis2/app-runtime'; +import { makeQuerySingleResource } from 'capture-core/utils/api'; import type { Props } from './dataEntry.types'; import { DataEntryComponent } from './DataEntry.component'; import { useLifecycle, useFormValidations } from './hooks'; @@ -24,9 +26,21 @@ export const DataEntry = ({ }: Props) => { const dataEntryId = 'trackedEntityProfile'; const itemId = 'edit'; + const dataEngine = useDataEngine(); + const querySingleResource = makeQuerySingleResource(dataEngine.query.bind(dataEngine)); const dispatch = useDispatch(); const [saveAttempted, setSaveAttempted] = useState(false); + const onGetValidationContext = useCallback( + () => ({ + programId: programAPI.id, + orgUnitId, + trackedEntityInstanceId, + trackedEntityTypeId: programAPI.trackedEntityType.id, + }), + [programAPI, orgUnitId, trackedEntityInstanceId], + ); + const context = useLifecycle({ programAPI, orgUnitId, @@ -36,13 +50,18 @@ export const DataEntry = ({ itemId, geometry, dataEntryFormConfig, + onGetValidationContext, }); const { formFoundation } = context; const { formValidated, errorsMessages, warningsMessages } = useFormValidations(dataEntryId, itemId, saveAttempted); const onUpdateFormField = useCallback( - (...args: Array) => dispatch(getUpdateFieldActions(context, ...args)), - [dispatch, context], + (...args: Array) => { + getUpdateFieldActions(context, querySingleResource, onGetValidationContext, ...args).then(actions => + dispatch(actions), + ); + }, + [dispatch, querySingleResource, context, onGetValidationContext], ); const onUpdateFormFieldAsync = useCallback( (innerAction: ReduxAction) => { @@ -50,15 +69,6 @@ export const DataEntry = ({ }, [dispatch], ); - const getValidationContext = useCallback( - () => ({ - programId: programAPI.id, - orgUnitId, - trackedEntityInstanceId, - trackedEntityTypeId: programAPI.trackedEntityType.id, - }), - [programAPI, orgUnitId, trackedEntityInstanceId], - ); const onSave = useCallback(() => { setSaveAttempted(true); @@ -107,7 +117,7 @@ export const DataEntry = ({ onUpdateFormField={onUpdateFormField} onUpdateFormFieldAsync={onUpdateFormFieldAsync} modalState={modalState} - onGetValidationContext={getValidationContext} + onGetValidationContext={onGetValidationContext} errorsMessages={errorsMessages} warningsMessages={warningsMessages} orgUnit={{ id: orgUnitId }} diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.js index 74aa721f08..a781f92ca2 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.js @@ -13,6 +13,8 @@ import type { import { rulesEngine } from '../../../../rules/rulesEngine'; import type { RenderFoundation } from '../../../../metaData'; import { updateRulesEffects, postProcessRulesEffects, buildEffectsHierarchy } from '../../../../rules'; +import type { QuerySingleResource } from '../../../../utils/api'; +import { validateAssignEffects } from '../../../D2Form'; const getEnrollmentForRulesExecution = enrollment => enrollment && { @@ -36,12 +38,30 @@ const getDataElementsForRulesExecution = (dataElements: ?DataElements) => {}, ); -const getRulesActions = (rulesEffects: OutputEffects, foundation: RenderFoundation, formId: string) => { - const effectsHierarchy = buildEffectsHierarchy(postProcessRulesEffects(rulesEffects, foundation)); - return [updateRulesEffects(effectsHierarchy, formId)]; +const getRulesActions = async ({ + effects, + foundation, + formId, + querySingleResource, + onGetValidationContext, +}: { + effects: OutputEffects, + foundation: RenderFoundation, + formId: string, + querySingleResource: QuerySingleResource, + onGetValidationContext: () => Object, +}) => { + const effectsHierarchy = buildEffectsHierarchy(postProcessRulesEffects(effects, foundation)); + const effectsWithValidations = await validateAssignEffects({ + dataElements: foundation.getElements(), + effects: effectsHierarchy, + querySingleResource, + onGetValidationContext, + }); + return updateRulesEffects(effectsWithValidations, formId); }; -export const getRulesActionsForTEI = ({ +export const getRulesActionsForTEI = async ({ foundation, formId, orgUnit, @@ -53,6 +73,8 @@ export const getRulesActionsForTEI = ({ otherEvents, dataElements, userRoles, + querySingleResource, + onGetValidationContext, }: { foundation: RenderFoundation, formId: string, @@ -65,6 +87,8 @@ export const getRulesActionsForTEI = ({ otherEvents?: ?EventsData, dataElements: ?DataElements, userRoles: Array, + querySingleResource: QuerySingleResource, + onGetValidationContext: () => Object, }) => { const effects: OutputEffects = rulesEngine.getProgramRuleEffects({ programRulesContainer: rulesContainer, @@ -78,5 +102,5 @@ export const getRulesActionsForTEI = ({ selectedUserRoles: userRoles, optionSets, }); - return getRulesActions(effects, foundation, formId); + return getRulesActions({ effects, foundation, formId, querySingleResource, onGetValidationContext }); }; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.js index 331f6e1a4d..ff41dbb77a 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.js @@ -22,6 +22,7 @@ import { startRunRulesPostUpdateField } from '../../DataEntry'; import { getRulesActionsForTEI } from './ProgramRules'; import { addFormData } from '../../D2Form/actions/form.actions'; import type { Geometry } from './helpers/types'; +import type { QuerySingleResource } from '../../../utils/api'; export const TEI_MODAL_STATE = { OPEN: 'Open', @@ -65,7 +66,12 @@ type Context = { state: ReduxState, }; -export const getUpdateFieldActions = (context: Context, innerAction: ReduxAction) => { +export const getUpdateFieldActions = async ( + context: Context, + querySingleResource: QuerySingleResource, + onGetValidationContext: () => Object, + innerAction: ReduxAction, +) => { const uid = uuid(); const { orgUnit, @@ -87,7 +93,7 @@ export const getUpdateFieldActions = (context: Context, innerAction: ReduxAction }; const formId = `${dataEntryId}-${itemId}`; const currentTEIValues = getCurrentClientValues(state, formFoundation, formId, fieldData); - const rulesActions = getRulesActionsForTEI({ + const rulesActions = await getRulesActionsForTEI({ foundation: formFoundation, formId, orgUnit, @@ -99,12 +105,14 @@ export const getUpdateFieldActions = (context: Context, innerAction: ReduxAction otherEvents, dataElements, userRoles, + querySingleResource, + onGetValidationContext, }); return batchActions( [ innerAction, - ...rulesActions, + rulesActions, rulesExecutedPostUpdateField(dataEntryId, itemId, uid), startRunRulesPostUpdateField(dataEntryId, itemId, uid), ], diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.js index 0847eda23d..b572b809f6 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.js @@ -1,6 +1,8 @@ // @flow import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import { useDataEngine } from '@dhis2/app-runtime'; +import { makeQuerySingleResource } from 'capture-core/utils/api'; import { useOrganisationUnit } from 'capture-core/dataQueries/useOrganisationUnit'; import type { OrgUnit, @@ -35,6 +37,7 @@ export const useLifecycle = ({ itemId, geometry, dataEntryFormConfig, + onGetValidationContext, }: { programAPI: any, orgUnitId: string, @@ -44,7 +47,9 @@ export const useLifecycle = ({ itemId: string, geometry: ?Geometry, dataEntryFormConfig: ?DataEntryFormConfig, + onGetValidationContext: () => Object, }) => { + const dataEngine = useDataEngine(); const dispatch = useDispatch(); // TODO: Getting the entire state object is bad and this needs to be refactored. // The problem is the helper methods that take the entire state object. @@ -88,21 +93,22 @@ export const useLifecycle = ({ Object.entries(clientValues).length > 0 && Object.entries(rulesContainer).length > 0 ) { - dispatch( - ...getRulesActionsForTEI({ - foundation: formFoundation, - formId: `${dataEntryId}-${itemId}`, - orgUnit, - trackedEntityAttributes: programTrackedEntityAttributes, - teiValues: { ...clientValues, ...clientGeometryValues }, - optionSets, - rulesContainer, - otherEvents, - dataElements, - enrollmentData: enrollment, - userRoles, - }), - ); + const querySingleResource = makeQuerySingleResource(dataEngine.query.bind(dataEngine)); + getRulesActionsForTEI({ + foundation: formFoundation, + formId: `${dataEntryId}-${itemId}`, + orgUnit, + trackedEntityAttributes: programTrackedEntityAttributes, + teiValues: { ...clientValues, ...clientGeometryValues }, + optionSets, + rulesContainer, + otherEvents, + dataElements, + enrollmentData: enrollment, + userRoles, + querySingleResource, + onGetValidationContext, + }).then(rulesActions => dispatch(rulesActions)); } }, [ dispatch, @@ -120,6 +126,8 @@ export const useLifecycle = ({ enrollment, clientGeometryValues, userRoles, + dataEngine, + onGetValidationContext, ]); return { diff --git a/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js index 30dd29a15f..69fbb85621 100644 --- a/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js @@ -1,6 +1,6 @@ // @flow import { effectActions } from '@dhis2/rules-engine-javascript'; -import type { AssignOutputEffect } from '@dhis2/rules-engine-javascript'; +import type { AssignOutputEffectWithValidations } from '../../components/D2Form'; import { createReducerDescription } from '../../trackerRedux'; import { asyncHandlerActionTypes } from '../../components/D2Form'; import { actionTypes as fieldActionTypes } from '../../components/D2Form/D2SectionFields.actions'; @@ -63,7 +63,7 @@ export const formsValuesDesc = createReducerDescription({ return newState; }, [rulesEffectsActionTypes.UPDATE_RULES_EFFECTS]: (state, action) => { - const assignEffects: { [id: string]: Array } = + const assignEffects: { [id: string]: Array } = action.payload.rulesEffects && action.payload.rulesEffects[effectActions.ASSIGN_VALUE]; if (!assignEffects) { return state; @@ -205,26 +205,25 @@ export const formsSectionsFieldsUIDesc = createReducerDescription({ [newPageActionTypes.CLEAN_UP_DATA_ENTRY]: cleanUp, [rulesEffectsActionTypes.UPDATE_RULES_EFFECTS]: (state, action) => { const { formId, rulesEffects } = action.payload; - const formSectionFields = state[formId]; - const assignEffects: { [id: string]: Array } = + const assignEffects: { [id: string]: Array } = rulesEffects && rulesEffects[effectActions.ASSIGN_VALUE]; - if (!assignEffects || !formSectionFields) { + if (!assignEffects) { return state; } const updatedFields = Object.keys(assignEffects).reduce((acc, id) => { - if (formSectionFields[id]) { - acc[id] = { - valid: true, - errorData: undefined, - errorMessage: undefined, - errorType: undefined, - modified: true, - touched: true, - validatingMessage: null, - }; - } + const effectsForId = assignEffects[id]; + const effect = effectsForId[effectsForId.length - 1]; + acc[id] = { + valid: effect.valid, + errorData: effect.errorData, + errorMessage: effect.errorMessage, + errorType: effect.errorType, + modified: true, + touched: true, + validatingMessage: null, + }; return acc; }, {}); From 3e8a59d4ba96ef974aae44234d1d01d3db5ca107 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Mon, 2 Sep 2024 12:23:03 +0200 Subject: [PATCH 02/22] fix: validate the assigned values from rules engine --- .../reducers/descriptions/form.reducerDescription.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js index 69fbb85621..41f26d8cf3 100644 --- a/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js @@ -220,7 +220,6 @@ export const formsSectionsFieldsUIDesc = createReducerDescription({ errorData: effect.errorData, errorMessage: effect.errorMessage, errorType: effect.errorType, - modified: true, touched: true, validatingMessage: null, }; From dd8f672d54d1e995d65d70f9fb609c06ec76548e Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Tue, 3 Sep 2024 09:59:30 +0200 Subject: [PATCH 03/22] fix: validate the assigned values from rules engine --- .../field/validators/validateAssignEffects.js | 50 ++++++++++--------- .../Enrollment/epics/enrollment.epics.js | 3 +- .../DataEntry/DataEntry.container.js | 4 +- .../DataEntry/dataEntry.actions.js | 9 +++- .../DataEntry/dataEntry.types.js | 2 +- 5 files changed, 39 insertions(+), 29 deletions(-) diff --git a/src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js b/src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js index e1babbeb2b..6a98205c94 100644 --- a/src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js +++ b/src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js @@ -7,8 +7,10 @@ import { type DataElement } from '../../../../metaData'; import type { QuerySingleResource } from '../../../../utils/api'; import { getValidators } from './getValidators'; +type Validations = { valid: boolean, errorMessage?: string, errorType?: string, errorData?: string }; + export type AssignOutputEffectWithValidations = { - [metaDataId: string]: Array, + [metaDataId: string]: Array, }; const getValidatorsResult = async (validators, value, validationContext) => @@ -50,16 +52,20 @@ export const validateAssignEffects = async ({ const assignEffectsWithValidations = await dataElements.reduce(async (passPromise, metaData: DataElement) => { const acc = await passPromise; - if (assignEffects[metaData.id]) { - const effectsForId = assignEffects[metaData.id]; - const lastEffect = effectsForId.length - 1; - const value = effectsForId[lastEffect].value; - const validators = getValidators(metaData, querySingleResource); - const validationContext = onGetValidationContext && onGetValidationContext(); + if (!assignEffects[metaData.id]) { + return acc; + } - try { - const validatorResult = await getValidatorsResult(validators, value, validationContext); - const effectWithValidation = validatorResult === true + const effectsForId = assignEffects[metaData.id]; + const lastEffect = effectsForId.length - 1; + const value = effectsForId[lastEffect].value; + const validators = getValidators(metaData, querySingleResource); + const validationContext = onGetValidationContext && onGetValidationContext(); + + try { + const validatorResult = await getValidatorsResult(validators, value, validationContext); + const effectWithValidation = + validatorResult === true ? { ...effectsForId[lastEffect], valid: true, @@ -72,20 +78,18 @@ export const validateAssignEffects = async ({ errorData: validatorResult.data, }; - acc[metaData.id] = [...effectsForId.slice(0, lastEffect - 1), effectWithValidation]; - return acc; - } catch (error) { - log.error( - errorCreator('an error occured while validating the assigned program rule effect')({ - metaData, - lastEffect, - error, - }), - ); - return acc; - } + acc[metaData.id] = [...effectsForId.slice(0, lastEffect - 1), effectWithValidation]; + return acc; + } catch (error) { + log.error( + errorCreator('an error occured while validating the assigned program rule effect')({ + metaData, + lastEffect, + error, + }), + ); + return acc; } - return acc; }, Promise.resolve({})); return { ...effects, [effectActions.ASSIGN_VALUE]: assignEffectsWithValidations }; diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/epics/enrollment.epics.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/epics/enrollment.epics.js index 881c5aa85d..77ee439a01 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/epics/enrollment.epics.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/epics/enrollment.epics.js @@ -10,6 +10,7 @@ import { getCurrentClientMainData, type FieldData } from '../../../../rules'; import { getDataEntryKey } from '../../../DataEntry/common/getDataEntryKey'; import { convertFormToClient } from '../../../../converters'; import { stageMainDataIds, convertToRulesEngineIds } from '../EnrollmentWithFirstStageDataEntry'; +import type { QuerySingleResource } from '../../../../utils/api'; type Context = { dataEntryId: string, @@ -49,7 +50,7 @@ const runRulesOnEnrollmentUpdate = ({ store: ReduxStore, context: Context, searchActions?: any, - querySingleResource: any, + querySingleResource: QuerySingleResource, onGetValidationContext: () => Object, }) => { const state = store.value; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.js index 19a2d2dc28..cb7dfc9497 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.js @@ -56,8 +56,8 @@ export const DataEntry = ({ const { formValidated, errorsMessages, warningsMessages } = useFormValidations(dataEntryId, itemId, saveAttempted); const onUpdateFormField = useCallback( - (...args: Array) => { - getUpdateFieldActions(context, querySingleResource, onGetValidationContext, ...args).then(actions => + (innerAction: ReduxAction) => { + getUpdateFieldActions({ context, querySingleResource, onGetValidationContext, innerAction }).then(actions => dispatch(actions), ); }, diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.js index ff41dbb77a..92e0415b49 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.js @@ -66,12 +66,17 @@ type Context = { state: ReduxState, }; -export const getUpdateFieldActions = async ( +export const getUpdateFieldActions = async ({ + context, + querySingleResource, + onGetValidationContext, + innerAction, +}: { context: Context, querySingleResource: QuerySingleResource, onGetValidationContext: () => Object, innerAction: ReduxAction, -) => { +}) => { const uid = uuid(); const { orgUnit, diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.types.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.types.js index ddb4f533c9..55a8b937d6 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.types.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.types.js @@ -13,7 +13,7 @@ export type PlainProps = {| formFoundation: any, onCancel: () => void, onSave: () => void, - onUpdateFormField: () => void, + onUpdateFormField: (innerAction: ReduxAction) => void, onUpdateFormFieldAsync: (innerAction: ReduxAction) => void, onGetValidationContext: () => Object, modalState: string, From 5df86674c96209539b67a87ea90c22d084bd6964 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Tue, 3 Sep 2024 10:38:26 +0200 Subject: [PATCH 04/22] chore: small improvements --- .../actions/enrollment.actionBatchs.js | 18 +++++++-------- .../Enrollment/actions/enrollment.actions.js | 23 +++++++++++++++---- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actionBatchs.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actionBatchs.js index 79746d77c2..b1658a1165 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actionBatchs.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actionBatchs.js @@ -90,15 +90,15 @@ export const updateDataEntryFieldBatch = ( [ innerAction, startRunRulesPostUpdateField(dataEntryId, itemId, uid), - startRunRulesOnUpdateForNewEnrollment( - innerAction.payload, + startRunRulesOnUpdateForNewEnrollment({ + payload: innerAction.payload, uid, programId, orgUnit, stage, formFoundation, onGetValidationContext, - ), + }), ], batchActionTypes.UPDATE_DATA_ENTRY_FIELD_NEW_ENROLLMENT_ACTION_BATCH, ); @@ -119,15 +119,15 @@ export const updateFieldBatch = ( [ innerAction, startRunRulesPostUpdateField(dataEntryId, itemId, uid), - startRunRulesOnUpdateForNewEnrollment( - innerAction.payload, + startRunRulesOnUpdateForNewEnrollment({ + payload: innerAction.payload, uid, programId, orgUnit, stage, formFoundation, onGetValidationContext, - ), + }), ], batchActionTypes.UPDATE_FIELD_NEW_ENROLLMENT_ACTION_BATCH, ); @@ -149,15 +149,15 @@ export const asyncUpdateSuccessBatch = ( [ innerAction, startRunRulesPostUpdateField(dataEntryId, itemId, uid), - startRunRulesOnUpdateForNewEnrollment( - { ...innerAction.payload, dataEntryId, itemId }, + startRunRulesOnUpdateForNewEnrollment({ + payload: { ...innerAction.payload, dataEntryId, itemId }, uid, programId, orgUnit, stage, formFoundation, onGetValidationContext, - ), + }), ], batchActionTypes.UPDATE_FIELD_NEW_ENROLLMENT_ACTION_BATCH, ); diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actions.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actions.js index 68dec4ccea..e0e0bbe343 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actions.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actions.js @@ -7,7 +7,15 @@ export const actionTypes = { START_RUN_RULES_ON_UPDATE: 'StartRunRulesOnUpdateForNewEnrollment', }; -export const startRunRulesOnUpdateForNewEnrollment = ( +export const startRunRulesOnUpdateForNewEnrollment = ({ + payload, + uid, + programId, + orgUnit, + stage, + formFoundation, + onGetValidationContext, +}: { payload: Object, uid: string, programId: string, @@ -15,9 +23,16 @@ export const startRunRulesOnUpdateForNewEnrollment = ( stage?: ProgramStage, formFoundation: RenderFoundation, onGetValidationContext: () => Object, -) => - actionCreator(actionTypes.START_RUN_RULES_ON_UPDATE)( - { innerPayload: payload, uid, programId, orgUnit, stage, formFoundation, onGetValidationContext }); +}) => + actionCreator(actionTypes.START_RUN_RULES_ON_UPDATE)({ + innerPayload: payload, + uid, + programId, + orgUnit, + stage, + formFoundation, + onGetValidationContext, + }); export const startAsyncUpdateFieldForNewEnrollment = ( innerAction: ReduxAction, From 71b5fa613196d246ff8831b66f6360566c9e4fb0 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Wed, 4 Sep 2024 09:53:47 +0200 Subject: [PATCH 05/22] feat: extract validateField from FormBuilder into common external function --- .../FormBuilder/FormBuilder.component.js | 66 ++----------------- .../components/D2Form/FormBuilder/index.js | 2 +- .../D2Form/field/configs/base/configBase.js | 2 +- .../D2Form/field/validators/index.js | 3 + .../field/validators/validateAssignEffects.js | 43 ++---------- .../D2Form/field/validators/validateField.js | 61 +++++++++++++++++ 6 files changed, 77 insertions(+), 100 deletions(-) create mode 100644 src/core_modules/capture-core/components/D2Form/field/validators/validateField.js diff --git a/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js b/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js index f62a5c32f0..23d724c3cc 100644 --- a/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js +++ b/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js @@ -11,24 +11,18 @@ import isObject from 'd2-utilizr/lib/isObject'; import defaultClasses from './formBuilder.module.css'; import type { ErrorData, PostProcessErrorMessage } from './formbuilder.types'; import type { PluginContext } from '../FormFieldPlugin/FormFieldPlugin.types'; -import { getValidators } from '../field/validators'; +import { getValidators, validateField } from '../field/validators'; +import type { ValidatorContainer } from '../field/validators'; import type { DataElement } from '../../../metaData'; import type { QuerySingleResource } from '../../../utils/api'; -export type ValidatorContainer = { - validator: (value: any, validationContext: ?Object) => boolean | Promise, - message: string, - validatingMessage?: ?string, - type?: ?string, - async?: ?boolean, -}; export type FieldConfig = { id: string, component: React.ComponentType, plugin?: boolean, props: Object, - validators?: ?Array, + validators?: ?Array, commitEvent?: ?string, onIsEqual?: ?(newValue: any, oldValue: any) => boolean, }; @@ -47,7 +41,7 @@ type GetContainerPropsFn = (index: number, fieldsCount: number, field: FieldConf type FieldCommitConfig = {| fieldId: string, - validators?: ?Array, + validators?: ?Array, onIsEqual?: ?(newValue: any, oldValue: any) => boolean, |} @@ -99,54 +93,6 @@ type FieldCommitOptions = { type FieldsValidatingPromiseContainer = { [fieldId: string]: ?{ cancelableValidatingPromise?: ?CancelablePromise, validatingCompleteUid: string } }; export class FormBuilder extends React.Component { - static async validateField( - { validators }: { validators?: ?Array }, - value: any, - validationContext: ?Object, - onIsValidatingInternal: ?Function, - ): Promise<{ valid: boolean, errorMessage?: ?string, errorType?: ?string }> { - if (!validators || validators.length === 0) { - return { - valid: true, - }; - } - - const validatorResult = await validators - .reduce(async (passPromise, currentValidator) => { - const pass = await passPromise; - if (pass === true) { - let result = currentValidator.validator(value, validationContext); - if (result instanceof Promise) { - result = onIsValidatingInternal ? onIsValidatingInternal(currentValidator.validatingMessage, result) : result; - result = await result; - } - - if (result === true || (result && result.valid)) { - return true; - } - return { - message: (result && result.errorMessage) || currentValidator.message, - type: currentValidator.type, - data: result && result.data, - }; - } - return pass; - }, Promise.resolve(true)); - - if (validatorResult !== true) { - return { - valid: false, - errorMessage: validatorResult.message, - errorType: validatorResult.type, - errorData: validatorResult.data, - }; - } - - return { - valid: true, - }; - } - static getAsyncUIState(fieldsUI: { [id: string]: FieldUI }) { return Object.keys(fieldsUI).reduce((accAsyncUIState, fieldId) => { const fieldUI = fieldsUI[fieldId]; @@ -219,7 +165,7 @@ export class FormBuilder extends React.Component { let validationData; try { - validationData = await FormBuilder.validateField( + validationData = await validateField( field, values[field.id], validationContext, @@ -429,7 +375,7 @@ export class FormBuilder extends React.Component { }; this.commitUpdateTriggeredForFields[fieldId] = true; - const updatePromise = FormBuilder.validateField( + const updatePromise = validateField( { validators }, value, onGetValidationContext && onGetValidationContext(), diff --git a/src/core_modules/capture-core/components/D2Form/FormBuilder/index.js b/src/core_modules/capture-core/components/D2Form/FormBuilder/index.js index 42434e8133..932791db25 100644 --- a/src/core_modules/capture-core/components/D2Form/FormBuilder/index.js +++ b/src/core_modules/capture-core/components/D2Form/FormBuilder/index.js @@ -1,4 +1,4 @@ // @flow export { FormBuilder } from './FormBuilder.component'; export type { PostProcessErrorMessage, ErrorData } from './formbuilder.types'; -export type { FieldConfig, ValidatorContainer } from './FormBuilder.component'; +export type { FieldConfig } from './FormBuilder.component'; diff --git a/src/core_modules/capture-core/components/D2Form/field/configs/base/configBase.js b/src/core_modules/capture-core/components/D2Form/field/configs/base/configBase.js index 9aeec1d79d..1a3e1be9a1 100644 --- a/src/core_modules/capture-core/components/D2Form/field/configs/base/configBase.js +++ b/src/core_modules/capture-core/components/D2Form/field/configs/base/configBase.js @@ -1,6 +1,6 @@ // @flow import { type ComponentType } from 'react'; -import type { ValidatorContainer } from '../../../FormBuilder'; +import type { ValidatorContainer } from '../../../field/validators'; import { getValidators } from '../../validators'; import type { DataElement } from '../../../../../metaData'; import type { QuerySingleResource } from '../../../../../utils/api/api.types'; diff --git a/src/core_modules/capture-core/components/D2Form/field/validators/index.js b/src/core_modules/capture-core/components/D2Form/field/validators/index.js index f21eb5e94e..2b07f6874f 100644 --- a/src/core_modules/capture-core/components/D2Form/field/validators/index.js +++ b/src/core_modules/capture-core/components/D2Form/field/validators/index.js @@ -1,4 +1,7 @@ // @flow export { getValidators } from './getValidators'; +export { validateField } from './validateField'; export { validateAssignEffects } from './validateAssignEffects'; + +export type { ValidatorContainer } from './getValidators'; export type { AssignOutputEffectWithValidations } from './validateAssignEffects'; diff --git a/src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js b/src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js index 6a98205c94..54e84b6da4 100644 --- a/src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js +++ b/src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js @@ -6,34 +6,13 @@ import type { AssignOutputEffect } from '@dhis2/rules-engine-javascript'; import { type DataElement } from '../../../../metaData'; import type { QuerySingleResource } from '../../../../utils/api'; import { getValidators } from './getValidators'; - -type Validations = { valid: boolean, errorMessage?: string, errorType?: string, errorData?: string }; +import type { Validations } from './validateField'; +import { validateField } from './validateField'; export type AssignOutputEffectWithValidations = { [metaDataId: string]: Array, }; -const getValidatorsResult = async (validators, value, validationContext) => - validators.reduce(async (passPromise, currentValidator) => { - const pass = await passPromise; - if (pass === true) { - if (!currentValidator) { - return true; - } - const result: Object = currentValidator.validator(value, validationContext); - - if (result === true || (result && result.valid)) { - return true; - } - return { - message: result.errorMessage || currentValidator.message, - type: currentValidator.type, - data: result.data, - }; - } - return pass; - }, Promise.resolve(true)); - export const validateAssignEffects = async ({ dataElements, effects, @@ -45,7 +24,7 @@ export const validateAssignEffects = async ({ querySingleResource: QuerySingleResource, onGetValidationContext?: () => Object, }): Promise => { - const assignEffects: { [metaDataId: string]: Array } = effects[effectActions.ASSIGN_VALUE]; + const assignEffects: {| [metaDataId: string]: Array |} = effects[effectActions.ASSIGN_VALUE]; if (!assignEffects) { return effects; } @@ -63,20 +42,8 @@ export const validateAssignEffects = async ({ const validationContext = onGetValidationContext && onGetValidationContext(); try { - const validatorResult = await getValidatorsResult(validators, value, validationContext); - const effectWithValidation = - validatorResult === true - ? { - ...effectsForId[lastEffect], - valid: true, - } - : { - ...effectsForId[lastEffect], - valid: false, - errorMessage: validatorResult.message, - errorType: validatorResult.type, - errorData: validatorResult.data, - }; + const validatorResult = await validateField({ validators }, value, validationContext); + const effectWithValidation = Object.assign({}, effectsForId[lastEffect], validatorResult); acc[metaData.id] = [...effectsForId.slice(0, lastEffect - 1), effectWithValidation]; return acc; diff --git a/src/core_modules/capture-core/components/D2Form/field/validators/validateField.js b/src/core_modules/capture-core/components/D2Form/field/validators/validateField.js new file mode 100644 index 0000000000..058786cc29 --- /dev/null +++ b/src/core_modules/capture-core/components/D2Form/field/validators/validateField.js @@ -0,0 +1,61 @@ +// @flow +import type { ValidatorContainer } from './getValidators'; + +export type Validations = { + valid: boolean, + errorMessage?: ?string, + errorType?: ?string, + errorData?: Object, +}; + +export const validateField = async ( + { validators }: { validators?: ?Array }, + value: any, + validationContext: ?Object, + onIsValidatingInternal: ?Function, +): Promise => { + if (!validators || validators.length === 0) { + return { + valid: true, + }; + } + + const validatorResult = await validators.reduce(async (passPromise, currentValidator) => { + const pass = await passPromise; + if (!currentValidator) { + return pass; + } + if (pass === true) { + let result = currentValidator.validator(value, validationContext); + if (result instanceof Promise) { + result = onIsValidatingInternal + ? onIsValidatingInternal(currentValidator.validatingMessage, result) + : result; + result = await result; + } + + if (result === true || (result && result.valid)) { + return true; + } + return { + message: (result && result.errorMessage) || currentValidator.message, + type: currentValidator.type, + data: result && result.data, + }; + } + return pass; + }, Promise.resolve(true)); + + if (validatorResult !== true) { + return { + valid: false, + errorMessage: validatorResult.message, + errorType: validatorResult.type, + errorData: validatorResult.data, + }; + } + + return { + valid: true, + }; +}; From e3f68942d7c1de27d033b0ca87f69f65b13938c5 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Mon, 9 Sep 2024 10:25:58 +0200 Subject: [PATCH 06/22] chore: improve ValidatorContainer type --- .../D2Form/FormBuilder/FormBuilder.component.js | 4 ++-- .../components/D2Form/field/validators/getValidators.js | 8 ++++---- .../components/D2Form/field/validators/validateField.js | 5 +---- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js b/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js index 23d724c3cc..b8e252bd37 100644 --- a/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js +++ b/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js @@ -22,7 +22,7 @@ export type FieldConfig = { component: React.ComponentType, plugin?: boolean, props: Object, - validators?: ?Array, + validators?: Array, commitEvent?: ?string, onIsEqual?: ?(newValue: any, oldValue: any) => boolean, }; @@ -41,7 +41,7 @@ type GetContainerPropsFn = (index: number, fieldsCount: number, field: FieldConf type FieldCommitConfig = {| fieldId: string, - validators?: ?Array, + validators?: Array, onIsEqual?: ?(newValue: any, oldValue: any) => boolean, |} diff --git a/src/core_modules/capture-core/components/D2Form/field/validators/getValidators.js b/src/core_modules/capture-core/components/D2Form/field/validators/getValidators.js index b05b037bfc..7b7a8b5aef 100644 --- a/src/core_modules/capture-core/components/D2Form/field/validators/getValidators.js +++ b/src/core_modules/capture-core/components/D2Form/field/validators/getValidators.js @@ -208,7 +208,7 @@ const validatorsForTypes = { }], }; -function buildTypeValidators(metaData: DataElement | DateDataElement): ?Array { +function buildTypeValidators(metaData: DataElement | DateDataElement): Array { // $FlowFixMe dataElementTypes flow error let validatorContainersForType = validatorsForTypes[metaData.type] ? validatorsForTypes[metaData.type] : []; @@ -229,7 +229,7 @@ function buildTypeValidators(metaData: DataElement | DateDataElement): ?Array { +function buildCompulsoryValidator(metaData: DataElement): Array { return metaData.compulsory ? [ @@ -246,7 +246,7 @@ function buildCompulsoryValidator(metaData: DataElement): Array { +): Array { return metaData.unique ? [ @@ -268,7 +268,7 @@ function buildUniqueValidator( } export const getValidators = -(metaData: DataElement, querySingleResource: QuerySingleResource): Array => [ +(metaData: DataElement, querySingleResource: QuerySingleResource): Array => [ buildCompulsoryValidator, buildTypeValidators, buildUniqueValidator, diff --git a/src/core_modules/capture-core/components/D2Form/field/validators/validateField.js b/src/core_modules/capture-core/components/D2Form/field/validators/validateField.js index 058786cc29..521239031d 100644 --- a/src/core_modules/capture-core/components/D2Form/field/validators/validateField.js +++ b/src/core_modules/capture-core/components/D2Form/field/validators/validateField.js @@ -9,7 +9,7 @@ export type Validations = { }; export const validateField = async ( - { validators }: { validators?: ?Array }, + { validators }: { validators?: Array }, value: any, validationContext: ?Object, onIsValidatingInternal: ?Function, @@ -22,9 +22,6 @@ export const validateField = async ( const validatorResult = await validators.reduce(async (passPromise, currentValidator) => { const pass = await passPromise; - if (!currentValidator) { - return pass; - } if (pass === true) { let result = currentValidator.validator(value, validationContext); if (result instanceof Promise) { From ffa3ed893a768a9225d43e2c1602197815319835 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Thu, 3 Oct 2024 09:40:06 +0200 Subject: [PATCH 07/22] chore: replace EMPTY with of() --- .../EditEventDataEntry/editEventDataEntry.epics.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/editEventDataEntry.epics.js b/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/editEventDataEntry.epics.js index 5e5524f645..7bb83f74fb 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/editEventDataEntry.epics.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/editEventDataEntry.epics.js @@ -4,7 +4,7 @@ import { map, filter, flatMap, switchMap } from 'rxjs/operators'; import { batchActions } from 'redux-batched-actions'; import { dataEntryKeys, dataEntryIds } from 'capture-core/constants'; import moment from 'moment'; -import { EMPTY } from 'rxjs'; +import { EMPTY, of } from 'rxjs'; import { getFormattedStringFromMomentUsingEuropeanGlyphs } from 'capture-core-utils/date'; import { convertCategoryOptionsToServer, convertValue as convertToServerValue } from '../../../converters/clientToServer'; import { getProgramAndStageFromEvent, scopeTypes, getScopeInfo } from '../../../metaData'; @@ -54,8 +54,7 @@ export const loadEditEventDataEntryEpic = (action$: InputObservable, store: Redu const eventContainer = loadedValues.eventContainer; const metadataContainer = getProgramAndStageFromEvent(eventContainer.event); if (metadataContainer.error) { - prerequisitesErrorLoadingEditEventDataEntry(metadataContainer.error); - return EMPTY; + return of(prerequisitesErrorLoadingEditEventDataEntry(metadataContainer.error)); } const program = metadataContainer.program; From 509196542a1ad0767d6823ea2f0c792fc43f7922 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Thu, 3 Oct 2024 09:41:53 +0200 Subject: [PATCH 08/22] chore: rollback formSectionFields checks --- .../descriptions/form.reducerDescription.js | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js index 41f26d8cf3..2664669576 100644 --- a/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js @@ -205,24 +205,27 @@ export const formsSectionsFieldsUIDesc = createReducerDescription({ [newPageActionTypes.CLEAN_UP_DATA_ENTRY]: cleanUp, [rulesEffectsActionTypes.UPDATE_RULES_EFFECTS]: (state, action) => { const { formId, rulesEffects } = action.payload; + const formSectionFields = state[formId]; const assignEffects: { [id: string]: Array } = rulesEffects && rulesEffects[effectActions.ASSIGN_VALUE]; - if (!assignEffects) { + if (!assignEffects || !formSectionFields) { return state; } const updatedFields = Object.keys(assignEffects).reduce((acc, id) => { - const effectsForId = assignEffects[id]; - const effect = effectsForId[effectsForId.length - 1]; - acc[id] = { - valid: effect.valid, - errorData: effect.errorData, - errorMessage: effect.errorMessage, - errorType: effect.errorType, - touched: true, - validatingMessage: null, - }; + if (formSectionFields[id]) { + const effectsForId = assignEffects[id]; + const effect = effectsForId[effectsForId.length - 1]; + acc[id] = { + valid: effect.valid, + errorData: effect.errorData, + errorMessage: effect.errorMessage, + errorType: effect.errorType, + touched: true, + validatingMessage: null, + }; + } return acc; }, {}); From e855660972568ffc2109718277466534c856812c Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Thu, 3 Oct 2024 09:44:07 +0200 Subject: [PATCH 09/22] chore: replace assigned effects with a one-element array & lastIndex variable rename --- .../D2Form/field/validators/validateAssignEffects.js | 10 +++++----- .../reducers/descriptions/form.reducerDescription.js | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js b/src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js index 54e84b6da4..eb6ea7f31e 100644 --- a/src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js +++ b/src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js @@ -36,22 +36,22 @@ export const validateAssignEffects = async ({ } const effectsForId = assignEffects[metaData.id]; - const lastEffect = effectsForId.length - 1; - const value = effectsForId[lastEffect].value; + const lastIndex = effectsForId.length - 1; + const value = effectsForId[lastIndex].value; const validators = getValidators(metaData, querySingleResource); const validationContext = onGetValidationContext && onGetValidationContext(); try { const validatorResult = await validateField({ validators }, value, validationContext); - const effectWithValidation = Object.assign({}, effectsForId[lastEffect], validatorResult); + const effectWithValidation = Object.assign({}, effectsForId[lastIndex], validatorResult); - acc[metaData.id] = [...effectsForId.slice(0, lastEffect - 1), effectWithValidation]; + acc[metaData.id] = [effectWithValidation]; return acc; } catch (error) { log.error( errorCreator('an error occured while validating the assigned program rule effect')({ metaData, - lastEffect, + lastIndex, error, }), ); diff --git a/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js index 2664669576..bbd8b865a7 100644 --- a/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js @@ -76,7 +76,7 @@ export const formsValuesDesc = createReducerDescription({ ...state[payload.formId], ...Object.keys(assignEffects).reduce((acc, id) => { const effectsForId = assignEffects[id]; - const value = effectsForId[effectsForId.length - 1].value; + const value = effectsForId[0].value; acc[id] = value; return acc; }, {}), @@ -216,7 +216,7 @@ export const formsSectionsFieldsUIDesc = createReducerDescription({ const updatedFields = Object.keys(assignEffects).reduce((acc, id) => { if (formSectionFields[id]) { const effectsForId = assignEffects[id]; - const effect = effectsForId[effectsForId.length - 1]; + const effect = effectsForId[0]; acc[id] = { valid: effect.valid, errorData: effect.errorData, From fcf1b3bd5ffef72937b04c7f9a775235c3f72bcb Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Thu, 3 Oct 2024 12:22:59 +0200 Subject: [PATCH 10/22] chore: variable rename Co-authored-by: Tony Valle <79843014+superskip@users.noreply.github.com> --- .../reducers/descriptions/form.reducerDescription.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js index bbd8b865a7..5e5441ab5b 100644 --- a/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js @@ -215,8 +215,7 @@ export const formsSectionsFieldsUIDesc = createReducerDescription({ const updatedFields = Object.keys(assignEffects).reduce((acc, id) => { if (formSectionFields[id]) { - const effectsForId = assignEffects[id]; - const effect = effectsForId[0]; + const effect = assignEffects[id][0]; acc[id] = { valid: effect.valid, errorData: effect.errorData, From 4d01a7223286ee5a40bfa56690081fb7812eaba6 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Wed, 9 Oct 2024 16:21:59 +0200 Subject: [PATCH 11/22] chore: rename validateField to validateValue --- .../components/D2Form/FormBuilder/FormBuilder.component.js | 6 +++--- .../components/D2Form/field/validators/index.js | 2 +- .../D2Form/field/validators/validateAssignEffects.js | 6 +++--- .../field/validators/{validateField.js => validateValue.js} | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) rename src/core_modules/capture-core/components/D2Form/field/validators/{validateField.js => validateValue.js} (97%) diff --git a/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js b/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js index b8e252bd37..856e8ac03d 100644 --- a/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js +++ b/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js @@ -11,7 +11,7 @@ import isObject from 'd2-utilizr/lib/isObject'; import defaultClasses from './formBuilder.module.css'; import type { ErrorData, PostProcessErrorMessage } from './formbuilder.types'; import type { PluginContext } from '../FormFieldPlugin/FormFieldPlugin.types'; -import { getValidators, validateField } from '../field/validators'; +import { getValidators, validateValue } from '../field/validators'; import type { ValidatorContainer } from '../field/validators'; import type { DataElement } from '../../../metaData'; import type { QuerySingleResource } from '../../../utils/api'; @@ -165,7 +165,7 @@ export class FormBuilder extends React.Component { let validationData; try { - validationData = await validateField( + validationData = await validateValue( field, values[field.id], validationContext, @@ -375,7 +375,7 @@ export class FormBuilder extends React.Component { }; this.commitUpdateTriggeredForFields[fieldId] = true; - const updatePromise = validateField( + const updatePromise = validateValue( { validators }, value, onGetValidationContext && onGetValidationContext(), diff --git a/src/core_modules/capture-core/components/D2Form/field/validators/index.js b/src/core_modules/capture-core/components/D2Form/field/validators/index.js index 2b07f6874f..b9bf19b05e 100644 --- a/src/core_modules/capture-core/components/D2Form/field/validators/index.js +++ b/src/core_modules/capture-core/components/D2Form/field/validators/index.js @@ -1,6 +1,6 @@ // @flow export { getValidators } from './getValidators'; -export { validateField } from './validateField'; +export { validateValue } from './validateValue'; export { validateAssignEffects } from './validateAssignEffects'; export type { ValidatorContainer } from './getValidators'; diff --git a/src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js b/src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js index eb6ea7f31e..dde69b4cce 100644 --- a/src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js +++ b/src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js @@ -6,8 +6,8 @@ import type { AssignOutputEffect } from '@dhis2/rules-engine-javascript'; import { type DataElement } from '../../../../metaData'; import type { QuerySingleResource } from '../../../../utils/api'; import { getValidators } from './getValidators'; -import type { Validations } from './validateField'; -import { validateField } from './validateField'; +import type { Validations } from './validateValue'; +import { validateValue } from './validateValue'; export type AssignOutputEffectWithValidations = { [metaDataId: string]: Array, @@ -42,7 +42,7 @@ export const validateAssignEffects = async ({ const validationContext = onGetValidationContext && onGetValidationContext(); try { - const validatorResult = await validateField({ validators }, value, validationContext); + const validatorResult = await validateValue({ validators }, value, validationContext); const effectWithValidation = Object.assign({}, effectsForId[lastIndex], validatorResult); acc[metaData.id] = [effectWithValidation]; diff --git a/src/core_modules/capture-core/components/D2Form/field/validators/validateField.js b/src/core_modules/capture-core/components/D2Form/field/validators/validateValue.js similarity index 97% rename from src/core_modules/capture-core/components/D2Form/field/validators/validateField.js rename to src/core_modules/capture-core/components/D2Form/field/validators/validateValue.js index 521239031d..c057573a18 100644 --- a/src/core_modules/capture-core/components/D2Form/field/validators/validateField.js +++ b/src/core_modules/capture-core/components/D2Form/field/validators/validateValue.js @@ -8,7 +8,7 @@ export type Validations = { errorData?: Object, }; -export const validateField = async ( +export const validateValue = async ( { validators }: { validators?: Array }, value: any, validationContext: ?Object, From 98dea4122ec5ea5b3862ccbc91916426d03f635f Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Thu, 10 Oct 2024 08:51:33 +0200 Subject: [PATCH 12/22] chore: move the files to capture-core/rules and capture-core/utils/validation --- i18n/en.pot | 124 +++++++++--------- .../D2Form/D2SectionFields.component.js | 2 +- .../FormBuilder/FormBuilder.component.js | 4 +- .../D2Form/field/configs/base/configBase.js | 4 +- .../capture-core/components/D2Form/index.js | 2 - .../eventDate.validatorContainersGetter.js | 2 +- .../actions/enrollment.actionBatchs.js | 7 +- .../Enrollment/actions/open.actionBatchs.js | 7 +- .../epics/newEventDataEntry.epics.js | 2 +- .../eventDate.validatorContainersGetter.js | 2 +- .../DataEntry/helpers/getRulesActions.js | 2 +- .../Date/DateFilter.component.js | 2 +- .../Date/DateFilterManager.component.js | 2 +- .../converters/dateConverter.js | 3 +- .../DataEntry/epics/dataEntryRules.epics.js | 2 +- .../eventDate.validatorContainersGetter.js | 2 +- .../DataEntry/helpers/getRulesActions.js | 2 +- .../DataEntry/editEventDataEntry.actions.js | 2 +- .../epics/editEventDataEntry.epics.js | 2 +- .../eventDate.validatorContainersGetter.js | 2 +- .../ProgramRules/getRulesActionsForTEI.js | 8 +- .../convertToClientConfig.js | 3 +- .../convertToClientFilters.js | 3 +- .../filterConverters/dateConverter.js | 3 +- .../descriptions/form.reducerDescription.js | 2 +- src/core_modules/capture-core/rules/index.js | 2 + .../validateAssignEffects.js | 10 +- .../validation}/constants/index.js | 0 .../constants/validatorTypes.const.js | 0 .../validation}/getValidators.js | 6 +- .../validators => utils/validation}/index.js | 2 - .../validation}/validateValue.js | 0 .../areRelativeRangeValuesSupported.js | 0 .../validators/form/ageValidator.js | 2 +- .../validators/form/dateTimeValidator.js | 2 +- .../validators/form/dateValidator.js | 2 +- .../validators/form/getDateRangeValidator.js | 2 +- .../form/getDateTimeRangeValidator.js | 2 +- .../form/getNumberRangeValidator.js | 0 .../validators/form/getTimeRangeValidator.js | 0 .../{ => validation}/validators/form/index.js | 0 .../validators/form/isValidNonFutureDate.js | 2 +- 42 files changed, 120 insertions(+), 108 deletions(-) rename src/core_modules/capture-core/{components/D2Form/field/validators => rules}/validateAssignEffects.js (87%) rename src/core_modules/capture-core/{components/D2Form/field/validators => utils/validation}/constants/index.js (100%) rename src/core_modules/capture-core/{components/D2Form/field/validators => utils/validation}/constants/validatorTypes.const.js (100%) rename src/core_modules/capture-core/{components/D2Form/field/validators => utils/validation}/getValidators.js (98%) rename src/core_modules/capture-core/{components/D2Form/field/validators => utils/validation}/index.js (53%) rename src/core_modules/capture-core/{components/D2Form/field/validators => utils/validation}/validateValue.js (100%) rename src/core_modules/capture-core/utils/{ => validation}/validators/areRelativeRangeValuesSupported.js (100%) rename src/core_modules/capture-core/utils/{ => validation}/validators/form/ageValidator.js (76%) rename src/core_modules/capture-core/utils/{ => validation}/validators/form/dateTimeValidator.js (78%) rename src/core_modules/capture-core/utils/{ => validation}/validators/form/dateValidator.js (77%) rename src/core_modules/capture-core/utils/{ => validation}/validators/form/getDateRangeValidator.js (94%) rename src/core_modules/capture-core/utils/{ => validation}/validators/form/getDateTimeRangeValidator.js (97%) rename src/core_modules/capture-core/utils/{ => validation}/validators/form/getNumberRangeValidator.js (100%) rename src/core_modules/capture-core/utils/{ => validation}/validators/form/getTimeRangeValidator.js (100%) rename src/core_modules/capture-core/utils/{ => validation}/validators/form/index.js (100%) rename src/core_modules/capture-core/utils/{ => validation}/validators/form/isValidNonFutureDate.js (89%) diff --git a/i18n/en.pot b/i18n/en.pot index 5d63ac1048..9eef17b39e 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2024-09-02T11:08:16.281Z\n" -"PO-Revision-Date: 2024-09-02T11:08:16.281Z\n" +"POT-Creation-Date: 2024-10-10T06:33:42.308Z\n" +"PO-Revision-Date: 2024-10-10T06:33:42.308Z\n" msgid "Choose one or more dates..." msgstr "Choose one or more dates..." @@ -80,66 +80,6 @@ msgstr "This value is validating" msgid "Async field update failed" msgstr "Async field update failed" -msgid "A value is required" -msgstr "A value is required" - -msgid "Please provide a valid number" -msgstr "Please provide a valid number" - -msgid "Please provide a valid integer" -msgstr "Please provide a valid integer" - -msgid "Please provide a positive integer" -msgstr "Please provide a positive integer" - -msgid "Please provide zero or a positive integer" -msgstr "Please provide zero or a positive integer" - -msgid "Please provide a negative integer" -msgstr "Please provide a negative integer" - -msgid "Please provide a valid date" -msgstr "Please provide a valid date" - -msgid "A date in the future is not allowed" -msgstr "A date in the future is not allowed" - -msgid "Please provide a valid date and time" -msgstr "Please provide a valid date and time" - -msgid "Please provide a valid time" -msgstr "Please provide a valid time" - -msgid "Please provide an integer between 0 and 100" -msgstr "Please provide an integer between 0 and 100" - -msgid "Please provide a valid url" -msgstr "Please provide a valid url" - -msgid "Please provide a valid email address" -msgstr "Please provide a valid email address" - -msgid "Please provide a valid age" -msgstr "Please provide a valid age" - -msgid "Please provide a valid phone number" -msgstr "Please provide a valid phone number" - -msgid "Please provide a valid organisation unit" -msgstr "Please provide a valid organisation unit" - -msgid "Please provide valid coordinates" -msgstr "Please provide valid coordinates" - -msgid "This value already exists" -msgstr "This value already exists" - -msgid "\"From\" cannot be greater than \"To\"" -msgstr "\"From\" cannot be greater than \"To\"" - -msgid "Checking..." -msgstr "Checking..." - msgid "Area" msgstr "Area" @@ -152,6 +92,12 @@ msgstr "Enrollment" msgid "Complete event" msgstr "Complete event" +msgid "A value is required" +msgstr "A value is required" + +msgid "Please provide a valid date" +msgstr "Please provide a valid date" + msgid "{{ stageName }} - Basic info" msgstr "{{ stageName }} - Basic info" @@ -1233,6 +1179,9 @@ msgstr "Set coordinates" msgid "Coordinates" msgstr "Coordinates" +msgid "Please provide valid coordinates" +msgstr "Please provide valid coordinates" + msgid "Delete polygon" msgstr "Delete polygon" @@ -1490,6 +1439,9 @@ msgstr "Scheduled date" msgid "Report date" msgstr "Report date" +msgid "Please provide a valid organisation unit" +msgstr "Please provide a valid organisation unit" + msgid "Please select a valid event" msgstr "Please select a valid event" @@ -1782,6 +1734,54 @@ msgstr "Error editing the event, the changes made were not saved" msgid "Error updating the Assignee" msgstr "Error updating the Assignee" +msgid "Please provide a valid number" +msgstr "Please provide a valid number" + +msgid "Please provide a valid integer" +msgstr "Please provide a valid integer" + +msgid "Please provide a positive integer" +msgstr "Please provide a positive integer" + +msgid "Please provide zero or a positive integer" +msgstr "Please provide zero or a positive integer" + +msgid "Please provide a negative integer" +msgstr "Please provide a negative integer" + +msgid "A date in the future is not allowed" +msgstr "A date in the future is not allowed" + +msgid "Please provide a valid date and time" +msgstr "Please provide a valid date and time" + +msgid "Please provide a valid time" +msgstr "Please provide a valid time" + +msgid "Please provide an integer between 0 and 100" +msgstr "Please provide an integer between 0 and 100" + +msgid "Please provide a valid url" +msgstr "Please provide a valid url" + +msgid "Please provide a valid email address" +msgstr "Please provide a valid email address" + +msgid "Please provide a valid age" +msgstr "Please provide a valid age" + +msgid "Please provide a valid phone number" +msgstr "Please provide a valid phone number" + +msgid "This value already exists" +msgstr "This value already exists" + +msgid "\"From\" cannot be greater than \"To\"" +msgstr "\"From\" cannot be greater than \"To\"" + +msgid "Checking..." +msgstr "Checking..." + msgid "Set coordinate" msgstr "Set coordinate" diff --git a/src/core_modules/capture-core/components/D2Form/D2SectionFields.component.js b/src/core_modules/capture-core/components/D2Form/D2SectionFields.component.js index c5aa1266a8..31d6d45b1a 100644 --- a/src/core_modules/capture-core/components/D2Form/D2SectionFields.component.js +++ b/src/core_modules/capture-core/components/D2Form/D2SectionFields.component.js @@ -11,7 +11,7 @@ import { buildField } from './field/buildField'; import { validationStrategies } from '../../metaData/RenderFoundation/renderFoundation.const'; import type { CustomForm, DataElement } from '../../metaData'; import { messageStateKeys } from '../../reducers/descriptions/rulesEffects.reducerDescription'; -import { validatorTypes } from './field/validators/constants'; +import { validatorTypes } from '../../utils/validation/constants'; import type { QuerySingleResource } from '../../utils/api/api.types'; import { FormFieldPlugin } from './FormFieldPlugin'; import { FormFieldPluginConfig } from '../../metaData/FormFieldPluginConfig'; diff --git a/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js b/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js index 856e8ac03d..57f90b086a 100644 --- a/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js +++ b/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js @@ -11,8 +11,8 @@ import isObject from 'd2-utilizr/lib/isObject'; import defaultClasses from './formBuilder.module.css'; import type { ErrorData, PostProcessErrorMessage } from './formbuilder.types'; import type { PluginContext } from '../FormFieldPlugin/FormFieldPlugin.types'; -import { getValidators, validateValue } from '../field/validators'; -import type { ValidatorContainer } from '../field/validators'; +import { getValidators, validateValue } from '../../../utils/validation'; +import type { ValidatorContainer } from '../../../utils/validation'; import type { DataElement } from '../../../metaData'; import type { QuerySingleResource } from '../../../utils/api'; diff --git a/src/core_modules/capture-core/components/D2Form/field/configs/base/configBase.js b/src/core_modules/capture-core/components/D2Form/field/configs/base/configBase.js index 1a3e1be9a1..f33d880d64 100644 --- a/src/core_modules/capture-core/components/D2Form/field/configs/base/configBase.js +++ b/src/core_modules/capture-core/components/D2Form/field/configs/base/configBase.js @@ -1,7 +1,7 @@ // @flow import { type ComponentType } from 'react'; -import type { ValidatorContainer } from '../../../field/validators'; -import { getValidators } from '../../validators'; +import type { ValidatorContainer } from '../../../../../utils/validation'; +import { getValidators } from '../../../../../utils/validation'; import type { DataElement } from '../../../../../metaData'; import type { QuerySingleResource } from '../../../../../utils/api/api.types'; diff --git a/src/core_modules/capture-core/components/D2Form/index.js b/src/core_modules/capture-core/components/D2Form/index.js index ac4d45758a..ac6560e0f2 100644 --- a/src/core_modules/capture-core/components/D2Form/index.js +++ b/src/core_modules/capture-core/components/D2Form/index.js @@ -1,5 +1,3 @@ // @flow export { asyncHandlerActionTypes, asyncUpdateFieldEpic } from './asyncHandlerHOC'; export { D2Form } from './D2Form.container'; -export { validateAssignEffects } from './field/validators'; -export type { AssignOutputEffectWithValidations } from './field/validators'; diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/fieldValidators/eventDate.validatorContainersGetter.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/fieldValidators/eventDate.validatorContainersGetter.js index 3cb0a56c19..f5f79a8a5d 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/fieldValidators/eventDate.validatorContainersGetter.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/fieldValidators/eventDate.validatorContainersGetter.js @@ -1,7 +1,7 @@ // @flow import { hasValue } from 'capture-core-utils/validators/form'; import i18n from '@dhis2/d2-i18n'; -import { isValidDate } from '../../../../../utils/validators/form'; +import { isValidDate } from '../../../../../utils/validation/validators/form'; const preValidateDate = (value?: ?string) => { if (!value) { diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actionBatchs.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actionBatchs.js index b1658a1165..da21783486 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actionBatchs.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/enrollment.actionBatchs.js @@ -6,12 +6,15 @@ import type { TEIValues, OrgUnit, } from '@dhis2/rules-engine-javascript'; -import { getApplicableRuleEffectsForTrackerProgram, updateRulesEffects } from '../../../../rules'; +import { + getApplicableRuleEffectsForTrackerProgram, + updateRulesEffects, + validateAssignEffects, +} from '../../../../rules'; import { rulesExecutedPostUpdateField } from '../../../DataEntry/actions/dataEntry.actions'; import { TrackerProgram, RenderFoundation, ProgramStage } from '../../../../metaData'; import { startRunRulesPostUpdateField } from '../../../DataEntry'; import { startRunRulesOnUpdateForNewEnrollment } from './enrollment.actions'; -import { validateAssignEffects } from '../../../D2Form'; import type { QuerySingleResource } from '../../../../utils/api'; export const batchActionTypes = { diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.js index cd41155381..e689bc34ff 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.js @@ -1,7 +1,11 @@ // @flow import { batchActions } from 'redux-batched-actions'; import type { OrgUnit } from '@dhis2/rules-engine-javascript'; -import { getApplicableRuleEffectsForTrackerProgram, updateRulesEffects } from '../../../../rules'; +import { + getApplicableRuleEffectsForTrackerProgram, + updateRulesEffects, + validateAssignEffects, +} from '../../../../rules'; import type { ProgramStage, TrackerProgram, RenderFoundation } from '../../../../metaData'; import { getDataEntryKey } from '../../../DataEntry/common/getDataEntryKey'; import { loadNewDataEntry } from '../../../DataEntry/actions/dataEntryLoadNew.actions'; @@ -16,7 +20,6 @@ import { convertDateObjectToDateFormatString } from '../../../../utils/converter import { addFormData } from '../../../D2Form/actions/form.actions'; import type { ProgramCategory } from '../../../WidgetEventSchedule/CategoryOptions/CategoryOptions.types'; import { getDataEntryPropsToInclude } from '../EnrollmentWithFirstStageDataEntry'; -import { validateAssignEffects } from '../../../D2Form'; import type { QuerySingleResource } from '../../../../utils/api'; const itemId = 'newEnrollment'; diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/newEventDataEntry.epics.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/newEventDataEntry.epics.js index eebbad9cd5..006d2a7003 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/newEventDataEntry.epics.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/newEventDataEntry.epics.js @@ -38,7 +38,7 @@ import { actionTypes as crossPageActionTypes } from '../../../../../Pages/action import { lockedSelectorActionTypes } from '../../../../../LockedSelector/LockedSelector.actions'; import { newPageActionTypes } from '../../../../../Pages/New/NewPage.actions'; import { programCollection } from '../../../../../../metaDataMemoryStores'; -import { validateAssignEffects } from '../../../../../D2Form'; +import { validateAssignEffects } from '../../../../../../rules'; import type { QuerySingleResource } from '../../../../../../utils/api'; export const resetDataEntryForNewEventEpic = (action$: InputObservable) => diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/fieldValidators/eventDate.validatorContainersGetter.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/fieldValidators/eventDate.validatorContainersGetter.js index 31afe2c4da..99c166cfa4 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/fieldValidators/eventDate.validatorContainersGetter.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/fieldValidators/eventDate.validatorContainersGetter.js @@ -1,7 +1,7 @@ // @flow import { hasValue } from 'capture-core-utils/validators/form'; import i18n from '@dhis2/d2-i18n'; -import { isValidDate } from '../../../../../../utils/validators/form'; +import { isValidDate } from '../../../../../../utils/validation/validators/form'; const preValidateDate = (value?: ?string) => { if (!value) { diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/helpers/getRulesActions.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/helpers/getRulesActions.js index d129ca4d24..62375d1ce1 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/helpers/getRulesActions.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/helpers/getRulesActions.js @@ -5,10 +5,10 @@ import { getCurrentClientMainData, getApplicableRuleEffectsForEventProgram, updateRulesEffects, + validateAssignEffects, } from '../../../../../../rules'; import type { RenderFoundation, EventProgram } from '../../../../../../metaData'; import { dataEntryId, itemId, formId } from './constants'; -import { validateAssignEffects } from '../../../../../D2Form'; import type { QuerySingleResource } from '../../../../../../utils/api'; export const getRulesActions = async ({ diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Date/DateFilter.component.js b/src/core_modules/capture-core/components/FiltersForTypes/Date/DateFilter.component.js index 09ce95ce70..44cc5db04e 100644 --- a/src/core_modules/capture-core/components/FiltersForTypes/Date/DateFilter.component.js +++ b/src/core_modules/capture-core/components/FiltersForTypes/Date/DateFilter.component.js @@ -10,7 +10,7 @@ import { Option } from '../../../metaData/OptionSet/Option'; import { FromDateFilter } from './From.component'; import { ToDateFilter } from './To.component'; -import { isValidDate } from '../../../utils/validators/form'; +import { isValidDate } from '../../../utils/validation/validators/form'; import { parseDate } from '../../../utils/converters/date'; import { dataElementTypes } from '../../../metaData'; import type { UpdatableFilterContent } from '../types'; diff --git a/src/core_modules/capture-core/components/FiltersForTypes/Date/DateFilterManager.component.js b/src/core_modules/capture-core/components/FiltersForTypes/Date/DateFilterManager.component.js index da08dcfca8..3625c5a70e 100644 --- a/src/core_modules/capture-core/components/FiltersForTypes/Date/DateFilterManager.component.js +++ b/src/core_modules/capture-core/components/FiltersForTypes/Date/DateFilterManager.component.js @@ -8,7 +8,7 @@ import { mainOptionKeys } from './options'; import { dateFilterTypes } from './constants'; import type { DateFilterData } from './types'; import type { Value } from './DateFilter.component'; -import { areRelativeRangeValuesSupported } from '../../../utils/validators/areRelativeRangeValuesSupported'; +import { areRelativeRangeValuesSupported } from '../../../utils/validation/validators/areRelativeRangeValuesSupported'; type Props = { filter: ?DateFilterData, diff --git a/src/core_modules/capture-core/components/ListView/Filters/FilterButton/buttonTextBuilder/converters/dateConverter.js b/src/core_modules/capture-core/components/ListView/Filters/FilterButton/buttonTextBuilder/converters/dateConverter.js index 3ff9d3289b..4dce8ba7a9 100644 --- a/src/core_modules/capture-core/components/ListView/Filters/FilterButton/buttonTextBuilder/converters/dateConverter.js +++ b/src/core_modules/capture-core/components/ListView/Filters/FilterButton/buttonTextBuilder/converters/dateConverter.js @@ -4,7 +4,8 @@ import { pipe } from 'capture-core-utils'; import moment from 'moment'; import { convertMomentToDateFormatString } from '../../../../../../utils/converters/date'; import type { DateFilterData, AbsoluteDateFilterData } from '../../../../../FiltersForTypes'; -import { areRelativeRangeValuesSupported } from '../../../../../../utils/validators/areRelativeRangeValuesSupported'; +import { areRelativeRangeValuesSupported } + from '../../../../../../utils/validation/validators/areRelativeRangeValuesSupported'; const periods = { TODAY: 'TODAY', diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/epics/dataEntryRules.epics.js b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/epics/dataEntryRules.epics.js index fe7a69ca2b..f582f16368 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/epics/dataEntryRules.epics.js +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/epics/dataEntryRules.epics.js @@ -16,12 +16,12 @@ import { getCurrentClientMainData, getApplicableRuleEffectsForTrackerProgram, updateRulesEffects, + validateAssignEffects, type FieldData, } from '../../../../rules'; import { getDataEntryKey } from '../../../DataEntry/common/getDataEntryKey'; import type { RulesExecutionDependenciesClientFormatted } from '../../common.types'; import { getLocationQuery } from '../../../../utils/routing'; -import { validateAssignEffects } from '../../../D2Form'; import type { QuerySingleResource } from '../../../../utils/api'; const runRulesForNewEvent = async ({ diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/fieldValidators/eventDate.validatorContainersGetter.js b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/fieldValidators/eventDate.validatorContainersGetter.js index 8bda3e79ef..8749ca4701 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/fieldValidators/eventDate.validatorContainersGetter.js +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/fieldValidators/eventDate.validatorContainersGetter.js @@ -1,7 +1,7 @@ // @flow import { hasValue } from 'capture-core-utils/validators/form'; import i18n from '@dhis2/d2-i18n'; -import { isValidDate } from '../../../../utils/validators/form'; +import { isValidDate } from '../../../../utils/validation/validators/form'; const preValidateDate = (value?: ?string) => { if (!value) { diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/helpers/getRulesActions.js b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/helpers/getRulesActions.js index 7fd5123258..fb79019f8d 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/helpers/getRulesActions.js +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/helpers/getRulesActions.js @@ -6,10 +6,10 @@ import { getCurrentClientMainData, getApplicableRuleEffectsForTrackerProgram, updateRulesEffects, + validateAssignEffects, } from '../../../../rules'; import type { RenderFoundation, TrackerProgram, ProgramStage } from '../../../../metaData'; import type { EnrollmentEvents, AttributeValuesClientFormatted, EnrollmentData } from '../../common.types'; -import { validateAssignEffects } from '../../../D2Form'; import type { QuerySingleResource } from '../../../../utils/api'; export const getRulesActions = async ({ diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/editEventDataEntry.actions.js b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/editEventDataEntry.actions.js index f39254fc87..c994073f08 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/editEventDataEntry.actions.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/editEventDataEntry.actions.js @@ -7,6 +7,7 @@ import { getApplicableRuleEffectsForEventProgram, getApplicableRuleEffectsForTrackerProgram, updateRulesEffects, + validateAssignEffects, } from '../../../rules'; import { RenderFoundation, Program } from '../../../metaData'; import { getEventDateValidatorContainers } from './fieldValidators/eventDate.validatorContainersGetter'; @@ -28,7 +29,6 @@ import { getEnrollmentForRulesEngine, getAttributeValuesForRulesEngine } from '. import type { EnrollmentData, AttributeValue } from '../../Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData'; import { prepareEnrollmentEventsForRulesEngine } from '../../../events/prepareEnrollmentEvents'; import type { ProgramCategory } from '../../WidgetEventSchedule/CategoryOptions/CategoryOptions.types'; -import { validateAssignEffects } from '../../D2Form'; import type { QuerySingleResource } from '../../../utils/api'; export const batchActionTypes = { diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/epics/editEventDataEntry.epics.js b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/epics/editEventDataEntry.epics.js index 101e95b615..1eebb5e6bf 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/epics/editEventDataEntry.epics.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/epics/editEventDataEntry.epics.js @@ -17,6 +17,7 @@ import { getApplicableRuleEffectsForEventProgram, getApplicableRuleEffectsForTrackerProgram, updateRulesEffects, + validateAssignEffects, type FieldData, } from '../../../../rules'; import { getStageFromEvent } from '../../../../metaData/helpers/getStageFromEvent'; @@ -24,7 +25,6 @@ import { EventProgram, TrackerProgram } from '../../../../metaData/Program'; import { getDataEntryKey } from '../../../DataEntry/common/getDataEntryKey'; import { prepareEnrollmentEventsForRulesEngine } from '../../../../events/prepareEnrollmentEvents'; import { getEnrollmentForRulesEngine, getAttributeValuesForRulesEngine } from '../../helpers'; -import { validateAssignEffects } from '../../../D2Form'; import type { QuerySingleResource } from '../../../../utils/api'; const runRulesForEditSingleEvent = async ({ diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/fieldValidators/eventDate.validatorContainersGetter.js b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/fieldValidators/eventDate.validatorContainersGetter.js index 821d0c2752..752313d41b 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/fieldValidators/eventDate.validatorContainersGetter.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/fieldValidators/eventDate.validatorContainersGetter.js @@ -1,7 +1,7 @@ // @flow import { hasValue } from 'capture-core-utils/validators/form'; import i18n from '@dhis2/d2-i18n'; -import { isValidDate } from '../../../../utils/validators/form'; +import { isValidDate } from '../../../../utils/validation/validators/form'; const preValidateDate = (value?: ?string) => { if (!value) { diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.js index a781f92ca2..f680bd7dee 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.js @@ -12,9 +12,13 @@ import type { } from '@dhis2/rules-engine-javascript'; import { rulesEngine } from '../../../../rules/rulesEngine'; import type { RenderFoundation } from '../../../../metaData'; -import { updateRulesEffects, postProcessRulesEffects, buildEffectsHierarchy } from '../../../../rules'; +import { + updateRulesEffects, + postProcessRulesEffects, + buildEffectsHierarchy, + validateAssignEffects, +} from '../../../../rules'; import type { QuerySingleResource } from '../../../../utils/api'; -import { validateAssignEffects } from '../../../D2Form'; const getEnrollmentForRulesExecution = enrollment => enrollment && { diff --git a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/helpers/eventFilters/apiEventFilterToClientConfigConverter/convertToClientConfig.js b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/helpers/eventFilters/apiEventFilterToClientConfigConverter/convertToClientConfig.js index 42bdd39aee..d34d81ab1e 100644 --- a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/helpers/eventFilters/apiEventFilterToClientConfigConverter/convertToClientConfig.js +++ b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/helpers/eventFilters/apiEventFilterToClientConfigConverter/convertToClientConfig.js @@ -26,7 +26,8 @@ import type { ColumnsMetaForDataFetching, ClientConfig, } from '../../../types'; -import { areRelativeRangeValuesSupported } from '../../../../../../utils/validators/areRelativeRangeValuesSupported'; +import { areRelativeRangeValuesSupported } + from '../../../../../../utils/validation/validators/areRelativeRangeValuesSupported'; const getTextFilter = (filter: ApiDataFilterText): TextFilterData => { const value = filter.like; diff --git a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/helpers/TEIFilters/apiTEIFilterToClientConfigConverter/convertToClientFilters.js b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/helpers/TEIFilters/apiTEIFilterToClientConfigConverter/convertToClientFilters.js index 6730b1eba4..33863ea4f5 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/helpers/TEIFilters/apiTEIFilterToClientConfigConverter/convertToClientFilters.js +++ b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/helpers/TEIFilters/apiTEIFilterToClientConfigConverter/convertToClientFilters.js @@ -19,7 +19,8 @@ import type { ApiTrackerQueryCriteria, TeiColumnsMetaForDataFetching, } from '../../../types'; -import { areRelativeRangeValuesSupported } from '../../../../../../utils/validators/areRelativeRangeValuesSupported'; +import { areRelativeRangeValuesSupported } + from '../../../../../../utils/validation/validators/areRelativeRangeValuesSupported'; import { DATE_TYPES, ASSIGNEE_MODES, MAIN_FILTERS } from '../../../constants'; import { ADDITIONAL_FILTERS } from '../../../helpers'; diff --git a/src/core_modules/capture-core/components/WorkingLists/WorkingListsCommon/helpers/buildFilterQueryArgs/filterConverters/dateConverter.js b/src/core_modules/capture-core/components/WorkingLists/WorkingListsCommon/helpers/buildFilterQueryArgs/filterConverters/dateConverter.js index c5c59e7675..4930ba75bb 100644 --- a/src/core_modules/capture-core/components/WorkingLists/WorkingListsCommon/helpers/buildFilterQueryArgs/filterConverters/dateConverter.js +++ b/src/core_modules/capture-core/components/WorkingLists/WorkingListsCommon/helpers/buildFilterQueryArgs/filterConverters/dateConverter.js @@ -8,7 +8,8 @@ import type { RelativeDateFilterData, AbsoluteDateFilterData, } from '../../../../../ListView'; -import { areRelativeRangeValuesSupported } from '../../../../../../utils/validators/areRelativeRangeValuesSupported'; +import { areRelativeRangeValuesSupported } + from '../../../../../../utils/validation/validators/areRelativeRangeValuesSupported'; const periods = { TODAY: 'TODAY', diff --git a/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js index 5e5441ab5b..36bb1a0872 100644 --- a/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js @@ -1,6 +1,6 @@ // @flow import { effectActions } from '@dhis2/rules-engine-javascript'; -import type { AssignOutputEffectWithValidations } from '../../components/D2Form'; +import type { AssignOutputEffectWithValidations } from '../../rules'; import { createReducerDescription } from '../../trackerRedux'; import { asyncHandlerActionTypes } from '../../components/D2Form'; import { actionTypes as fieldActionTypes } from '../../components/D2Form/D2SectionFields.actions'; diff --git a/src/core_modules/capture-core/rules/index.js b/src/core_modules/capture-core/rules/index.js index 7eedf9ff6d..96467328c2 100644 --- a/src/core_modules/capture-core/rules/index.js +++ b/src/core_modules/capture-core/rules/index.js @@ -10,3 +10,5 @@ export type { FieldData } from './inputHelpers'; export { postProcessRulesEffects } from './postProcessRulesEffects'; export { buildEffectsHierarchy } from './buildEffectsHierarchy'; export { filterApplicableRuleEffects } from './filterApplicableRuleEffects'; +export { validateAssignEffects } from './validateAssignEffects'; +export type { AssignOutputEffectWithValidations } from './validateAssignEffects'; diff --git a/src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js b/src/core_modules/capture-core/rules/validateAssignEffects.js similarity index 87% rename from src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js rename to src/core_modules/capture-core/rules/validateAssignEffects.js index dde69b4cce..c34cee441b 100644 --- a/src/core_modules/capture-core/components/D2Form/field/validators/validateAssignEffects.js +++ b/src/core_modules/capture-core/rules/validateAssignEffects.js @@ -3,11 +3,11 @@ import { errorCreator } from 'capture-core-utils'; import { effectActions } from '@dhis2/rules-engine-javascript'; import log from 'loglevel'; import type { AssignOutputEffect } from '@dhis2/rules-engine-javascript'; -import { type DataElement } from '../../../../metaData'; -import type { QuerySingleResource } from '../../../../utils/api'; -import { getValidators } from './getValidators'; -import type { Validations } from './validateValue'; -import { validateValue } from './validateValue'; +import { type DataElement } from '../metaData'; +import type { QuerySingleResource } from '../utils/api'; +import { getValidators } from '../utils/validation/getValidators'; +import type { Validations } from '../utils/validation/validateValue'; +import { validateValue } from '../utils/validation/validateValue'; export type AssignOutputEffectWithValidations = { [metaDataId: string]: Array, diff --git a/src/core_modules/capture-core/components/D2Form/field/validators/constants/index.js b/src/core_modules/capture-core/utils/validation/constants/index.js similarity index 100% rename from src/core_modules/capture-core/components/D2Form/field/validators/constants/index.js rename to src/core_modules/capture-core/utils/validation/constants/index.js diff --git a/src/core_modules/capture-core/components/D2Form/field/validators/constants/validatorTypes.const.js b/src/core_modules/capture-core/utils/validation/constants/validatorTypes.const.js similarity index 100% rename from src/core_modules/capture-core/components/D2Form/field/validators/constants/validatorTypes.const.js rename to src/core_modules/capture-core/utils/validation/constants/validatorTypes.const.js diff --git a/src/core_modules/capture-core/components/D2Form/field/validators/getValidators.js b/src/core_modules/capture-core/utils/validation/getValidators.js similarity index 98% rename from src/core_modules/capture-core/components/D2Form/field/validators/getValidators.js rename to src/core_modules/capture-core/utils/validation/getValidators.js index 7b7a8b5aef..c899321f24 100644 --- a/src/core_modules/capture-core/components/D2Form/field/validators/getValidators.js +++ b/src/core_modules/capture-core/utils/validation/getValidators.js @@ -25,10 +25,10 @@ import { getDateRangeValidator, getDateTimeRangeValidator, getTimeRangeValidator, -} from '../../../../utils/validators/form'; -import { dataElementTypes, type DateDataElement, type DataElement } from '../../../../metaData'; +} from './validators/form'; +import { dataElementTypes, type DateDataElement, type DataElement } from '../../metaData'; import { validatorTypes } from './constants'; -import type { QuerySingleResource } from '../../../../utils/api/api.types'; +import type { QuerySingleResource } from '../../utils/api/api.types'; type Validator = ( value: any, diff --git a/src/core_modules/capture-core/components/D2Form/field/validators/index.js b/src/core_modules/capture-core/utils/validation/index.js similarity index 53% rename from src/core_modules/capture-core/components/D2Form/field/validators/index.js rename to src/core_modules/capture-core/utils/validation/index.js index b9bf19b05e..58310084b3 100644 --- a/src/core_modules/capture-core/components/D2Form/field/validators/index.js +++ b/src/core_modules/capture-core/utils/validation/index.js @@ -1,7 +1,5 @@ // @flow export { getValidators } from './getValidators'; export { validateValue } from './validateValue'; -export { validateAssignEffects } from './validateAssignEffects'; export type { ValidatorContainer } from './getValidators'; -export type { AssignOutputEffectWithValidations } from './validateAssignEffects'; diff --git a/src/core_modules/capture-core/components/D2Form/field/validators/validateValue.js b/src/core_modules/capture-core/utils/validation/validateValue.js similarity index 100% rename from src/core_modules/capture-core/components/D2Form/field/validators/validateValue.js rename to src/core_modules/capture-core/utils/validation/validateValue.js diff --git a/src/core_modules/capture-core/utils/validators/areRelativeRangeValuesSupported.js b/src/core_modules/capture-core/utils/validation/validators/areRelativeRangeValuesSupported.js similarity index 100% rename from src/core_modules/capture-core/utils/validators/areRelativeRangeValuesSupported.js rename to src/core_modules/capture-core/utils/validation/validators/areRelativeRangeValuesSupported.js diff --git a/src/core_modules/capture-core/utils/validators/form/ageValidator.js b/src/core_modules/capture-core/utils/validation/validators/form/ageValidator.js similarity index 76% rename from src/core_modules/capture-core/utils/validators/form/ageValidator.js rename to src/core_modules/capture-core/utils/validation/validators/form/ageValidator.js index f2653017fc..99690c1686 100644 --- a/src/core_modules/capture-core/utils/validators/form/ageValidator.js +++ b/src/core_modules/capture-core/utils/validation/validators/form/ageValidator.js @@ -1,6 +1,6 @@ // @flow import { isValidAge as isValidAgeCore } from 'capture-core-utils/validators/form'; -import { systemSettingsStore } from '../../../metaDataMemoryStores'; +import { systemSettingsStore } from '../../../../metaDataMemoryStores'; export function isValidAge(value: Object) { const format = systemSettingsStore.get().dateFormat; diff --git a/src/core_modules/capture-core/utils/validators/form/dateTimeValidator.js b/src/core_modules/capture-core/utils/validation/validators/form/dateTimeValidator.js similarity index 78% rename from src/core_modules/capture-core/utils/validators/form/dateTimeValidator.js rename to src/core_modules/capture-core/utils/validation/validators/form/dateTimeValidator.js index 5db8270a2b..66362c95ee 100644 --- a/src/core_modules/capture-core/utils/validators/form/dateTimeValidator.js +++ b/src/core_modules/capture-core/utils/validation/validators/form/dateTimeValidator.js @@ -1,6 +1,6 @@ // @flow import { isValidDateTime as isValidDateTimeCore } from 'capture-core-utils/validators/form'; -import { systemSettingsStore } from '../../../metaDataMemoryStores'; +import { systemSettingsStore } from '../../../../metaDataMemoryStores'; export function isValidDateTime(value: Object) { const dateFormat = systemSettingsStore.get().dateFormat; diff --git a/src/core_modules/capture-core/utils/validators/form/dateValidator.js b/src/core_modules/capture-core/utils/validation/validators/form/dateValidator.js similarity index 77% rename from src/core_modules/capture-core/utils/validators/form/dateValidator.js rename to src/core_modules/capture-core/utils/validation/validators/form/dateValidator.js index 65416c8532..2ccf681fe1 100644 --- a/src/core_modules/capture-core/utils/validators/form/dateValidator.js +++ b/src/core_modules/capture-core/utils/validation/validators/form/dateValidator.js @@ -1,6 +1,6 @@ // @flow import { isValidDate as isValidDateCore } from 'capture-core-utils/validators/form'; -import { systemSettingsStore } from '../../../metaDataMemoryStores'; +import { systemSettingsStore } from '../../../../metaDataMemoryStores'; export function isValidDate(value: string) { const format = systemSettingsStore.get().dateFormat; diff --git a/src/core_modules/capture-core/utils/validators/form/getDateRangeValidator.js b/src/core_modules/capture-core/utils/validation/validators/form/getDateRangeValidator.js similarity index 94% rename from src/core_modules/capture-core/utils/validators/form/getDateRangeValidator.js rename to src/core_modules/capture-core/utils/validation/validators/form/getDateRangeValidator.js index e82e219f27..c76d738184 100644 --- a/src/core_modules/capture-core/utils/validators/form/getDateRangeValidator.js +++ b/src/core_modules/capture-core/utils/validation/validators/form/getDateRangeValidator.js @@ -1,6 +1,6 @@ // @flow import { isValidDate } from './dateValidator'; -import { parseDate } from '../../converters/date'; +import { parseDate } from '../../../converters/date'; /** * * @export diff --git a/src/core_modules/capture-core/utils/validators/form/getDateTimeRangeValidator.js b/src/core_modules/capture-core/utils/validation/validators/form/getDateTimeRangeValidator.js similarity index 97% rename from src/core_modules/capture-core/utils/validators/form/getDateTimeRangeValidator.js rename to src/core_modules/capture-core/utils/validation/validators/form/getDateTimeRangeValidator.js index 0953479053..154749b666 100644 --- a/src/core_modules/capture-core/utils/validators/form/getDateTimeRangeValidator.js +++ b/src/core_modules/capture-core/utils/validation/validators/form/getDateTimeRangeValidator.js @@ -1,6 +1,6 @@ // @flow import { isValidDateTime } from './dateTimeValidator'; -import { parseDate } from '../../converters/date'; +import { parseDate } from '../../../converters/date'; function isValidDateTimeWithEmptyCheck(value: ?Object) { return value && isValidDateTime(value); diff --git a/src/core_modules/capture-core/utils/validators/form/getNumberRangeValidator.js b/src/core_modules/capture-core/utils/validation/validators/form/getNumberRangeValidator.js similarity index 100% rename from src/core_modules/capture-core/utils/validators/form/getNumberRangeValidator.js rename to src/core_modules/capture-core/utils/validation/validators/form/getNumberRangeValidator.js diff --git a/src/core_modules/capture-core/utils/validators/form/getTimeRangeValidator.js b/src/core_modules/capture-core/utils/validation/validators/form/getTimeRangeValidator.js similarity index 100% rename from src/core_modules/capture-core/utils/validators/form/getTimeRangeValidator.js rename to src/core_modules/capture-core/utils/validation/validators/form/getTimeRangeValidator.js diff --git a/src/core_modules/capture-core/utils/validators/form/index.js b/src/core_modules/capture-core/utils/validation/validators/form/index.js similarity index 100% rename from src/core_modules/capture-core/utils/validators/form/index.js rename to src/core_modules/capture-core/utils/validation/validators/form/index.js diff --git a/src/core_modules/capture-core/utils/validators/form/isValidNonFutureDate.js b/src/core_modules/capture-core/utils/validation/validators/form/isValidNonFutureDate.js similarity index 89% rename from src/core_modules/capture-core/utils/validators/form/isValidNonFutureDate.js rename to src/core_modules/capture-core/utils/validation/validators/form/isValidNonFutureDate.js index 3827f2ee12..e20aa52c83 100644 --- a/src/core_modules/capture-core/utils/validators/form/isValidNonFutureDate.js +++ b/src/core_modules/capture-core/utils/validation/validators/form/isValidNonFutureDate.js @@ -1,7 +1,7 @@ // @flow import i18n from '@dhis2/d2-i18n'; import moment from 'moment'; -import { parseDate } from '../../converters/date'; +import { parseDate } from '../../../converters/date'; export const isValidNonFutureDate = (value: string) => { const { isValid, momentDate } = parseDate(value); From 0c6a995fba037895b2c0592d4626029af743dbec Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Thu, 10 Oct 2024 09:01:11 +0200 Subject: [PATCH 13/22] chore: rename callback --- .../capture-core/utils/validation/validateValue.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core_modules/capture-core/utils/validation/validateValue.js b/src/core_modules/capture-core/utils/validation/validateValue.js index c057573a18..e213496005 100644 --- a/src/core_modules/capture-core/utils/validation/validateValue.js +++ b/src/core_modules/capture-core/utils/validation/validateValue.js @@ -12,7 +12,7 @@ export const validateValue = async ( { validators }: { validators?: Array }, value: any, validationContext: ?Object, - onIsValidatingInternal: ?Function, + postProcessAsyncValidatonInitiation: ?Function, ): Promise => { if (!validators || validators.length === 0) { return { @@ -25,8 +25,8 @@ export const validateValue = async ( if (pass === true) { let result = currentValidator.validator(value, validationContext); if (result instanceof Promise) { - result = onIsValidatingInternal - ? onIsValidatingInternal(currentValidator.validatingMessage, result) + result = postProcessAsyncValidatonInitiation + ? postProcessAsyncValidatonInitiation(currentValidator.validatingMessage, result) : result; result = await result; } From f18f2c7f3a94331792ff892a52ad2a2e772d970e Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Thu, 10 Oct 2024 09:02:41 +0200 Subject: [PATCH 14/22] chore: remove object destructering --- .../components/D2Form/FormBuilder/FormBuilder.component.js | 5 +++-- src/core_modules/capture-core/rules/validateAssignEffects.js | 2 +- .../capture-core/utils/validation/validateValue.js | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js b/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js index 57f90b086a..b8a8fda6b1 100644 --- a/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js +++ b/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js @@ -165,8 +165,9 @@ export class FormBuilder extends React.Component { let validationData; try { + const { validators } = field; validationData = await validateValue( - field, + validators, values[field.id], validationContext, handleIsValidatingInternal, @@ -376,7 +377,7 @@ export class FormBuilder extends React.Component { this.commitUpdateTriggeredForFields[fieldId] = true; const updatePromise = validateValue( - { validators }, + validators, value, onGetValidationContext && onGetValidationContext(), handleIsValidatingInternal, diff --git a/src/core_modules/capture-core/rules/validateAssignEffects.js b/src/core_modules/capture-core/rules/validateAssignEffects.js index c34cee441b..183bee1e42 100644 --- a/src/core_modules/capture-core/rules/validateAssignEffects.js +++ b/src/core_modules/capture-core/rules/validateAssignEffects.js @@ -42,7 +42,7 @@ export const validateAssignEffects = async ({ const validationContext = onGetValidationContext && onGetValidationContext(); try { - const validatorResult = await validateValue({ validators }, value, validationContext); + const validatorResult = await validateValue(validators, value, validationContext); const effectWithValidation = Object.assign({}, effectsForId[lastIndex], validatorResult); acc[metaData.id] = [effectWithValidation]; diff --git a/src/core_modules/capture-core/utils/validation/validateValue.js b/src/core_modules/capture-core/utils/validation/validateValue.js index e213496005..d4474c7b41 100644 --- a/src/core_modules/capture-core/utils/validation/validateValue.js +++ b/src/core_modules/capture-core/utils/validation/validateValue.js @@ -9,7 +9,7 @@ export type Validations = { }; export const validateValue = async ( - { validators }: { validators?: Array }, + validators?: Array, value: any, validationContext: ?Object, postProcessAsyncValidatonInitiation: ?Function, From 87d0d7867ad74ecbc766b282f09a988a46c5f5a3 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Wed, 23 Oct 2024 13:55:59 +0200 Subject: [PATCH 15/22] fix: properly update dataEntriesInProgressList while async validation is running --- .../Enrollment/actions/open.actionBatchs.js | 2 +- .../DataEntries/Enrollment/index.js | 1 + .../hooks/useLifecycle.js | 5 +++ .../DataEntry/helpers/getRulesActions.js | 8 ++++- .../DataEntryWrapper/DataEntry/index.js | 1 + .../DataEntryWrapper/useRulesEngine.js | 9 ++++-- .../DataEntry/actions/dataEntry.actions.js | 15 +++++++++ .../components/DataEntry/index.js | 3 ++ .../DataEntry/helpers/getRulesActions.js | 8 ++++- .../Validated/useLifecycle.js | 7 ++++- .../WidgetEventEdit.container.js | 8 ++++- .../dataEntry.reducerDescription.js | 31 +++++++++++++++++++ 12 files changed, 91 insertions(+), 7 deletions(-) diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.js index e689bc34ff..7804c49e50 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.js @@ -22,7 +22,7 @@ import type { ProgramCategory } from '../../../WidgetEventSchedule/CategoryOptio import { getDataEntryPropsToInclude } from '../EnrollmentWithFirstStageDataEntry'; import type { QuerySingleResource } from '../../../../utils/api'; -const itemId = 'newEnrollment'; +export const itemId = 'newEnrollment'; type DataEntryPropsToInclude = Array; diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/index.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/index.js index dcbc06780b..9c1cda8b89 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/index.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/index.js @@ -5,6 +5,7 @@ export { actionTypes as openActionTypes } from './actions/open.actions'; export { batchActionTypes as openBatchActionTypes, openDataEntryForNewEnrollmentBatchAsync, + itemId, } from './actions/open.actionBatchs'; export { batchActionTypes as enrollmentBatchActionTypes, diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useLifecycle.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useLifecycle.js index a9b0eda796..bce6700c5a 100644 --- a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useLifecycle.js +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useLifecycle.js @@ -1,4 +1,5 @@ // @flow +import { v4 as uuid } from 'uuid'; import { useDispatch, useSelector } from 'react-redux'; import { useEffect, useRef } from 'react'; import type { OrgUnit } from '@dhis2/rules-engine-javascript'; @@ -11,6 +12,8 @@ import { useBuildFirstStageRegistration } from './useBuildFirstStageRegistration import { useMetadataForRegistrationForm } from '../../common/TEIAndEnrollment/useMetadataForRegistrationForm'; import { useCategoryCombinations } from '../../../DataEntryDhis2Helpers/AOC/useCategoryCombinations'; import { useMergeFormFoundationsIfApplicable } from './useMergeFormFoundationsIfApplicable'; +import { startLoadDataEntry } from '../../../DataEntry'; +import { itemId } from '../../Enrollment'; export const useLifecycle = ( selectedScopeId: string, @@ -56,6 +59,8 @@ export const useLifecycle = ( formFoundation ) { dataEntryReadyRef.current = true; + const uid = uuid(); + dispatch(startLoadDataEntry(dataEntryId, itemId, uid)); dispatch( startNewEnrollmentDataEntryInitialisation({ selectedOrgUnit: orgUnit, diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/helpers/getRulesActions.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/helpers/getRulesActions.js index 62375d1ce1..a052465d9e 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/helpers/getRulesActions.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/helpers/getRulesActions.js @@ -10,6 +10,7 @@ import { import type { RenderFoundation, EventProgram } from '../../../../../../metaData'; import { dataEntryId, itemId, formId } from './constants'; import type { QuerySingleResource } from '../../../../../../utils/api'; +import { rulesExecutedPostLoadDataEntry } from '../../../../../DataEntry'; export const getRulesActions = async ({ state, // temporary @@ -17,12 +18,14 @@ export const getRulesActions = async ({ formFoundation, orgUnit, querySingleResource, + uid, }: { state: ReduxState, program: EventProgram, formFoundation: RenderFoundation, orgUnit: OrgUnit, querySingleResource: QuerySingleResource, + uid: string, }) => { const formValuesClient = getCurrentClientValues(state, formFoundation, formId); const dataEntryValuesClient = getCurrentClientMainData(state, itemId, dataEntryId, formFoundation); @@ -40,5 +43,8 @@ export const getRulesActions = async ({ querySingleResource, }); - return updateRulesEffects(effectsWithValidations, formId); + return [ + updateRulesEffects(effectsWithValidations, formId), + rulesExecutedPostLoadDataEntry(dataEntryId, itemId, uid), + ]; }; diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/index.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/index.js index 47038dcfc7..8e4453d8b3 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/index.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/index.js @@ -3,3 +3,4 @@ export { DataEntry } from './DataEntry.container'; export { getOpenDataEntryActions } from './helpers/getOpenDataEntryActions'; export { getRulesActions } from './helpers/getRulesActions'; +export { dataEntryId, itemId } from './helpers/constants'; diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/useRulesEngine.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/useRulesEngine.js index f59400ebcc..dec1061b8b 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/useRulesEngine.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/useRulesEngine.js @@ -1,4 +1,5 @@ // @flow +import { v4 as uuid } from 'uuid'; import { useEffect, useRef, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useDataEngine } from '@dhis2/app-runtime'; @@ -6,8 +7,9 @@ import { makeQuerySingleResource } from 'capture-core/utils/api'; import { batchActions } from 'redux-batched-actions'; import type { OrgUnit } from '@dhis2/rules-engine-javascript'; import { getEventProgramThrowIfNotFound } from '../../../../metaData'; -import { getRulesActions } from './DataEntry'; +import { getRulesActions, dataEntryId, itemId } from './DataEntry'; import type { RenderFoundation } from '../../../../metaData'; +import { startRunRulesPostLoadDataEntry } from '../../../DataEntry'; export const useRulesEngine = ({ programId, @@ -29,6 +31,8 @@ export const useRulesEngine = ({ const state = useSelector(stateArg => stateArg); useEffect(() => { if (orgUnit && program && !!formFoundation) { + const uid = uuid(); + dispatch(startRunRulesPostLoadDataEntry(dataEntryId, itemId, uid)); const querySingleResource = makeQuerySingleResource(dataEngine.query.bind(dataEngine)); getRulesActions({ state, @@ -36,7 +40,8 @@ export const useRulesEngine = ({ orgUnit, formFoundation, querySingleResource, - }).then(rulesActions => dispatch(batchActions([rulesActions]))); + uid, + }).then(rulesActions => dispatch(batchActions(rulesActions))); orgUnitRef.current = orgUnit; } // Ignoring state (due to various reasons, bottom line being that field updates are handled in epic) diff --git a/src/core_modules/capture-core/components/DataEntry/actions/dataEntry.actions.js b/src/core_modules/capture-core/components/DataEntry/actions/dataEntry.actions.js index 5b5bf1a6f6..a30f38c8ec 100644 --- a/src/core_modules/capture-core/components/DataEntry/actions/dataEntry.actions.js +++ b/src/core_modules/capture-core/components/DataEntry/actions/dataEntry.actions.js @@ -18,16 +18,19 @@ export const actionTypes = { SAVE_ABORT: 'SaveAbortedForDataEntry', UPDATE_FIELD: 'UpdateDataEntryField', UPDATE_FORM_FIELD: 'UpdateDataEntryFormField', + RULES_EXECUTED_POST_LOAD_DATA_ENTRY: 'RulesExecutedPostLoadDataEntry', RULES_EXECUTED_POST_UPDATE_FIELD: 'RulesExecutedPostUpdateFieldDataEntry', ADD_DATA_ENTRY_NOTE: 'AddDataEntryNote', REMOVE_DATA_ENTRY_NOTE: 'RemoveDataEntryNote', SET_CURRENT_DATA_ENTRY: 'SetCurrentDataEntry', + START_RUN_RULES_POST_LOAD_DATA_ENTRY: 'StartRunRulesPostLoadDataEntry', START_RUN_RULES_POST_UPDATE_FIELD: 'StartRunRulesPostUpdateFieldDataEntry', REMOVE_DATA_ENTRY_RELATIONSHIP: 'RemoveDataEntryRelationship', ADD_DATA_ENTRY_RELATIONSHIP: 'AddDataEntryRelationship', DATA_ENTRY_RELATIONSHIP_ALREADY_EXISTS: 'DataEntryRelationshipAlreadyExists', LOAD_EDIT_DATA_ENTRY: 'LoadEditDataEntry', CLEAN_UP_DATA_ENTRY: 'CleanUpDataEntry', + START_LOAD_DATA_ENTRY: 'StartLoadDataEntry', }; // COMPLETE @@ -116,6 +119,18 @@ export const updateFormField = updateCompleteUid, }); +export const startLoadDataEntry = + (dataEntryId: string, itemId: string, uid: string) => + actionCreator(actionTypes.START_LOAD_DATA_ENTRY)({ dataEntryId, itemId, uid }); + +export const startRunRulesPostLoadDataEntry = + (dataEntryId: string, itemId: string, uid: string) => + actionCreator(actionTypes.START_RUN_RULES_POST_LOAD_DATA_ENTRY)({ dataEntryId, itemId, uid }); + +export const rulesExecutedPostLoadDataEntry = + (dataEntryId: string, itemId: string, uid: string) => + actionCreator(actionTypes.RULES_EXECUTED_POST_LOAD_DATA_ENTRY)({ dataEntryId, itemId, uid }); + export const startRunRulesPostUpdateField = (dataEntryId: string, itemId: string, uid: string) => actionCreator(actionTypes.START_RUN_RULES_POST_UPDATE_FIELD)({ dataEntryId, itemId, uid }); diff --git a/src/core_modules/capture-core/components/DataEntry/index.js b/src/core_modules/capture-core/components/DataEntry/index.js index 97ce464d1a..369cfad117 100644 --- a/src/core_modules/capture-core/components/DataEntry/index.js +++ b/src/core_modules/capture-core/components/DataEntry/index.js @@ -25,8 +25,11 @@ export { placements } from './constants/placements.const'; // actions export { actionTypes as mainActionTypes, + startLoadDataEntry, startRunRulesPostUpdateField, + startRunRulesPostLoadDataEntry, rulesExecutedPostUpdateField, + rulesExecutedPostLoadDataEntry, } from './actions/dataEntry.actions'; export { actionTypes as loadNewActionTypes } from './actions/dataEntryLoadNew.actions'; export { actionTypes as loadEditActionTypes, cleanUpDataEntry } from './actions/dataEntry.actions'; diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/helpers/getRulesActions.js b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/helpers/getRulesActions.js index fb79019f8d..16575f74dd 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/helpers/getRulesActions.js +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/helpers/getRulesActions.js @@ -11,6 +11,7 @@ import { import type { RenderFoundation, TrackerProgram, ProgramStage } from '../../../../metaData'; import type { EnrollmentEvents, AttributeValuesClientFormatted, EnrollmentData } from '../../common.types'; import type { QuerySingleResource } from '../../../../utils/api'; +import { rulesExecutedPostLoadDataEntry } from '../../../DataEntry'; export const getRulesActions = async ({ state, // temporary @@ -24,6 +25,7 @@ export const getRulesActions = async ({ attributesValuesRulesDependency, enrollmentDataRulesDependency, querySingleResource, + uid, }: { state: ReduxState, program: TrackerProgram, @@ -36,6 +38,7 @@ export const getRulesActions = async ({ attributesValuesRulesDependency: AttributeValuesClientFormatted, enrollmentDataRulesDependency: EnrollmentData, querySingleResource: QuerySingleResource, + uid: string, }) => { const formId = getDataEntryKey(dataEntryId, itemId); @@ -59,5 +62,8 @@ export const getRulesActions = async ({ querySingleResource, }); - return updateRulesEffects(effectsWithValidations, formId); + return [ + updateRulesEffects(effectsWithValidations, formId), + rulesExecutedPostLoadDataEntry(dataEntryId, itemId, uid), + ]; }; diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/useLifecycle.js b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/useLifecycle.js index 6fcd31e0e3..fa430e1731 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/useLifecycle.js +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/useLifecycle.js @@ -1,5 +1,6 @@ // @flow import { useEffect, useRef, useState } from 'react'; +import { v4 as uuid } from 'uuid'; import { useDispatch, useSelector } from 'react-redux'; import { useDataEngine } from '@dhis2/app-runtime'; import { makeQuerySingleResource } from 'capture-core/utils/api'; @@ -9,6 +10,7 @@ import { getOpenDataEntryActions, getRulesActions } from '../DataEntry'; import type { TrackerProgram, ProgramStage, RenderFoundation } from '../../../metaData'; import type { RulesExecutionDependenciesClientFormatted } from '../common.types'; import { useCategoryCombinations } from '../../DataEntryDhis2Helpers/AOC/useCategoryCombinations'; +import { startRunRulesPostLoadDataEntry } from '../../DataEntry'; export const useLifecycle = ({ program, @@ -65,6 +67,8 @@ export const useLifecycle = ({ delayRulesExecutionRef.current = false; setRulesExecutionTrigger(-rulesExecutionTrigger); } else { + const uid = uuid(); + dispatch(startRunRulesPostLoadDataEntry(dataEntryId, itemId, uid)); const querySingleResource = makeQuerySingleResource(dataEngine.query.bind(dataEngine)); getRulesActions({ @@ -79,7 +83,8 @@ export const useLifecycle = ({ attributesValuesRulesDependency, enrollmentDataRulesDependency, querySingleResource, - }).then(rulesActions => dispatch(batchActions([rulesActions]))); + uid, + }).then(rulesActions => dispatch(batchActions(rulesActions))); eventsRef.current = eventsRulesDependency; attributesRef.current = attributesValuesRulesDependency; enrollmentDataRef.current = enrollmentDataRulesDependency; diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js index f953852223..f2820c6d26 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js @@ -1,4 +1,5 @@ // @flow +import { v4 as uuid } from 'uuid'; import React, { type ComponentType, useState, useEffect } from 'react'; import { dataEntryIds, dataEntryKeys } from 'capture-core/constants'; import { useDispatch, useSelector } from 'react-redux'; @@ -33,6 +34,7 @@ import { FEATURES, useFeature } from '../../../capture-core-utils'; import { inMemoryFileStore } from '../DataEntry/file/inMemoryFileStore'; import { eventStatuses } from './constants/status.const'; import { useAuthorities } from './hooks'; +import { startLoadDataEntry } from '../DataEntry'; const styles = { header: { @@ -136,7 +138,11 @@ export const WidgetEventEditPlain = ({ secondary disabled={disableEdit} icon={} - onClick={() => dispatch(startShowEditEventDataEntry(orgUnit, programCategory))} + onClick={() => { + const uid = uuid(); + dispatch(startLoadDataEntry(dataEntryIds.ENROLLMENT_EVENT, dataEntryKeys.EDIT, uid)); + dispatch(startShowEditEventDataEntry(orgUnit, programCategory)); + }} > {i18n.t('Edit event')} diff --git a/src/core_modules/capture-core/reducers/descriptions/dataEntry.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/dataEntry.reducerDescription.js index 362c0c993d..695a7b6317 100644 --- a/src/core_modules/capture-core/reducers/descriptions/dataEntry.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/dataEntry.reducerDescription.js @@ -273,6 +273,17 @@ export const dataEntriesInProgressListDesc = createReducerDescription({ [key]: null, }; }, + [actionTypes.START_LOAD_DATA_ENTRY]: (state, action) => { + const { dataEntryId, itemId, uid } = action.payload; + const dataEntryKey = getDataEntryKey(dataEntryId, itemId); + return { + ...state, + [dataEntryKey]: [ + ...(state[dataEntryKey] || []), + uid, + ], + }; + }, [formAsyncActionTypes.FIELD_IS_VALIDATING]: (state, action) => { const { formId, validatingUid } = action.payload; const listWithPotentialDupes = [...(state[formId] || []), validatingUid]; @@ -336,6 +347,17 @@ export const dataEntriesInProgressListDesc = createReducerDescription({ ], }; }, + [actionTypes.START_RUN_RULES_POST_LOAD_DATA_ENTRY]: (state, action) => { + const { dataEntryId, itemId, uid } = action.payload; + const dataEntryKey = getDataEntryKey(dataEntryId, itemId); + return { + ...state, + [dataEntryKey]: [ + ...(state[dataEntryKey] || []), + uid, + ], + }; + }, [actionTypes.RULES_EXECUTED_POST_UPDATE_FIELD]: (state, action) => { const { dataEntryId, itemId, uid } = action.payload; const dataEntryKey = getDataEntryKey(dataEntryId, itemId); @@ -345,4 +367,13 @@ export const dataEntriesInProgressListDesc = createReducerDescription({ [dataEntryKey]: updatedList, }; }, + [actionTypes.RULES_EXECUTED_POST_LOAD_DATA_ENTRY]: (state, action) => { + const { dataEntryId, itemId, uid } = action.payload; + const dataEntryKey = getDataEntryKey(dataEntryId, itemId); + const updatedList = (state[dataEntryKey] || []).filter(item => item !== uid); + return { + ...state, + [dataEntryKey]: updatedList, + }; + }, }, 'dataEntriesInProgressList'); From 64db5720fb661b20e51a83bf78b6497ba7e48877 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Wed, 23 Oct 2024 14:00:42 +0200 Subject: [PATCH 16/22] fix: disable save button in WidgetProfile while async validation is running --- .../DataEntry/DataEntry.container.js | 18 +++++++++++++++--- .../DataEntry/dataEntry.actions.js | 6 ++---- .../WidgetProfile/DataEntry/dataEntry.types.js | 1 + .../DataEntry/hooks/useLifecycle.js | 17 +++++++++++++++-- .../WidgetProfile/WidgetProfile.component.js | 5 ++++- 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.js index cb7dfc9497..4f13a3173d 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.js @@ -1,4 +1,5 @@ // @flow +import { v4 as uuid } from 'uuid'; import React, { useState, useCallback } from 'react'; import { useDispatch } from 'react-redux'; import { useDataEngine } from '@dhis2/app-runtime'; @@ -7,12 +8,14 @@ import type { Props } from './dataEntry.types'; import { DataEntryComponent } from './DataEntry.component'; import { useLifecycle, useFormValidations } from './hooks'; import { getUpdateFieldActions, updateTeiRequest, setTeiModalError } from './dataEntry.actions'; +import { startRunRulesPostUpdateField } from '../../DataEntry'; export const DataEntry = ({ programAPI, orgUnitId, onCancel, onDisable, + onEnable, clientAttributesWithSubvalues, userRoles, modalState, @@ -51,17 +54,26 @@ export const DataEntry = ({ geometry, dataEntryFormConfig, onGetValidationContext, + onDisable, + onEnable, }); const { formFoundation } = context; const { formValidated, errorsMessages, warningsMessages } = useFormValidations(dataEntryId, itemId, saveAttempted); const onUpdateFormField = useCallback( (innerAction: ReduxAction) => { - getUpdateFieldActions({ context, querySingleResource, onGetValidationContext, innerAction }).then(actions => - dispatch(actions), + const uid = uuid(); + onDisable(); + dispatch(startRunRulesPostUpdateField(dataEntryId, itemId, uid)); + + getUpdateFieldActions({ context, querySingleResource, onGetValidationContext, innerAction, uid }).then( + (actions) => { + onEnable(); + return dispatch(actions); + }, ); }, - [dispatch, querySingleResource, context, onGetValidationContext], + [dispatch, querySingleResource, context, onGetValidationContext, onDisable, onEnable], ); const onUpdateFormFieldAsync = useCallback( (innerAction: ReduxAction) => { diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.js index 92e0415b49..aa1dfbaf3b 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.js @@ -1,5 +1,4 @@ // @flow -import { v4 as uuid } from 'uuid'; import { batchActions } from 'redux-batched-actions'; import type { OrgUnit, @@ -18,7 +17,6 @@ import type { FieldData } from '../../../rules'; import { getCurrentClientValues } from '../../../rules'; import { loadNewDataEntry } from '../../DataEntry/actions/dataEntryLoadNew.actions'; import { rulesExecutedPostUpdateField } from '../../DataEntry/actions/dataEntry.actions'; -import { startRunRulesPostUpdateField } from '../../DataEntry'; import { getRulesActionsForTEI } from './ProgramRules'; import { addFormData } from '../../D2Form/actions/form.actions'; import type { Geometry } from './helpers/types'; @@ -71,13 +69,14 @@ export const getUpdateFieldActions = async ({ querySingleResource, onGetValidationContext, innerAction, + uid, }: { context: Context, querySingleResource: QuerySingleResource, onGetValidationContext: () => Object, innerAction: ReduxAction, + uid: string }) => { - const uid = uuid(); const { orgUnit, trackedEntityAttributes, @@ -119,7 +118,6 @@ export const getUpdateFieldActions = async ({ innerAction, rulesActions, rulesExecutedPostUpdateField(dataEntryId, itemId, uid), - startRunRulesPostUpdateField(dataEntryId, itemId, uid), ], dataEntryActionTypes.UPDATE_FIELD_PROFILE_ACTION_BATCH, ); diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.types.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.types.js index 55a8b937d6..a5a7ef9c4c 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.types.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.types.js @@ -29,6 +29,7 @@ export type Props = {| dataEntryFormConfig: ?DataEntryFormConfig, onCancel: () => void, onDisable: () => void, + onEnable: () => void, clientAttributesWithSubvalues: Array, trackedEntityInstanceId: string, onSaveSuccessActionType?: string, diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.js index b572b809f6..2be7de1775 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.js @@ -1,4 +1,5 @@ // @flow +import { v4 as uuid } from 'uuid'; import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useDataEngine } from '@dhis2/app-runtime'; @@ -11,7 +12,7 @@ import type { ProgramRulesContainer, DataElements, } from '@dhis2/rules-engine-javascript'; -import { cleanUpDataEntry } from '../../../DataEntry'; +import { cleanUpDataEntry, startLoadDataEntry } from '../../../DataEntry'; import { RenderFoundation } from '../../../../metaData'; import { getOpenDataEntryActions, cleanTeiModal } from '../dataEntry.actions'; import { @@ -38,6 +39,8 @@ export const useLifecycle = ({ geometry, dataEntryFormConfig, onGetValidationContext, + onEnable, + onDisable, }: { programAPI: any, orgUnitId: string, @@ -48,6 +51,8 @@ export const useLifecycle = ({ geometry: ?Geometry, dataEntryFormConfig: ?DataEntryFormConfig, onGetValidationContext: () => Object, + onEnable: () => void, + onDisable: () => void, }) => { const dataEngine = useDataEngine(); const dispatch = useDispatch(); @@ -71,6 +76,8 @@ export const useLifecycle = ({ useEffect(() => { if (Object.entries(formValues).length > 0) { + const uid = uuid(); + dispatch(startLoadDataEntry(dataEntryId, itemId, uid)); dispatch( getOpenDataEntryActions({ dataEntryId, @@ -93,6 +100,7 @@ export const useLifecycle = ({ Object.entries(clientValues).length > 0 && Object.entries(rulesContainer).length > 0 ) { + onDisable(); const querySingleResource = makeQuerySingleResource(dataEngine.query.bind(dataEngine)); getRulesActionsForTEI({ foundation: formFoundation, @@ -108,7 +116,10 @@ export const useLifecycle = ({ userRoles, querySingleResource, onGetValidationContext, - }).then(rulesActions => dispatch(rulesActions)); + }).then((rulesActions) => { + onEnable(); + return dispatch(rulesActions); + }); } }, [ dispatch, @@ -128,6 +139,8 @@ export const useLifecycle = ({ userRoles, dataEngine, onGetValidationContext, + onDisable, + onEnable, ]); return { diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.js b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.js index 0204ac19a7..bdf1d44447 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.js +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.js @@ -140,6 +140,8 @@ const WidgetProfilePlain = ({ ); }; + const handleOnDisable = useCallback(() => setTeiModalState(TEI_MODAL_STATE.OPEN_DISABLE), [setTeiModalState]); + const handleOnEnable = useCallback(() => setTeiModalState(TEI_MODAL_STATE.OPEN), [setTeiModalState]); return (
@@ -178,7 +180,8 @@ const WidgetProfilePlain = ({ <> setTeiModalState(TEI_MODAL_STATE.CLOSE)} - onDisable={() => setTeiModalState(TEI_MODAL_STATE.OPEN_DISABLE)} + onDisable={handleOnDisable} + onEnable={handleOnEnable} programAPI={program} dataEntryFormConfig={dataEntryFormConfig} orgUnitId={orgUnitId} From 86bbb6ef0c4fa3634e823ad3d3a6c641aaa84586 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Thu, 21 Nov 2024 08:52:11 +0100 Subject: [PATCH 17/22] chore: skip assigned values validation when opening forms --- .../Enrollment/actions/open.actionBatchs.js | 19 +------ .../DataEntries/Enrollment/index.js | 1 - .../EnrollmentRegistrationEntry.epics.js | 3 - .../hooks/useLifecycle.js | 5 -- .../DataEntry/actions/dataEntry.actions.js | 5 -- .../components/DataEntry/index.js | 1 - .../DataEntry/editEventDataEntry.actions.js | 25 +++----- .../editEventDataEntry.epics.js | 36 ++++++------ .../WidgetEventEdit.container.js | 8 +-- .../DataEntry/DataEntry.container.js | 3 - .../ProgramRules/getRulesActionsForTEI.js | 57 +++++++++++++------ .../DataEntry/ProgramRules/index.js | 2 +- .../DataEntry/dataEntry.actions.js | 4 +- .../DataEntry/hooks/useLifecycle.js | 53 ++++++----------- .../dataEntry.reducerDescription.js | 11 ---- .../descriptions/form.reducerDescription.js | 23 ++++---- 16 files changed, 100 insertions(+), 156 deletions(-) diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.js index 7804c49e50..b6dcff586f 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.js @@ -1,11 +1,7 @@ // @flow import { batchActions } from 'redux-batched-actions'; import type { OrgUnit } from '@dhis2/rules-engine-javascript'; -import { - getApplicableRuleEffectsForTrackerProgram, - updateRulesEffects, - validateAssignEffects, -} from '../../../../rules'; +import { getApplicableRuleEffectsForTrackerProgram, updateRulesEffects } from '../../../../rules'; import type { ProgramStage, TrackerProgram, RenderFoundation } from '../../../../metaData'; import { getDataEntryKey } from '../../../DataEntry/common/getDataEntryKey'; import { loadNewDataEntry } from '../../../DataEntry/actions/dataEntryLoadNew.actions'; @@ -20,9 +16,8 @@ import { convertDateObjectToDateFormatString } from '../../../../utils/converter import { addFormData } from '../../../D2Form/actions/form.actions'; import type { ProgramCategory } from '../../../WidgetEventSchedule/CategoryOptions/CategoryOptions.types'; import { getDataEntryPropsToInclude } from '../EnrollmentWithFirstStageDataEntry'; -import type { QuerySingleResource } from '../../../../utils/api'; -export const itemId = 'newEnrollment'; +const itemId = 'newEnrollment'; type DataEntryPropsToInclude = Array; @@ -61,7 +56,6 @@ export const openDataEntryForNewEnrollmentBatchAsync = async ({ firstStage, programCategory, formFoundation, - querySingleResource, }: { program: TrackerProgram, orgUnit: OrgUnit, @@ -73,7 +67,6 @@ export const openDataEntryForNewEnrollmentBatchAsync = async ({ firstStage?: ProgramStage, programCategory?: ProgramCategory, formFoundation: RenderFoundation, - querySingleResource: QuerySingleResource, }) => { const formId = getDataEntryKey(dataEntryId, itemId); const addFormDataActions = addFormData(`${dataEntryId}-${itemId}`, formValues); @@ -108,19 +101,13 @@ export const openDataEntryForNewEnrollmentBatchAsync = async ({ formFoundation, }); - const effectsWithValidations = await validateAssignEffects({ - dataElements: formFoundation.getElements(), - effects, - querySingleResource, - }); - return batchActions([ openDataEntryForNewEnrollment( dataEntryId, ), ...dataEntryActions, addFormDataActions, - updateRulesEffects(effectsWithValidations, formId), + updateRulesEffects(effects, formId), ...extraActions, ], batchActionTypes.OPEN_DATA_ENTRY_FOR_NEW_ENROLLMENT_BATCH); }; diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/index.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/index.js index 9c1cda8b89..dcbc06780b 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/index.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/index.js @@ -5,7 +5,6 @@ export { actionTypes as openActionTypes } from './actions/open.actions'; export { batchActionTypes as openBatchActionTypes, openDataEntryForNewEnrollmentBatchAsync, - itemId, } from './actions/open.actionBatchs'; export { batchActionTypes as enrollmentBatchActionTypes, diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.epics.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.epics.js index 48ba34c309..a6162ddeb8 100644 --- a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.epics.js +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.epics.js @@ -13,8 +13,6 @@ import type { TrackerProgram } from '../../../metaData/Program'; export const startNewEnrollmentDataEntrySelfInitialisationEpic = ( action$: InputObservable, - store: ReduxStore, - { querySingleResource }: ApiUtils, ) => action$.pipe( ofType(enrollmentRegistrationEntryActionTypes.TRACKER_PROGRAM_REGISTRATION_ENTRY_INITIALISATION_START), @@ -49,7 +47,6 @@ export const startNewEnrollmentDataEntrySelfInitialisationEpic = ( firstStage, programCategory, formFoundation, - querySingleResource, }); return from(openEnrollmentPromise); diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useLifecycle.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useLifecycle.js index bce6700c5a..a9b0eda796 100644 --- a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useLifecycle.js +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useLifecycle.js @@ -1,5 +1,4 @@ // @flow -import { v4 as uuid } from 'uuid'; import { useDispatch, useSelector } from 'react-redux'; import { useEffect, useRef } from 'react'; import type { OrgUnit } from '@dhis2/rules-engine-javascript'; @@ -12,8 +11,6 @@ import { useBuildFirstStageRegistration } from './useBuildFirstStageRegistration import { useMetadataForRegistrationForm } from '../../common/TEIAndEnrollment/useMetadataForRegistrationForm'; import { useCategoryCombinations } from '../../../DataEntryDhis2Helpers/AOC/useCategoryCombinations'; import { useMergeFormFoundationsIfApplicable } from './useMergeFormFoundationsIfApplicable'; -import { startLoadDataEntry } from '../../../DataEntry'; -import { itemId } from '../../Enrollment'; export const useLifecycle = ( selectedScopeId: string, @@ -59,8 +56,6 @@ export const useLifecycle = ( formFoundation ) { dataEntryReadyRef.current = true; - const uid = uuid(); - dispatch(startLoadDataEntry(dataEntryId, itemId, uid)); dispatch( startNewEnrollmentDataEntryInitialisation({ selectedOrgUnit: orgUnit, diff --git a/src/core_modules/capture-core/components/DataEntry/actions/dataEntry.actions.js b/src/core_modules/capture-core/components/DataEntry/actions/dataEntry.actions.js index a30f38c8ec..59fe158f19 100644 --- a/src/core_modules/capture-core/components/DataEntry/actions/dataEntry.actions.js +++ b/src/core_modules/capture-core/components/DataEntry/actions/dataEntry.actions.js @@ -30,7 +30,6 @@ export const actionTypes = { DATA_ENTRY_RELATIONSHIP_ALREADY_EXISTS: 'DataEntryRelationshipAlreadyExists', LOAD_EDIT_DATA_ENTRY: 'LoadEditDataEntry', CLEAN_UP_DATA_ENTRY: 'CleanUpDataEntry', - START_LOAD_DATA_ENTRY: 'StartLoadDataEntry', }; // COMPLETE @@ -119,10 +118,6 @@ export const updateFormField = updateCompleteUid, }); -export const startLoadDataEntry = - (dataEntryId: string, itemId: string, uid: string) => - actionCreator(actionTypes.START_LOAD_DATA_ENTRY)({ dataEntryId, itemId, uid }); - export const startRunRulesPostLoadDataEntry = (dataEntryId: string, itemId: string, uid: string) => actionCreator(actionTypes.START_RUN_RULES_POST_LOAD_DATA_ENTRY)({ dataEntryId, itemId, uid }); diff --git a/src/core_modules/capture-core/components/DataEntry/index.js b/src/core_modules/capture-core/components/DataEntry/index.js index 369cfad117..ac3a14f0f7 100644 --- a/src/core_modules/capture-core/components/DataEntry/index.js +++ b/src/core_modules/capture-core/components/DataEntry/index.js @@ -25,7 +25,6 @@ export { placements } from './constants/placements.const'; // actions export { actionTypes as mainActionTypes, - startLoadDataEntry, startRunRulesPostUpdateField, startRunRulesPostLoadDataEntry, rulesExecutedPostUpdateField, diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/editEventDataEntry.actions.js b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/editEventDataEntry.actions.js index c994073f08..7486323f62 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/editEventDataEntry.actions.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/editEventDataEntry.actions.js @@ -7,7 +7,6 @@ import { getApplicableRuleEffectsForEventProgram, getApplicableRuleEffectsForTrackerProgram, updateRulesEffects, - validateAssignEffects, } from '../../../rules'; import { RenderFoundation, Program } from '../../../metaData'; import { getEventDateValidatorContainers } from './fieldValidators/eventDate.validatorContainersGetter'; @@ -29,7 +28,6 @@ import { getEnrollmentForRulesEngine, getAttributeValuesForRulesEngine } from '. import type { EnrollmentData, AttributeValue } from '../../Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData'; import { prepareEnrollmentEventsForRulesEngine } from '../../../events/prepareEnrollmentEvents'; import type { ProgramCategory } from '../../WidgetEventSchedule/CategoryOptions/CategoryOptions.types'; -import type { QuerySingleResource } from '../../../utils/api'; export const batchActionTypes = { UPDATE_DATA_ENTRY_FIELD_EDIT_SINGLE_EVENT_ACTION_BATCH: 'UpdateDataEntryFieldForEditSingleEventActionsBatch', @@ -72,7 +70,7 @@ function getLoadActions( ]; } -export const openEventForEditInDataEntry = async ({ +export const openEventForEditInDataEntry = ({ loadedValues: { eventContainer, dataEntryValues, @@ -86,7 +84,6 @@ export const openEventForEditInDataEntry = async ({ dataEntryId, dataEntryKey, programCategory, - querySingleResource, }: { loadedValues: { eventContainer: Object, @@ -101,7 +98,6 @@ export const openEventForEditInDataEntry = async ({ enrollment?: EnrollmentData, attributeValues?: Array, programCategory?: ProgramCategory, - querySingleResource: QuerySingleResource, }) => { const dataEntryPropsToInclude = [ { @@ -150,13 +146,12 @@ export const openEventForEditInDataEntry = async ({ }, ); const currentEvent = { ...eventContainer.event, ...eventContainer.values }; - const stage = getStageFromEvent(eventContainer.event)?.stage; - if (!stage) { - throw Error(i18n.t('stage not found in rules execution')); - } - - let effects: Object = {}; + let effects; if (program instanceof TrackerProgram) { + const stage = getStageFromEvent(eventContainer.event)?.stage; + if (!stage) { + throw Error(i18n.t('stage not found in rules execution')); + } // TODO: Add attributeValues & enrollmentData effects = getApplicableRuleEffectsForTrackerProgram({ program, @@ -177,15 +172,9 @@ export const openEventForEditInDataEntry = async ({ }); } - const effectsWithValidations = await validateAssignEffects({ - dataElements: stage.stageForm.getElements(), - effects, - querySingleResource, - }); - return [ ...dataEntryActions, - updateRulesEffects(effectsWithValidations, formId), + updateRulesEffects(effects, formId), actionCreator(actionTypes.OPEN_EVENT_FOR_EDIT_IN_DATA_ENTRY)(), ]; }; diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/editEventDataEntry.epics.js b/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/editEventDataEntry.epics.js index 35dd2cd939..de9c392677 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/editEventDataEntry.epics.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/editEventDataEntry.epics.js @@ -1,9 +1,9 @@ // @flow import { ofType } from 'redux-observable'; -import { map, filter, flatMap, switchMap } from 'rxjs/operators'; +import { map, filter, flatMap } from 'rxjs/operators'; import { batchActions } from 'redux-batched-actions'; import { dataEntryKeys, dataEntryIds } from 'capture-core/constants'; -import { EMPTY, of } from 'rxjs'; +import { EMPTY } from 'rxjs'; import { convertCategoryOptionsToServer, convertValue as convertToServerValue } from '../../../converters/clientToServer'; import { getProgramAndStageFromEvent, scopeTypes, getScopeInfo } from '../../../metaData'; import { openEventForEditInDataEntry } from '../DataEntry/editEventDataEntry.actions'; @@ -43,16 +43,16 @@ const getDataEntryId = (event): string => ( : dataEntryIds.SINGLE_EVENT ); -export const loadEditEventDataEntryEpic = (action$: InputObservable, store: ReduxStore, { querySingleResource }: ApiUtils) => +export const loadEditEventDataEntryEpic = (action$: InputObservable, store: ReduxStore) => action$.pipe( ofType(eventDetailsActionTypes.START_SHOW_EDIT_EVENT_DATA_ENTRY, widgetEventEditActionTypes.START_SHOW_EDIT_EVENT_DATA_ENTRY), - switchMap((action) => { + map((action) => { const state = store.value; const loadedValues = state.viewEventPage.loadedValues; const eventContainer = loadedValues.eventContainer; const metadataContainer = getProgramAndStageFromEvent(eventContainer.event); if (metadataContainer.error) { - return of(prerequisitesErrorLoadingEditEventDataEntry(metadataContainer.error)); + return prerequisitesErrorLoadingEditEventDataEntry(metadataContainer.error); } const program = metadataContainer.program; @@ -60,18 +60,20 @@ export const loadEditEventDataEntryEpic = (action$: InputObservable, store: Redu const { orgUnit, programCategory } = action.payload; const { enrollment, attributeValues } = state.enrollmentDomain; - return openEventForEditInDataEntry({ - loadedValues, - orgUnit, - foundation, - program, - enrollment, - attributeValues, - dataEntryId: getDataEntryId(eventContainer.event), - dataEntryKey: dataEntryKeys.EDIT, - programCategory, - querySingleResource, - }).then(actions => batchActions([showEditEventDataEntry(), ...actions])); + return batchActions([ + showEditEventDataEntry(), + ...openEventForEditInDataEntry({ + loadedValues, + orgUnit, + foundation, + program, + enrollment, + attributeValues, + dataEntryId: getDataEntryId(eventContainer.event), + dataEntryKey: dataEntryKeys.EDIT, + programCategory, + }), + ]); })); export const saveEditedEventEpic = (action$: InputObservable, store: ReduxStore, { serverVersion: { minor } }: ApiUtils) => diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js index 031b8cb8f9..eedcb781a8 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js @@ -1,5 +1,4 @@ // @flow -import { v4 as uuid } from 'uuid'; import React, { type ComponentType, useState, useEffect } from 'react'; import { dataEntryIds, dataEntryKeys } from 'capture-core/constants'; import { useDispatch, useSelector } from 'react-redux'; @@ -34,7 +33,6 @@ import { FEATURES, useFeature } from '../../../capture-core-utils'; import { inMemoryFileStore } from '../DataEntry/file/inMemoryFileStore'; import { eventStatuses } from './constants/status.const'; import { useAuthorities } from '../../utils/authority/useAuthorities'; -import { startLoadDataEntry } from '../DataEntry'; const styles = { header: { @@ -138,11 +136,7 @@ export const WidgetEventEditPlain = ({ secondary disabled={disableEdit} icon={} - onClick={() => { - const uid = uuid(); - dispatch(startLoadDataEntry(dataEntryIds.ENROLLMENT_EVENT, dataEntryKeys.EDIT, uid)); - dispatch(startShowEditEventDataEntry(orgUnit, programCategory)); - }} + onClick={() => dispatch(startShowEditEventDataEntry(orgUnit, programCategory))} > {i18n.t('Edit event')} diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.js index 4f13a3173d..3fd1630ac2 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.js @@ -53,9 +53,6 @@ export const DataEntry = ({ itemId, geometry, dataEntryFormConfig, - onGetValidationContext, - onDisable, - onEnable, }); const { formFoundation } = context; const { formValidated, errorsMessages, warningsMessages } = useFormValidations(dataEntryId, itemId, saveAttempted); diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.js index f680bd7dee..03c2aff309 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.js @@ -42,30 +42,48 @@ const getDataElementsForRulesExecution = (dataElements: ?DataElements) => {}, ); -const getRulesActions = async ({ - effects, +export const getRulesActionsForTEI = ({ foundation, formId, - querySingleResource, - onGetValidationContext, + orgUnit, + enrollmentData, + teiValues, + trackedEntityAttributes, + optionSets, + rulesContainer, + otherEvents, + dataElements, + userRoles, }: { - effects: OutputEffects, foundation: RenderFoundation, formId: string, - querySingleResource: QuerySingleResource, - onGetValidationContext: () => Object, + orgUnit: OrgUnit, + enrollmentData?: ?Enrollment, + teiValues?: ?TEIValues, + trackedEntityAttributes: ?TrackedEntityAttributes, + optionSets: OptionSets, + rulesContainer: ProgramRulesContainer, + otherEvents?: ?EventsData, + dataElements: ?DataElements, + userRoles: Array, }) => { - const effectsHierarchy = buildEffectsHierarchy(postProcessRulesEffects(effects, foundation)); - const effectsWithValidations = await validateAssignEffects({ - dataElements: foundation.getElements(), - effects: effectsHierarchy, - querySingleResource, - onGetValidationContext, + const effects: OutputEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: rulesContainer, + currentEvent: null, + otherEvents, + dataElements: getDataElementsForRulesExecution(dataElements), + trackedEntityAttributes, + selectedEnrollment: getEnrollmentForRulesExecution(enrollmentData), + selectedEntity: teiValues, + selectedOrgUnit: orgUnit, + selectedUserRoles: userRoles, + optionSets, }); - return updateRulesEffects(effectsWithValidations, formId); + const effectsHierarchy = buildEffectsHierarchy(postProcessRulesEffects(effects, foundation)); + return updateRulesEffects(effectsHierarchy, formId); }; -export const getRulesActionsForTEI = async ({ +export const getRulesActionsForTEIAsync = async ({ foundation, formId, orgUnit, @@ -106,5 +124,12 @@ export const getRulesActionsForTEI = async ({ selectedUserRoles: userRoles, optionSets, }); - return getRulesActions({ effects, foundation, formId, querySingleResource, onGetValidationContext }); + const effectsHierarchy = buildEffectsHierarchy(postProcessRulesEffects(effects, foundation)); + const effectsWithValidations = await validateAssignEffects({ + dataElements: foundation.getElements(), + effects: effectsHierarchy, + querySingleResource, + onGetValidationContext, + }); + return updateRulesEffects(effectsWithValidations, formId); }; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/index.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/index.js index a088da2ddb..494f821921 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/index.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/index.js @@ -1,3 +1,3 @@ // @flow export { buildRulesContainer } from './rulesContainer'; -export { getRulesActionsForTEI } from './getRulesActionsForTEI'; +export { getRulesActionsForTEI, getRulesActionsForTEIAsync } from './getRulesActionsForTEI'; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.js index aa1dfbaf3b..f326ab200c 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.js @@ -17,7 +17,7 @@ import type { FieldData } from '../../../rules'; import { getCurrentClientValues } from '../../../rules'; import { loadNewDataEntry } from '../../DataEntry/actions/dataEntryLoadNew.actions'; import { rulesExecutedPostUpdateField } from '../../DataEntry/actions/dataEntry.actions'; -import { getRulesActionsForTEI } from './ProgramRules'; +import { getRulesActionsForTEIAsync } from './ProgramRules'; import { addFormData } from '../../D2Form/actions/form.actions'; import type { Geometry } from './helpers/types'; import type { QuerySingleResource } from '../../../utils/api'; @@ -97,7 +97,7 @@ export const getUpdateFieldActions = async ({ }; const formId = `${dataEntryId}-${itemId}`; const currentTEIValues = getCurrentClientValues(state, formFoundation, formId, fieldData); - const rulesActions = await getRulesActionsForTEI({ + const rulesActions = await getRulesActionsForTEIAsync({ foundation: formFoundation, formId, orgUnit, diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.js index 2be7de1775..16a8ff9cea 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.js @@ -1,9 +1,6 @@ // @flow -import { v4 as uuid } from 'uuid'; import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { useDataEngine } from '@dhis2/app-runtime'; -import { makeQuerySingleResource } from 'capture-core/utils/api'; import { useOrganisationUnit } from 'capture-core/dataQueries/useOrganisationUnit'; import type { OrgUnit, @@ -12,7 +9,7 @@ import type { ProgramRulesContainer, DataElements, } from '@dhis2/rules-engine-javascript'; -import { cleanUpDataEntry, startLoadDataEntry } from '../../../DataEntry'; +import { cleanUpDataEntry } from '../../../DataEntry'; import { RenderFoundation } from '../../../../metaData'; import { getOpenDataEntryActions, cleanTeiModal } from '../dataEntry.actions'; import { @@ -38,9 +35,6 @@ export const useLifecycle = ({ itemId, geometry, dataEntryFormConfig, - onGetValidationContext, - onEnable, - onDisable, }: { programAPI: any, orgUnitId: string, @@ -50,11 +44,7 @@ export const useLifecycle = ({ itemId: string, geometry: ?Geometry, dataEntryFormConfig: ?DataEntryFormConfig, - onGetValidationContext: () => Object, - onEnable: () => void, - onDisable: () => void, }) => { - const dataEngine = useDataEngine(); const dispatch = useDispatch(); // TODO: Getting the entire state object is bad and this needs to be refactored. // The problem is the helper methods that take the entire state object. @@ -76,8 +66,6 @@ export const useLifecycle = ({ useEffect(() => { if (Object.entries(formValues).length > 0) { - const uid = uuid(); - dispatch(startLoadDataEntry(dataEntryId, itemId, uid)); dispatch( getOpenDataEntryActions({ dataEntryId, @@ -100,26 +88,21 @@ export const useLifecycle = ({ Object.entries(clientValues).length > 0 && Object.entries(rulesContainer).length > 0 ) { - onDisable(); - const querySingleResource = makeQuerySingleResource(dataEngine.query.bind(dataEngine)); - getRulesActionsForTEI({ - foundation: formFoundation, - formId: `${dataEntryId}-${itemId}`, - orgUnit, - trackedEntityAttributes: programTrackedEntityAttributes, - teiValues: { ...clientValues, ...clientGeometryValues }, - optionSets, - rulesContainer, - otherEvents, - dataElements, - enrollmentData: enrollment, - userRoles, - querySingleResource, - onGetValidationContext, - }).then((rulesActions) => { - onEnable(); - return dispatch(rulesActions); - }); + dispatch( + getRulesActionsForTEI({ + foundation: formFoundation, + formId: `${dataEntryId}-${itemId}`, + orgUnit, + trackedEntityAttributes: programTrackedEntityAttributes, + teiValues: { ...clientValues, ...clientGeometryValues }, + optionSets, + rulesContainer, + otherEvents, + dataElements, + enrollmentData: enrollment, + userRoles, + }), + ); } }, [ dispatch, @@ -137,10 +120,6 @@ export const useLifecycle = ({ enrollment, clientGeometryValues, userRoles, - dataEngine, - onGetValidationContext, - onDisable, - onEnable, ]); return { diff --git a/src/core_modules/capture-core/reducers/descriptions/dataEntry.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/dataEntry.reducerDescription.js index 695a7b6317..b79bcbe349 100644 --- a/src/core_modules/capture-core/reducers/descriptions/dataEntry.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/dataEntry.reducerDescription.js @@ -273,17 +273,6 @@ export const dataEntriesInProgressListDesc = createReducerDescription({ [key]: null, }; }, - [actionTypes.START_LOAD_DATA_ENTRY]: (state, action) => { - const { dataEntryId, itemId, uid } = action.payload; - const dataEntryKey = getDataEntryKey(dataEntryId, itemId); - return { - ...state, - [dataEntryKey]: [ - ...(state[dataEntryKey] || []), - uid, - ], - }; - }, [formAsyncActionTypes.FIELD_IS_VALIDATING]: (state, action) => { const { formId, validatingUid } = action.payload; const listWithPotentialDupes = [...(state[formId] || []), validatingUid]; diff --git a/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js index 36bb1a0872..2499f29815 100644 --- a/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js @@ -205,26 +205,23 @@ export const formsSectionsFieldsUIDesc = createReducerDescription({ [newPageActionTypes.CLEAN_UP_DATA_ENTRY]: cleanUp, [rulesEffectsActionTypes.UPDATE_RULES_EFFECTS]: (state, action) => { const { formId, rulesEffects } = action.payload; - const formSectionFields = state[formId]; const assignEffects: { [id: string]: Array } = rulesEffects && rulesEffects[effectActions.ASSIGN_VALUE]; - if (!assignEffects || !formSectionFields) { + if (!assignEffects) { return state; } const updatedFields = Object.keys(assignEffects).reduce((acc, id) => { - if (formSectionFields[id]) { - const effect = assignEffects[id][0]; - acc[id] = { - valid: effect.valid, - errorData: effect.errorData, - errorMessage: effect.errorMessage, - errorType: effect.errorType, - touched: true, - validatingMessage: null, - }; - } + const effect = assignEffects[id][0]; + acc[id] = { + valid: effect.valid, + errorData: effect.errorData, + errorMessage: effect.errorMessage, + errorType: effect.errorType, + touched: true, + validatingMessage: null, + }; return acc; }, {}); From 2ecf3001f57d1e120ee8fcd681eb106b9f193020 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Thu, 21 Nov 2024 09:00:16 +0100 Subject: [PATCH 18/22] chore: skip assigned values validation when opening forms --- .../EnrollmentRegistrationEntry.epics.js | 4 +--- .../WidgetEventEdit/DataEntry/editEventDataEntry.actions.js | 3 ++- .../components/WidgetEventEdit/WidgetEventEdit.container.js | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.epics.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.epics.js index a6162ddeb8..44e25f9bc4 100644 --- a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.epics.js +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.epics.js @@ -11,9 +11,7 @@ import { getTrackerProgramThrowIfNotFound } from '../../../metaData/helpers'; import { openDataEntryFailed } from '../../Pages/NewRelationship/RegisterTei/DataEntry/RegisterTeiDataEntry.actions'; import type { TrackerProgram } from '../../../metaData/Program'; -export const startNewEnrollmentDataEntrySelfInitialisationEpic = ( - action$: InputObservable, -) => +export const startNewEnrollmentDataEntrySelfInitialisationEpic = (action$: InputObservable) => action$.pipe( ofType(enrollmentRegistrationEntryActionTypes.TRACKER_PROGRAM_REGISTRATION_ENTRY_INITIALISATION_START), pluck('payload'), diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/editEventDataEntry.actions.js b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/editEventDataEntry.actions.js index 7486323f62..503a47524d 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/editEventDataEntry.actions.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/editEventDataEntry.actions.js @@ -97,7 +97,7 @@ export const openEventForEditInDataEntry = ({ dataEntryKey: string, enrollment?: EnrollmentData, attributeValues?: Array, - programCategory?: ProgramCategory, + programCategory?: ProgramCategory }) => { const dataEntryPropsToInclude = [ { @@ -146,6 +146,7 @@ export const openEventForEditInDataEntry = ({ }, ); const currentEvent = { ...eventContainer.event, ...eventContainer.values }; + let effects; if (program instanceof TrackerProgram) { const stage = getStageFromEvent(eventContainer.event)?.stage; diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js index eedcb781a8..075855e6cd 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js @@ -1,5 +1,5 @@ // @flow -import React, { type ComponentType, useState, useEffect } from 'react'; +import React, { type ComponentType, useEffect, useState } from 'react'; import { dataEntryIds, dataEntryKeys } from 'capture-core/constants'; import { useDispatch, useSelector } from 'react-redux'; import { From 360933edf7ee791eb8f23ea82f209ec8e8b1c29a Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Mon, 25 Nov 2024 14:22:05 +0100 Subject: [PATCH 19/22] chore: skip assigned values validation when opening new event forms --- .../DataEntry/helpers/getRulesActions.js | 20 +--------- .../DataEntryWrapper/DataEntry/index.js | 1 - .../DataEntryWrapper/useRulesEngine.js | 26 +++++-------- .../DataEntry/actions/dataEntry.actions.js | 10 ----- .../components/DataEntry/index.js | 2 - .../DataEntry/helpers/getRulesActions.js | 20 +--------- .../Validated/useLifecycle.js | 37 +++++++------------ .../dataEntry.reducerDescription.js | 20 ---------- .../descriptions/form.reducerDescription.js | 24 ++++++++---- 9 files changed, 43 insertions(+), 117 deletions(-) diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/helpers/getRulesActions.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/helpers/getRulesActions.js index a052465d9e..ce36f91b0d 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/helpers/getRulesActions.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/helpers/getRulesActions.js @@ -5,27 +5,20 @@ import { getCurrentClientMainData, getApplicableRuleEffectsForEventProgram, updateRulesEffects, - validateAssignEffects, } from '../../../../../../rules'; import type { RenderFoundation, EventProgram } from '../../../../../../metaData'; import { dataEntryId, itemId, formId } from './constants'; -import type { QuerySingleResource } from '../../../../../../utils/api'; -import { rulesExecutedPostLoadDataEntry } from '../../../../../DataEntry'; -export const getRulesActions = async ({ +export const getRulesActions = ({ state, // temporary program, formFoundation, orgUnit, - querySingleResource, - uid, }: { state: ReduxState, program: EventProgram, formFoundation: RenderFoundation, orgUnit: OrgUnit, - querySingleResource: QuerySingleResource, - uid: string, }) => { const formValuesClient = getCurrentClientValues(state, formFoundation, formId); const dataEntryValuesClient = getCurrentClientMainData(state, itemId, dataEntryId, formFoundation); @@ -37,14 +30,5 @@ export const getRulesActions = async ({ currentEvent, }); - const effectsWithValidations = await validateAssignEffects({ - dataElements: formFoundation.getElements(), - effects, - querySingleResource, - }); - - return [ - updateRulesEffects(effectsWithValidations, formId), - rulesExecutedPostLoadDataEntry(dataEntryId, itemId, uid), - ]; + return updateRulesEffects(effects, formId); }; diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/index.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/index.js index 8e4453d8b3..47038dcfc7 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/index.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/index.js @@ -3,4 +3,3 @@ export { DataEntry } from './DataEntry.container'; export { getOpenDataEntryActions } from './helpers/getOpenDataEntryActions'; export { getRulesActions } from './helpers/getRulesActions'; -export { dataEntryId, itemId } from './helpers/constants'; diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/useRulesEngine.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/useRulesEngine.js index dec1061b8b..61e646ec29 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/useRulesEngine.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/useRulesEngine.js @@ -1,15 +1,11 @@ // @flow -import { v4 as uuid } from 'uuid'; import { useEffect, useRef, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { useDataEngine } from '@dhis2/app-runtime'; -import { makeQuerySingleResource } from 'capture-core/utils/api'; import { batchActions } from 'redux-batched-actions'; import type { OrgUnit } from '@dhis2/rules-engine-javascript'; import { getEventProgramThrowIfNotFound } from '../../../../metaData'; -import { getRulesActions, dataEntryId, itemId } from './DataEntry'; +import { getRulesActions } from './DataEntry'; import type { RenderFoundation } from '../../../../metaData'; -import { startRunRulesPostLoadDataEntry } from '../../../DataEntry'; export const useRulesEngine = ({ programId, @@ -20,7 +16,6 @@ export const useRulesEngine = ({ orgUnit: ?OrgUnit, formFoundation: ?RenderFoundation, }) => { - const dataEngine = useDataEngine(); const dispatch = useDispatch(); const program = useMemo(() => programId && getEventProgramThrowIfNotFound(programId), [programId]); const orgUnitRef = useRef(); @@ -31,17 +26,14 @@ export const useRulesEngine = ({ const state = useSelector(stateArg => stateArg); useEffect(() => { if (orgUnit && program && !!formFoundation) { - const uid = uuid(); - dispatch(startRunRulesPostLoadDataEntry(dataEntryId, itemId, uid)); - const querySingleResource = makeQuerySingleResource(dataEngine.query.bind(dataEngine)); - getRulesActions({ - state, - program, - orgUnit, - formFoundation, - querySingleResource, - uid, - }).then(rulesActions => dispatch(batchActions(rulesActions))); + dispatch(batchActions([ + getRulesActions({ + state, + program, + orgUnit, + formFoundation, + }), + ])); orgUnitRef.current = orgUnit; } // Ignoring state (due to various reasons, bottom line being that field updates are handled in epic) diff --git a/src/core_modules/capture-core/components/DataEntry/actions/dataEntry.actions.js b/src/core_modules/capture-core/components/DataEntry/actions/dataEntry.actions.js index 59fe158f19..5b5bf1a6f6 100644 --- a/src/core_modules/capture-core/components/DataEntry/actions/dataEntry.actions.js +++ b/src/core_modules/capture-core/components/DataEntry/actions/dataEntry.actions.js @@ -18,12 +18,10 @@ export const actionTypes = { SAVE_ABORT: 'SaveAbortedForDataEntry', UPDATE_FIELD: 'UpdateDataEntryField', UPDATE_FORM_FIELD: 'UpdateDataEntryFormField', - RULES_EXECUTED_POST_LOAD_DATA_ENTRY: 'RulesExecutedPostLoadDataEntry', RULES_EXECUTED_POST_UPDATE_FIELD: 'RulesExecutedPostUpdateFieldDataEntry', ADD_DATA_ENTRY_NOTE: 'AddDataEntryNote', REMOVE_DATA_ENTRY_NOTE: 'RemoveDataEntryNote', SET_CURRENT_DATA_ENTRY: 'SetCurrentDataEntry', - START_RUN_RULES_POST_LOAD_DATA_ENTRY: 'StartRunRulesPostLoadDataEntry', START_RUN_RULES_POST_UPDATE_FIELD: 'StartRunRulesPostUpdateFieldDataEntry', REMOVE_DATA_ENTRY_RELATIONSHIP: 'RemoveDataEntryRelationship', ADD_DATA_ENTRY_RELATIONSHIP: 'AddDataEntryRelationship', @@ -118,14 +116,6 @@ export const updateFormField = updateCompleteUid, }); -export const startRunRulesPostLoadDataEntry = - (dataEntryId: string, itemId: string, uid: string) => - actionCreator(actionTypes.START_RUN_RULES_POST_LOAD_DATA_ENTRY)({ dataEntryId, itemId, uid }); - -export const rulesExecutedPostLoadDataEntry = - (dataEntryId: string, itemId: string, uid: string) => - actionCreator(actionTypes.RULES_EXECUTED_POST_LOAD_DATA_ENTRY)({ dataEntryId, itemId, uid }); - export const startRunRulesPostUpdateField = (dataEntryId: string, itemId: string, uid: string) => actionCreator(actionTypes.START_RUN_RULES_POST_UPDATE_FIELD)({ dataEntryId, itemId, uid }); diff --git a/src/core_modules/capture-core/components/DataEntry/index.js b/src/core_modules/capture-core/components/DataEntry/index.js index ac3a14f0f7..97ce464d1a 100644 --- a/src/core_modules/capture-core/components/DataEntry/index.js +++ b/src/core_modules/capture-core/components/DataEntry/index.js @@ -26,9 +26,7 @@ export { placements } from './constants/placements.const'; export { actionTypes as mainActionTypes, startRunRulesPostUpdateField, - startRunRulesPostLoadDataEntry, rulesExecutedPostUpdateField, - rulesExecutedPostLoadDataEntry, } from './actions/dataEntry.actions'; export { actionTypes as loadNewActionTypes } from './actions/dataEntryLoadNew.actions'; export { actionTypes as loadEditActionTypes, cleanUpDataEntry } from './actions/dataEntry.actions'; diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/helpers/getRulesActions.js b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/helpers/getRulesActions.js index 16575f74dd..d8aaa50247 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/helpers/getRulesActions.js +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/helpers/getRulesActions.js @@ -6,14 +6,11 @@ import { getCurrentClientMainData, getApplicableRuleEffectsForTrackerProgram, updateRulesEffects, - validateAssignEffects, } from '../../../../rules'; import type { RenderFoundation, TrackerProgram, ProgramStage } from '../../../../metaData'; import type { EnrollmentEvents, AttributeValuesClientFormatted, EnrollmentData } from '../../common.types'; -import type { QuerySingleResource } from '../../../../utils/api'; -import { rulesExecutedPostLoadDataEntry } from '../../../DataEntry'; -export const getRulesActions = async ({ +export const getRulesActions = ({ state, // temporary program, stage, @@ -24,8 +21,6 @@ export const getRulesActions = async ({ eventsRulesDependency, attributesValuesRulesDependency, enrollmentDataRulesDependency, - querySingleResource, - uid, }: { state: ReduxState, program: TrackerProgram, @@ -37,8 +32,6 @@ export const getRulesActions = async ({ eventsRulesDependency: EnrollmentEvents, attributesValuesRulesDependency: AttributeValuesClientFormatted, enrollmentDataRulesDependency: EnrollmentData, - querySingleResource: QuerySingleResource, - uid: string, }) => { const formId = getDataEntryKey(dataEntryId, itemId); @@ -56,14 +49,5 @@ export const getRulesActions = async ({ enrollmentData: enrollmentDataRulesDependency, }); - const effectsWithValidations = await validateAssignEffects({ - dataElements: formFoundation.getElements(), - effects, - querySingleResource, - }); - - return [ - updateRulesEffects(effectsWithValidations, formId), - rulesExecutedPostLoadDataEntry(dataEntryId, itemId, uid), - ]; + return updateRulesEffects(effects, formId); }; diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/useLifecycle.js b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/useLifecycle.js index fa430e1731..bf41bf3e53 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/useLifecycle.js +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/useLifecycle.js @@ -1,16 +1,12 @@ // @flow import { useEffect, useRef, useState } from 'react'; -import { v4 as uuid } from 'uuid'; import { useDispatch, useSelector } from 'react-redux'; -import { useDataEngine } from '@dhis2/app-runtime'; -import { makeQuerySingleResource } from 'capture-core/utils/api'; import { batchActions } from 'redux-batched-actions'; import type { OrgUnit } from '@dhis2/rules-engine-javascript'; import { getOpenDataEntryActions, getRulesActions } from '../DataEntry'; import type { TrackerProgram, ProgramStage, RenderFoundation } from '../../../metaData'; import type { RulesExecutionDependenciesClientFormatted } from '../common.types'; import { useCategoryCombinations } from '../../DataEntryDhis2Helpers/AOC/useCategoryCombinations'; -import { startRunRulesPostLoadDataEntry } from '../../DataEntry'; export const useLifecycle = ({ program, @@ -33,7 +29,6 @@ export const useLifecycle = ({ itemId: string, rulesExecutionDependenciesClientFormatted: RulesExecutionDependenciesClientFormatted, }) => { - const dataEngine = useDataEngine(); const dispatch = useDispatch(); const [rulesExecutionTrigger, setRulesExecutionTrigger] = useState(1); @@ -67,24 +62,20 @@ export const useLifecycle = ({ delayRulesExecutionRef.current = false; setRulesExecutionTrigger(-rulesExecutionTrigger); } else { - const uid = uuid(); - dispatch(startRunRulesPostLoadDataEntry(dataEntryId, itemId, uid)); - const querySingleResource = makeQuerySingleResource(dataEngine.query.bind(dataEngine)); - - getRulesActions({ - state, - program, - stage, - formFoundation, - dataEntryId, - itemId, - orgUnit, - eventsRulesDependency, - attributesValuesRulesDependency, - enrollmentDataRulesDependency, - querySingleResource, - uid, - }).then(rulesActions => dispatch(batchActions(rulesActions))); + dispatch(batchActions([ + getRulesActions({ + state, + program, + stage, + formFoundation, + dataEntryId, + itemId, + orgUnit, + eventsRulesDependency, + attributesValuesRulesDependency, + enrollmentDataRulesDependency, + }), + ])); eventsRef.current = eventsRulesDependency; attributesRef.current = attributesValuesRulesDependency; enrollmentDataRef.current = enrollmentDataRulesDependency; diff --git a/src/core_modules/capture-core/reducers/descriptions/dataEntry.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/dataEntry.reducerDescription.js index b79bcbe349..362c0c993d 100644 --- a/src/core_modules/capture-core/reducers/descriptions/dataEntry.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/dataEntry.reducerDescription.js @@ -336,17 +336,6 @@ export const dataEntriesInProgressListDesc = createReducerDescription({ ], }; }, - [actionTypes.START_RUN_RULES_POST_LOAD_DATA_ENTRY]: (state, action) => { - const { dataEntryId, itemId, uid } = action.payload; - const dataEntryKey = getDataEntryKey(dataEntryId, itemId); - return { - ...state, - [dataEntryKey]: [ - ...(state[dataEntryKey] || []), - uid, - ], - }; - }, [actionTypes.RULES_EXECUTED_POST_UPDATE_FIELD]: (state, action) => { const { dataEntryId, itemId, uid } = action.payload; const dataEntryKey = getDataEntryKey(dataEntryId, itemId); @@ -356,13 +345,4 @@ export const dataEntriesInProgressListDesc = createReducerDescription({ [dataEntryKey]: updatedList, }; }, - [actionTypes.RULES_EXECUTED_POST_LOAD_DATA_ENTRY]: (state, action) => { - const { dataEntryId, itemId, uid } = action.payload; - const dataEntryKey = getDataEntryKey(dataEntryId, itemId); - const updatedList = (state[dataEntryKey] || []).filter(item => item !== uid); - return { - ...state, - [dataEntryKey]: updatedList, - }; - }, }, 'dataEntriesInProgressList'); diff --git a/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js index 2499f29815..bed2120f6e 100644 --- a/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js @@ -205,6 +205,7 @@ export const formsSectionsFieldsUIDesc = createReducerDescription({ [newPageActionTypes.CLEAN_UP_DATA_ENTRY]: cleanUp, [rulesEffectsActionTypes.UPDATE_RULES_EFFECTS]: (state, action) => { const { formId, rulesEffects } = action.payload; + const formSectionFields = state[formId]; const assignEffects: { [id: string]: Array } = rulesEffects && rulesEffects[effectActions.ASSIGN_VALUE]; @@ -214,14 +215,21 @@ export const formsSectionsFieldsUIDesc = createReducerDescription({ const updatedFields = Object.keys(assignEffects).reduce((acc, id) => { const effect = assignEffects[id][0]; - acc[id] = { - valid: effect.valid, - errorData: effect.errorData, - errorMessage: effect.errorMessage, - errorType: effect.errorType, - touched: true, - validatingMessage: null, - }; + const isEffectWithValidations = effect.hasOwnProperty('valid'); + if (formSectionFields?.[id] && isEffectWithValidations) { + acc[id] = { + valid: effect.valid, + errorData: effect.errorData, + errorMessage: effect.errorMessage, + errorType: effect.errorType, + touched: true, + validatingMessage: null, + }; + } else { + acc[id] = { + touched: true, + }; + } return acc; }, {}); From fefbdc6101b78f545fb262aed7e3e9a4966d6547 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Tue, 3 Dec 2024 15:23:55 +0100 Subject: [PATCH 20/22] fix: flow errors --- .../D2Form/D2SectionFields.container.js | 2 +- .../FormBuilder/FormBuilder.component.js | 21 +++++++++---------- .../components/D2Form/FormBuilder/index.js | 2 +- .../capture-core/components/D2Form/index.js | 2 +- .../rules/validateAssignEffects.js | 2 +- .../utils/validation/validateValue.js | 16 +++++++++----- 6 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/core_modules/capture-core/components/D2Form/D2SectionFields.container.js b/src/core_modules/capture-core/components/D2Form/D2SectionFields.container.js index 0ce87af445..39bfcce74b 100644 --- a/src/core_modules/capture-core/components/D2Form/D2SectionFields.container.js +++ b/src/core_modules/capture-core/components/D2Form/D2SectionFields.container.js @@ -16,7 +16,7 @@ const makeMapStateToProps = () => { const getRulesMessages = makeGetMessages(); const getCompulsory = makeGetCompulsory(); const getDisabled = makeGetDisabled(); - const mapStateToProps = (state: Object, props: { formId: string }) => ({ + const mapStateToProps = (state: Object, props: { formId: string, fieldsMetaData: any }) => ({ values: getSectionValues(state, props), rulesHiddenFields: getHiddenFields(state, props), rulesMessages: getRulesMessages(state, props), diff --git a/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js b/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js index 2cad7557fd..b6aaf32d5c 100644 --- a/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js +++ b/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js @@ -89,7 +89,7 @@ export type FieldCommitOptions = {| errorCode?: string, |}; -type FieldCommitOptionsExtended = {| +export type FieldCommitOptionsExtended = {| ...FieldCommitOptions, plugin?: ?boolean, |}; @@ -171,12 +171,12 @@ export class FormBuilder extends React.Component { let validationData; try { const { validators } = field; - validationData = await validateValue( + validationData = await validateValue({ validators, - values[field.id], + value: values[field.id], validationContext, - handleIsValidatingInternal, - ); + postProcessAsyncValidatonInitiation: handleIsValidatingInternal, + }); } catch (reason) { if (reason && isObject(reason) && reason.isCanceled) { validationData = null; @@ -405,14 +405,13 @@ export class FormBuilder extends React.Component { errorMessage: options.error, errorType: validatorTypes.TYPE_BASE, errorData: undefined }) : - (await validateValue( + (await validateValue({ validators, value, - onGetValidationContext && onGetValidationContext(), - handleIsValidatingInternal, - // $FlowFixMe - options, - ) + validationContext: onGetValidationContext && onGetValidationContext(), + postProcessAsyncValidatonInitiation: handleIsValidatingInternal, + commitOptions: options, + }) // $FlowFixMe[prop-missing] automated comment .then(({ valid, errorMessage, errorType, errorData }) => { updateField({ valid, errorMessage, errorType, errorData }); diff --git a/src/core_modules/capture-core/components/D2Form/FormBuilder/index.js b/src/core_modules/capture-core/components/D2Form/FormBuilder/index.js index 58c5e95c43..fab5ff84f6 100644 --- a/src/core_modules/capture-core/components/D2Form/FormBuilder/index.js +++ b/src/core_modules/capture-core/components/D2Form/FormBuilder/index.js @@ -1,4 +1,4 @@ // @flow export { FormBuilder } from './FormBuilder.component'; export type { PostProcessErrorMessage, ErrorData } from './formbuilder.types'; -export type { FieldConfig, FieldCommitOptions } from './FormBuilder.component'; +export type { FieldConfig, FieldCommitOptionsExtended } from './FormBuilder.component'; diff --git a/src/core_modules/capture-core/components/D2Form/index.js b/src/core_modules/capture-core/components/D2Form/index.js index 854c2f82c9..8117978a25 100644 --- a/src/core_modules/capture-core/components/D2Form/index.js +++ b/src/core_modules/capture-core/components/D2Form/index.js @@ -1,4 +1,4 @@ // @flow export { asyncHandlerActionTypes, asyncUpdateFieldEpic } from './asyncHandlerHOC'; export { D2Form } from './D2Form.container'; -export type { FieldCommitOptions } from './FormBuilder'; +export type { FieldCommitOptionsExtended } from './FormBuilder'; diff --git a/src/core_modules/capture-core/rules/validateAssignEffects.js b/src/core_modules/capture-core/rules/validateAssignEffects.js index 183bee1e42..62f71694d7 100644 --- a/src/core_modules/capture-core/rules/validateAssignEffects.js +++ b/src/core_modules/capture-core/rules/validateAssignEffects.js @@ -42,7 +42,7 @@ export const validateAssignEffects = async ({ const validationContext = onGetValidationContext && onGetValidationContext(); try { - const validatorResult = await validateValue(validators, value, validationContext); + const validatorResult = await validateValue({ validators, value, validationContext }); const effectWithValidation = Object.assign({}, effectsForId[lastIndex], validatorResult); acc[metaData.id] = [effectWithValidation]; diff --git a/src/core_modules/capture-core/utils/validation/validateValue.js b/src/core_modules/capture-core/utils/validation/validateValue.js index 8ef0ed0eaa..c91bf08fda 100644 --- a/src/core_modules/capture-core/utils/validation/validateValue.js +++ b/src/core_modules/capture-core/utils/validation/validateValue.js @@ -1,6 +1,6 @@ // @flow import type { ValidatorContainer } from './getValidators'; -import type { FieldCommitOptions } from '../../components/D2Form'; +import type { FieldCommitOptionsExtended } from '../../components/D2Form'; export type Validations = { valid: boolean, @@ -9,13 +9,19 @@ export type Validations = { errorData?: Object, }; -export const validateValue = async ( +export const validateValue = async ({ + validators, + value, + validationContext, + postProcessAsyncValidatonInitiation, + commitOptions, +}: { validators?: Array, value: any, validationContext: ?Object, - postProcessAsyncValidatonInitiation: ?Function, - commitOptions?: ?FieldCommitOptions, -): Promise => { + postProcessAsyncValidatonInitiation?: Function, + commitOptions?: ?FieldCommitOptionsExtended, +}): Promise => { if (!validators || validators.length === 0) { return { valid: true, From a19ed6e9e2b10268e619f03436066481dfd6f8ba Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Tue, 3 Dec 2024 15:36:14 +0100 Subject: [PATCH 21/22] fix: flow errors --- .../components/D2Form/FormBuilder/FormBuilder.component.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js b/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js index b6aaf32d5c..9f07004ebb 100644 --- a/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js +++ b/src/core_modules/capture-core/components/D2Form/FormBuilder/FormBuilder.component.js @@ -170,9 +170,8 @@ export class FormBuilder extends React.Component { let validationData; try { - const { validators } = field; validationData = await validateValue({ - validators, + validators: field.validators, value: values[field.id], validationContext, postProcessAsyncValidatonInitiation: handleIsValidatingInternal, From d7ee45c56dfbc39cb02005889d0a43784cdced6a Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Wed, 4 Dec 2024 10:41:01 +0100 Subject: [PATCH 22/22] fix: stabilize flacky cypress tests --- cypress/e2e/NewPage/NewPage.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cypress/e2e/NewPage/NewPage.js b/cypress/e2e/NewPage/NewPage.js index 74201146ac..caecc7dd8b 100644 --- a/cypress/e2e/NewPage/NewPage.js +++ b/cypress/e2e/NewPage/NewPage.js @@ -634,6 +634,9 @@ And('you see the enrollment minimap', () => { }); And('you delete the recently added tracked entity', () => { + cy.get('[data-test="profile-widget"]') + .contains('Person profile') + .should('exist'); cy.get('[data-test="widget-profile-overflow-menu"]') .click(); cy.contains('Delete Person') @@ -646,6 +649,9 @@ And('you delete the recently added tracked entity', () => { }); And('you delete the recently added malaria entity', () => { + cy.get('[data-test="profile-widget"]') + .contains('Malaria Entity profile') + .should('exist'); cy.get('[data-test="widget-profile-overflow-menu"]') .click(); cy.contains('Delete Malaria Entity')