diff --git a/packages/rules-engine/src/processors/rulesEffectsProcessor/rulesEffectsProcessor.js b/packages/rules-engine/src/processors/rulesEffectsProcessor/rulesEffectsProcessor.js index 15951dbc57..72cbd2f20f 100644 --- a/packages/rules-engine/src/processors/rulesEffectsProcessor/rulesEffectsProcessor.js +++ b/packages/rules-engine/src/processors/rulesEffectsProcessor/rulesEffectsProcessor.js @@ -296,26 +296,23 @@ export function getRulesEffectsProcessor( formValues, onProcessValue, }: { - effects: ?Array, + effects: Array, dataElements: ?DataElements, trackedEntityAttributes: ?TrackedEntityAttributes, formValues?: ?{ [key: string]: any }, onProcessValue: (value: any, type: $Values) => any, }): OutputEffects { - if (effects) { - return effects - .filter(({ action }) => mapActionsToProcessor[action]) - .flatMap(effect => mapActionsToProcessor[effect.action]( - effect, - dataElements, - trackedEntityAttributes, - formValues, - onProcessValue, - )) - // when mapActionsToProcessor function returns `null` we filter those value out. - .filter(keepTruthyValues => keepTruthyValues); - } - return []; + return effects + .filter(({ action }) => mapActionsToProcessor[action]) + .flatMap(effect => mapActionsToProcessor[effect.action]( + effect, + dataElements, + trackedEntityAttributes, + formValues, + onProcessValue, + )) + // when mapActionsToProcessor function returns `null` we filter those value out. + .filter(keepTruthyValues => keepTruthyValues); } return processRulesEffects; diff --git a/packages/rules-engine/src/services/VariableService/helpers/structureEvents.js b/packages/rules-engine/src/services/VariableService/helpers/structureEvents.js index e0e1422616..0482dc98c6 100644 --- a/packages/rules-engine/src/services/VariableService/helpers/structureEvents.js +++ b/packages/rules-engine/src/services/VariableService/helpers/structureEvents.js @@ -38,7 +38,7 @@ export const getStructureEvents = (compareDates: CompareDates) => { event.eventId !== currentEvent.eventId, ); - const events = [...otherEventsFiltered, currentEvent] + const events = [...otherEventsFiltered, ...(Object.keys(currentEvent).length !== 0 ? [currentEvent] : [])] .sort(compareEvents); return createEventsContainer(events); diff --git a/src/core_modules/capture-core/rules/__tests__/ruleEffectsForEventProgram.test.js b/src/core_modules/capture-core/rules/__tests__/ruleEffectsForEventProgram.test.js index 7287de4aa5..b8bf1d7bdf 100644 --- a/src/core_modules/capture-core/rules/__tests__/ruleEffectsForEventProgram.test.js +++ b/src/core_modules/capture-core/rules/__tests__/ruleEffectsForEventProgram.test.js @@ -1822,21 +1822,21 @@ describe('Event rules engine effects with functions and effects', () => { { id: 'Zj7UnCAulEk.vV9UWAZohSf', displayName: 'Zj7UnCAulEk.vV9UWAZohSf', - programRuleVariableSourceType: 'DATAELEMENT_CURRENT_EVENT', + programRuleVariableSourceType: 'DATAELEMENT_PREVIOUS_EVENT', dataElementId: 'vV9UWAZohSf', programId: 'eBAyeGv0exc', }, { id: 'Zj7UnCAulEk.GieVkTxp4HH', displayName: 'Zj7UnCAulEk.GieVkTxp4HH', - programRuleVariableSourceType: 'DATAELEMENT_CURRENT_EVENT', + programRuleVariableSourceType: 'TEI_ATTRIBUTE', dataElementId: 'GieVkTxp4HH', programId: 'eBAyeGv0exc', }, { id: 'Zj7UnCAulEk.GieVkTxp4HH', displayName: 'Zj7UnCAulEk.GieVkTxp4HH', - programRuleVariableSourceType: 'DATAELEMENT_CURRENT_EVENT', + programRuleVariableSourceType: 'UNKNOWN', dataElementId: 'GieVkTxp4HH', programId: 'eBAyeGv0exc', }, @@ -2536,3 +2536,108 @@ describe('Event rules engine', () => { }); }); }); + +describe('Assign effects', () => { + // these variables are shared between each test + const constants = []; + const dataElementsInProgram = { + qrur9Dvnyt5: { id: 'qrur9Dvnyt5', valueType: 'NUMBER' }, + oZg33kd9taw: { id: 'oZg33kd9taw', valueType: 'BOOLEAN' }, + oZ3fhkd9taw: { id: 'oZ3fhkd9taw', valueType: 'UNKNOWN' }, + }; + const orgUnit = { id: 'DiszpKrYNg8', name: 'Ngelehun CHC' }; + const optionSets = {}; + const currentEvent = {}; + + test('Assign effect corner cases', () => { + const programRules = [ + { + id: 'cq1dwUY4lVU', + condition: 'true', + displayName: 'testing assign actions', + programId: 'eBAyeGv0exc', + programRuleActions: [ + { + id: 'lJOYxhjupx2', + data: "'string'", + dataElementId: 'qrur9Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + { id: 'lJOYxkjupx2', data: "'10'", dataElementId: 'qrur9Dvnyt5', programRuleActionType: 'ASSIGN' }, + { + id: 'lJhYxhjupxz', + data: "'string'", + dataElementId: 'oZg33kd9taw', + programRuleActionType: 'ASSIGN', + }, + { + id: 'lJfYxhjupxz', + data: "'string'", + dataElementId: 'oZ3fhkd9taw', + programRuleActionType: 'ASSIGN', + }, + ], + }, + ]; + const programRuleVariables = []; + // when + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + currentEvent, + dataElements: dataElementsInProgram, + selectedOrgUnit: orgUnit, + optionSets, + }); + + // then + expect(rulesEffects).toEqual([ + { type: 'ASSIGN', id: 'qrur9Dvnyt5', value: null, targetDataType: 'dataElement' }, + { type: 'ASSIGN', id: 'qrur9Dvnyt5', value: '10', targetDataType: 'dataElement' }, + { type: 'ASSIGN', id: 'oZg33kd9taw', value: 'false', targetDataType: 'dataElement' }, + { type: 'ASSIGN', id: 'oZ3fhkd9taw', value: '', targetDataType: 'dataElement' }, + ]); + }); + + test('Assign effect with the program rule variable id found in the content key', () => { + const programRules = [ + { + id: 'cq1dwUY4lVU', + condition: 'true', + displayName: 'testing assign actions', + programId: 'eBAyeGv0exc', + programRuleActions: [ + { + id: 'lJOYxhjupx2', + data: 'rowExpresion', + dataElementId: 'qrur9Dvnyt5', + programRuleActionType: 'ASSIGN', + content: 'Hemoglobin value lower than normal RycV5uDi66i', + }, + ], + }, + ]; + const programRuleVariables = [ + { + id: 'RycV5uDi66i', + dataElementId: 'qrur9Dvnyt5', + displayName: 'age', + programId: 'eBAyeGv0exc', + programRuleVariableSourceType: 'DATAELEMENT_NEWEST_EVENT_PROGRAM', + useNameForOptionSet: true, + }, + ]; + // when + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + currentEvent, + dataElements: dataElementsInProgram, + selectedOrgUnit: orgUnit, + optionSets, + }); + + // then + expect(rulesEffects).toEqual([ + { type: 'ASSIGN', id: 'qrur9Dvnyt5', value: 'false', targetDataType: 'dataElement' }, + ]); + }); +}); diff --git a/src/core_modules/capture-core/rules/__tests__/rulesEffectsForTrackerProgram.test.js b/src/core_modules/capture-core/rules/__tests__/rulesEffectsForTrackerProgram.test.js index dea5c21b98..a3731c8152 100644 --- a/src/core_modules/capture-core/rules/__tests__/rulesEffectsForTrackerProgram.test.js +++ b/src/core_modules/capture-core/rules/__tests__/rulesEffectsForTrackerProgram.test.js @@ -37,6 +37,188 @@ test('expressions with d2Functions in tracker program', () => { location: 'feedback', programRuleActionType: 'DISPLAYTEXT', }, + { + id: 'polhYpVRDyj', + displayContent: "d2:daysBetween( '2020-01-28', V{enrollment_date}) = ", + data: "d2:daysBetween( '2020-01-28', V{enrollment_date})", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'Foc3PhzoAVr', + displayContent: 'd2:count(#{undefinedVariable}) = ', + data: 'd2:count(#{unknow})', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FjkwPhzoAVr', + displayContent: "d2:countIfValue(#{undefinedVariable}, 'Male') = ", + data: "d2:countIfValue(#{undefinedVariable}, 'Male') = ", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'TkgrHcLselM', + displayContent: 'd2:countIfZeroPos(100) = ', + data: 'd2:countIfZeroPos(100)', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'Foc3PhzoAVr', + displayContent: 'd2:hasValue(#{undefinedVariable})', + data: 'd2:hasValue(#{undefinedVariable})', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FoljdkeoAVr', + displayContent: "d2:validatePattern('Male', 'a')", + data: "d2:validatePattern('Male','a')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FoljdlkYAVr', + displayContent: "d2:validatePattern('Male', 'Male')", + data: "d2:validatePattern('Male','Male')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FoljdjkRAVr', + displayContent: "d2:hasUserRole('admin')", + data: "d2:hasUserRole('admin')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FoljdkLRAVr', + displayContent: "d2:extractDataMatrixValue('batch number', 3)", + data: "d2:extractDataMatrixValue('batch number', 3)", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FollkyLRAVr', + displayContent: "d2:extractDataMatrixValue('batch number', 'unknow')", + data: "d2:extractDataMatrixValue('batch number', 'unknow')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'lkEUdkLRAVr', + displayContent: "d2:extractDataMatrixValue('gtin', ']d2')", + data: "d2:extractDataMatrixValue('gtin', ']d2')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FlkodkLRAVr', + displayContent: "d2:extractDataMatrixValue('batch number', ']d2')", + data: "d2:extractDataMatrixValue('batch number', ']d2')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'kuYodkLRAVr', + displayContent: "d2:extractDataMatrixValue('production date', ']Q3unknown')", + data: "d2:extractDataMatrixValue('production date', ']Q3unknown')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'kuYookLRAVr', + displayContent: "d2:extractDataMatrixValue('lot number', ']Q3703')", + data: "d2:extractDataMatrixValue('lot number', ']Q3703')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'lkyodkLRAVr', + displayContent: "d2:extractDataMatrixValue('best before date', ']Q3369')", + data: "d2:extractDataMatrixValue('best before date', ']Q3369')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'lkpyodkLRAVr', + displayContent: "d2:extractDataMatrixValue('expiration date', ']Q3369')", + data: "d2:extractDataMatrixValue('expiration date', ']Q3369')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'lkyohgyRAVr', + displayContent: "d2:extractDataMatrixValue('serial number', ']Q3369')", + data: "d2:extractDataMatrixValue('serial number', ']Q3369')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'lkyolktRAVr', + displayContent: "d2:extractDataMatrixValue('unknow key', ']d2')", + data: "d2:extractDataMatrixValue('unknow key', ']d2')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FolpqkLRAVr', + displayContent: 'd2:lastEventDate(V{event_id})', + data: 'd2:lastEventDate(V{event_id})', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FolkokLRAVr', + displayContent: 'd2:lastEventDate(V{enrollment_date})', + data: 'd2:lastEventDate(V{enrollment_date})', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FolkwkLRAVr', + displayContent: 'd2:lastEventDate(#{undefinedVariable})', + data: 'd2:lastEventDate(#{undefinedVariable})', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'llokowfRAVr', + displayContent: "d2:addControlDigits('2')", + data: "d2:addControlDigits('2')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'lloksfegwAVr', + displayContent: "d2:addControlDigits('7')", + data: "d2:addControlDigits('7')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'lkyksfegwAVr', + displayContent: "d2:addControlDigits('9')", + data: "d2:addControlDigits('9')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'llolkyfRAVr', + displayContent: "d2:addControlDigits('12345678912')", + data: "d2:addControlDigits('12345678912')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FollowfRAVr', + displayContent: 'd2:checkControlDigits(1)', + data: 'd2:checkControlDigits(1)', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, { id: 'AFkfzcDf4Fs', displayContent: "d2:inOrgUnitGroup('CHC') = ", @@ -46,8 +228,8 @@ test('expressions with d2Functions in tracker program', () => { }, { id: 'wmAQnxbs7V8', - displayContent: 'd2:round( 12.5 ) = ', - data: 'd2:round( 12.5 )', + displayContent: 'd2:round( 12.5, 1 ) = ', + data: 'd2:round( 12.5, 1 )', location: 'feedback', programRuleActionType: 'DISPLAYTEXT', }, @@ -95,8 +277,8 @@ test('expressions with d2Functions in tracker program', () => { }, { id: 'NLsawa3P5hc', - displayContent: "d2:substring('hello dhis 2', 6, 10) = ", - data: "d2:substring('hello dhis 2', 6, 10)", + displayContent: "d2:substring('hello dhis 2', -1, 10) = ", + data: "d2:substring('hello dhis 2', -1, 10)", location: 'feedback', programRuleActionType: 'DISPLAYTEXT', }, @@ -164,23 +346,104 @@ test('expressions with d2Functions in tracker program', () => { programRuleActionType: 'DISPLAYTEXT', }, { id: 'nKNmayYigcy', programStageId: 'PUZaKR0Jh2k', programRuleActionType: 'HIDEPROGRAMSTAGE' }, + { + id: 'nKNmayYigcy', + programStageSectionId: 'SWfdBhglX0fk', + programRuleActionType: 'HIDESECTION', + }, + { + id: 'lJOYxhjupxz', + data: 'true', + trackedEntityAttributeId: 'w75KJ2mc4zz', + programRuleActionType: 'ASSIGN', + }, + { + id: 'lJOdxhjupxz', + data: 'true', + trackedEntityAttributeId: 'w75KJ2mc4zz', + programRuleActionType: 'ERRORONCOMPLETE', + }, + { + id: 'ljlYxhjupxz', + data: 'true', + trackedEntityAttributeId: 'w75KJ2mc4zz', + programRuleActionType: 'WARNINGONCOMPLETE', + }, + { + id: 'lljoxhjupxz', + data: 'true', + trackedEntityAttributeId: 'w75KJ2mc4zz', + programRuleActionType: 'SETMANDATORYFIELD', + }, + { + id: 'lgjoxhjupxz', + data: 'true', + optionGroupId: 'w75KJ2mc4zz', + programRuleActionType: 'HIDEOPTIONGROUP', + }, + { + id: 'lljljhjupxz', + data: 'true', + optionGroupId: 'w75KJ2mc4zz', + programRuleActionType: 'SHOWOPTIONGROUP', + }, ], }, ]; - const programRulesVariables = []; + const programRuleVariables = [ + { + id: 'DoRHHfNPccb', + trackedEntityAttributeId: 'w75KJ2mc4zz', + displayName: 'INFECTION_SOURCE', + programId: 'IpHINAT79UW', + programRuleVariableSourceType: 'DATAELEMENT_CURRENT_EVENT', + useNameForOptionSet: false, + }, + { + id: 'lokHHfNPccb', + trackedEntityAttributeId: 'w75KJ2mc4zz', + displayName: 'INFECTION_SOURCE', + programId: 'IpHINAT79UW', + programRuleVariableSourceType: 'DATAELEMENT_CURRENT_EVENT', + useNameForOptionSet: false, + }, + { + id: 'Zj7UnCAulEk', + displayName: 'Zj7UnCAulEk', + programRuleVariableSourceType: 'TEI_ATTRIBUTE', + trackedEntityAttributeId: 'w75KJ2mc4zz', + programId: 'IpHINAT79UW', + }, + ]; const optionSets = {}; const teiValues = {}; const orgUnit = { id: 'DiszpKrYNg8', name: 'Ngelehun CHC' }; const enrollmentData = { enrolledAt: '2020-05-14T22:00:00.000Z' }; + const currentEvent = { + occurredAt: '2020-07-14T22:00:00.000Z', + da1Id: 'currentEventText', + dueDate: '2021-05-31T09:51:38.134', + enrollmentId: 'vVtmDlsu3me', + enrollmentStatus: 'ACTIVE', + eventDate: '2021-05-31T00:00:00.000', + eventId: 'BxGzDJK3JqN', + orgUnitId: 'DiszpKrYNg8', + orgUnitName: 'Ngelehun CHC', + programId: 'IpHINAT79UW', + programStageId: 'A03MvHHogjR', + status: 'ACTIVE', + trackedEntityInstanceId: 'vCGpQAWG17I', + }; // when const rulesEffects = rulesEngine.getProgramRuleEffects({ - programRulesContainer: { programRulesVariables, programRules, constants }, + programRulesContainer: { programRuleVariables, programRules, constants }, trackedEntityAttributes, selectedEntity: teiValues, selectedEnrollment: enrollmentData, selectedOrgUnit: orgUnit, optionSets, + currentEvent, }); // then @@ -196,12 +459,185 @@ test('expressions with d2Functions in tracker program', () => { id: 'feedback', displayText: { id: 'kwKhYpVRDyj', message: "d2:monthsBetween( '2020-01-28', V{enrollment_date}) = 3" }, }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'polhYpVRDyj', message: "d2:daysBetween( '2020-01-28', V{enrollment_date}) = 108" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Foc3PhzoAVr', message: 'd2:count(#{undefinedVariable}) = 0' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'FjkwPhzoAVr', message: "d2:countIfValue(#{undefinedVariable}, 'Male') = " }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'TkgrHcLselM', message: 'd2:countIfZeroPos(100) = 0' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Foc3PhzoAVr', message: 'd2:hasValue(#{undefinedVariable}) ' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'FoljdkeoAVr', message: "d2:validatePattern('Male', 'a') " }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'FoljdlkYAVr', message: "d2:validatePattern('Male', 'Male') true" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'FoljdjkRAVr', message: "d2:hasUserRole('admin') " }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'FoljdkLRAVr', + message: "d2:extractDataMatrixValue('batch number', 3) Incomplete DataMatrix input", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'FollkyLRAVr', + message: + "d2:extractDataMatrixValue('batch number', 'unknow') Unsupported GS1 identifier: {gs1Identifier}", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'lkEUdkLRAVr', + message: + "d2:extractDataMatrixValue('gtin', ']d2') ", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'FlkodkLRAVr', + message: + "d2:extractDataMatrixValue('batch number', ']d2') ", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'kuYodkLRAVr', + message: + "d2:extractDataMatrixValue('production date', ']Q3unknown') ", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'kuYookLRAVr', + message: + "d2:extractDataMatrixValue('lot number', ']Q3703') ", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'lkyodkLRAVr', + message: + "d2:extractDataMatrixValue('best before date', ']Q3369') ", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'lkpyodkLRAVr', + message: + "d2:extractDataMatrixValue('expiration date', ']Q3369') ", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'lkyohgyRAVr', + message: + "d2:extractDataMatrixValue('serial number', ']Q3369') ", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'lkyolktRAVr', + message: + "d2:extractDataMatrixValue('unknow key', ']d2') ", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'FolpqkLRAVr', message: 'd2:lastEventDate(V{event_id}) 2020-07-15' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'FolkokLRAVr', message: 'd2:lastEventDate(V{enrollment_date}) ' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'FolkwkLRAVr', message: 'd2:lastEventDate(#{undefinedVariable}) ' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'llokowfRAVr', message: "d2:addControlDigits('2') 261" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'lloksfegwAVr', message: "d2:addControlDigits('7') 709" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'lkyksfegwAVr', message: "d2:addControlDigits('9') 950" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'llolkyfRAVr', message: "d2:addControlDigits('12345678912') 1234567891200" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'FollowfRAVr', message: 'd2:checkControlDigits(1) 1' }, + }, { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'AFkfzcDf4Fs', message: "d2:inOrgUnitGroup('CHC') = " }, }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'wmAQnxbs7V8', message: 'd2:round( 12.5 ) = 13' } }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'wmAQnxbs7V8', message: 'd2:round( 12.5, 1 ) = 12.5' }, + }, { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'tFGwyDBQk3b', message: 'd2:round( 0 ) = 0' } }, { type: 'DISPLAYTEXT', @@ -226,7 +662,7 @@ test('expressions with d2Functions in tracker program', () => { { type: 'DISPLAYTEXT', id: 'feedback', - displayText: { id: 'NLsawa3P5hc', message: "d2:substring('hello dhis 2', 6, 10) = dhis" }, + displayText: { id: 'NLsawa3P5hc', message: "d2:substring('hello dhis 2', -1, 10) = " }, }, { type: 'DISPLAYTEXT', @@ -274,5 +710,24 @@ test('expressions with d2Functions in tracker program', () => { displayText: { id: 'Eeb7Ixr4Pv6', message: 'd2:floor((5+5) / 2) = 5' }, }, // check double parentheses { type: 'HIDEPROGRAMSTAGE', id: 'PUZaKR0Jh2k' }, + { id: 'SWfdBhglX0fk', type: 'HIDESECTION' }, + { id: 'w75KJ2mc4zz', targetDataType: 'trackedEntityAttribute', type: 'ASSIGN', value: 'true' }, + { + id: 'w75KJ2mc4zz', + message: ' true', + targetDataType: 'trackedEntityAttribute', + type: 'ERRORONCOMPLETE', + }, + { + id: 'w75KJ2mc4zz', + message: ' true', + targetDataType: 'trackedEntityAttribute', + type: 'WARNINGONCOMPLETE', + }, + { + id: 'w75KJ2mc4zz', + targetDataType: 'trackedEntityAttribute', + type: 'SETMANDATORYFIELD', + }, ]); }); diff --git a/src/core_modules/capture-core/rules/__tests__/rulesEngine.test.js b/src/core_modules/capture-core/rules/__tests__/rulesEngine.test.js new file mode 100644 index 0000000000..5e79db8520 --- /dev/null +++ b/src/core_modules/capture-core/rules/__tests__/rulesEngine.test.js @@ -0,0 +1,455 @@ +import { rulesEngine } from '../rulesEngine'; + +describe('Rules engine', () => { + const constants = []; + const dataElementsInProgram = {}; + const programRuleVariables = []; + const orgUnit = { id: 'DiszpKrYNg8', name: 'Ngelehun CHC' }; + const optionSets = {}; + const currentEvent = {}; + + test('Rules engine without programRules', () => { + // when + const programRules = undefined; + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + currentEvent, + dataElements: dataElementsInProgram, + selectedOrgUnit: orgUnit, + optionSets, + }); + + // then + expect(rulesEffects).toEqual([]); + }); + + test('Program rule without an condition', () => { + // when + const programRules = [ + { + id: 'GC4gpdoSD4r', + condition: undefined, + description: 'Show warning if hemoglobin is dangerously low', + displayName: 'Hemoglobin warning', + programId: 'lxAQ7Zs9VYR', + programRuleActions: [ + { + id: 'suS9GnraCx1', + content: 'Hemoglobin value lower than normal', + displayContent: 'Hemoglobin value lower than normal', + dataElementId: 'vANAXwtLwcT', + programRuleActionType: 'SHOWWARNING', + }, + ], + }, + ]; + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + currentEvent, + dataElements: dataElementsInProgram, + selectedOrgUnit: orgUnit, + optionSets, + }); + + // then + expect(rulesEffects).toEqual([]); + }); + + test('Program rule condition error handeling', () => { + const programRules = [ + { + id: 'GC4gpdoSD4r', + condition: 'i am a condition with error', + description: 'Show warning if hemoglobin is dangerously low', + displayName: 'Hemoglobin warning', + programId: 'lxAQ7Zs9VYR', + programRuleActions: [ + { + id: 'suS9GnraCx1', + content: 'Hemoglobin value lower than normal', + displayContent: 'Hemoglobin value lower than normal', + dataElementId: 'vANAXwtLwcT', + programRuleActionType: 'SHOWWARNING', + }, + ], + }, + ]; + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + currentEvent, + dataElements: dataElementsInProgram, + selectedOrgUnit: orgUnit, + optionSets, + }); + + // then + expect(rulesEffects).toEqual([]); + }); + + test('user roles', () => { + rulesEngine.setSelectedUserRoles(['ADMIN']); + expect(rulesEngine.userRoles).toEqual(['ADMIN']); + }); + + test('SHOW_WARNING program rule effect with a general target', () => { + const programRules = [ + { + id: 'GC4gpdoSD4r', + condition: 'true', + description: 'Show warning if hemoglobin is dangerously low', + displayName: 'Hemoglobin warning', + programId: 'lxAQ7Zs9VYR', + programRuleActions: [ + { + id: 'SWfdB5lX0fk', + content: 'Hemoglobin value lower than normal', + displayContent: 'Hemoglobin value lower than normal', + programRuleActionType: 'SHOWWARNING', + }, + ], + }, + ]; + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + currentEvent, + dataElements: dataElementsInProgram, + selectedOrgUnit: orgUnit, + optionSets, + }); + + // then + expect(rulesEffects).toEqual([ + { + id: 'general', + type: 'SHOWWARNING', + warning: { + id: 'SWfdB5lX0fk', + message: 'Hemoglobin value lower than normal ', + }, + }, + ]); + }); + + test('SHOW_ERROR program rule effect with a general target', () => { + const programRules = [ + { + id: 'GC4gpdoSD4r', + condition: 'true', + description: 'Show error if hemoglobin is dangerously low', + displayName: 'Hemoglobin error', + programId: 'lxAQ7Zs9VYR', + programRuleActions: [ + { + id: 'SWfdB5lX0fk', + content: 'Hemoglobin value lower than normal', + displayContent: 'Hemoglobin value lower than normal', + programRuleActionType: 'SHOWERROR', + }, + ], + }, + ]; + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + currentEvent, + dataElements: dataElementsInProgram, + selectedOrgUnit: orgUnit, + optionSets, + }); + + // then + expect(rulesEffects).toEqual([ + { + id: 'general', + type: 'SHOWERROR', + error: { + id: 'SWfdB5lX0fk', + message: 'Hemoglobin value lower than normal ', + }, + }, + ]); + }); + + test('HIDE_PROGRAM_STAGE program rule effect corner case. The action does not have a program stage id', () => { + const programRules = [ + { + id: 'GC4gpdoSD4r', + condition: 'true', + description: 'Show warning if hemoglobin is dangerously low', + displayName: 'Hemoglobin warning', + programId: 'lxAQ7Zs9VYR', + programRuleActions: [ + { + id: 'nKNmayYigcy', + programRuleActionType: 'HIDEPROGRAMSTAGE', + }, + ], + }, + ]; + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + currentEvent, + dataElements: dataElementsInProgram, + selectedOrgUnit: orgUnit, + optionSets, + }); + + // then + expect(rulesEffects).toEqual([]); + }); + + test('HIDE_SECTION program rule effect corner case. The action does not have a program stage section id', () => { + const programRules = [ + { + id: 'GC4gpdoSD4r', + condition: 'true', + description: 'Show warning if hemoglobin is dangerously low', + displayName: 'Hemoglobin warning', + programId: 'lxAQ7Zs9VYR', + programRuleActions: [ + { + id: 'nKNmayYigcy', + programRuleActionType: 'HIDESECTION', + }, + ], + }, + ]; + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + currentEvent, + dataElements: dataElementsInProgram, + selectedOrgUnit: orgUnit, + optionSets, + }); + + // then + expect(rulesEffects).toEqual([]); + }); +}); + +describe('Program Rule Variables corner cases', () => { + const constants = []; + const orgUnit = { id: 'DiszpKrYNg8', name: 'Ngelehun CHC' }; + const optionSets = {}; + const programRules = [ + { + id: 'g82J3xsNer9', + condition: 'true', + displayName: 'Testing the variables source type', + programId: 'PNClHaZARtz', + programRuleActions: [ + { + id: 'Eeb7Ixr4Pvx', + displayContent: "d2:left('dhis', 3) = ", + data: "d2:left('dhis', 3)", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'ElktIxr4Pvx', + displayContent: "d2:left('dhis', 3) = ", + data: "d2:left('dhis', 3)", + content: 'event_status', + programRuleActionType: 'ASSIGN', + }, + ], + }, + ]; + + test('without currentEvent and without otherEvents', () => { + // given + const dataElementsInProgram = { + f8j4XDEozvj: { id: 'f8j4XDEozvj', valueType: 'INTEGER', optionSetId: undefined }, + }; + const currentEvent = undefined; + const programRuleVariables = [ + { + id: 'DoRHHfNPccb', + dataElementId: 'f8j4XDEozvj', + displayName: 'INFECTION_SOURCE', + programId: 'PNClHaZARtz', + programRuleVariableSourceType: 'DATAELEMENT_CURRENT_EVENT', + useNameForOptionSet: false, + }, + { + id: 'lokHHfNPccb', + dataElementId: 'f8j4XDEozvj', + displayName: 'INFECTION_SOURCE', + programId: 'PNClHaZARtz', + programRuleVariableSourceType: 'DATAELEMENT_PREVIOUS_EVENT', + useNameForOptionSet: false, + }, + { + id: 'DolgHfNPccb', + dataElementId: 'f8j4XDEozvj', + displayName: 'INFECTION_SOURCE', + programId: 'PNClHaZARtz', + programRuleVariableSourceType: 'DATAELEMENT_NEWEST_EVENT_PROGRAM', + useNameForOptionSet: false, + }, + ]; + + // when + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + selectedOrgUnit: orgUnit, + optionSets, + currentEvent, + dataElements: dataElementsInProgram, + }); + + // then + expect(rulesEffects).toEqual([ + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Eeb7Ixr4Pvx', message: "d2:left('dhis', 3) = dhi" }, + }, + ]); + }); + + test('without currentEvent and with otherEvents', () => { + // given + const dataElementsInProgram = { + f8j4XDEozvj: { id: 'f8j4XDEozvj', valueType: 'INTEGER', optionSetId: undefined }, + }; + const currentEvent = undefined; + const otherEvents = [ + { + da1Id: 'otherEventText', + dueDate: '2021-05-31T09:51:38.134', + enrollmentId: 'vVtmDlsu3me', + enrollmentStatus: 'ACTIVE', + eventDate: '2021-05-31T00:00:00.000', + eventId: 'BxGzDJK3JqN', + orgUnitId: 'DiszpKrYNg8', + orgUnitName: 'Ngelehun CHC', + programId: 'IpHINAT79UW', + programStageId: 'A03MvHHogjR', + status: 'ACTIVE', + trackedEntityInstanceId: 'vCGpQAWG17I', + occurredAt: '2021-05-31T00:00:00.000', + }, + ]; + const programRuleVariables = [ + { + id: 'lokHHfNPccb', + dataElementId: 'f8j4XDEozvj', + displayName: 'INFECTION_SOURCE', + programId: 'PNClHaZARtz', + programRuleVariableSourceType: 'DATAELEMENT_PREVIOUS_EVENT', + useNameForOptionSet: false, + }, + ]; + + // when + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + selectedOrgUnit: orgUnit, + optionSets, + currentEvent, + dataElements: dataElementsInProgram, + otherEvents, + }); + + // then + expect(rulesEffects).toEqual([ + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Eeb7Ixr4Pvx', message: "d2:left('dhis', 3) = dhi" }, + }, + ]); + }); + + test('with currentEvent and with otherEvents', () => { + // given + const dataElementsInProgram = { + f8j4XDEozvj: { id: 'f8j4XDEozvj', valueType: 'INTEGER', optionSetId: undefined }, + GieVkTxp4HH: { id: 'GieVkTxp4HH', valueType: 'NUMBER', optionSetId: undefined }, + }; + const currentEvent = { + occurredAt: '2020-07-14T22:00:00.000Z', + da1Id: 'currentEventText', + dueDate: '2021-05-31T09:51:38.134', + enrollmentId: 'vVtmDlsu3me', + enrollmentStatus: 'ACTIVE', + eventDate: '2021-05-31T00:00:00.000', + eventId: 'BlgrDJK3JqN', + orgUnitId: 'DiszpKrYNg8', + orgUnitName: 'Ngelehun CHC', + programId: 'IpHINAT79UW', + programStageId: 'A03MvHHogjR', + status: 'ACTIVE', + trackedEntityInstanceId: 'vCsfsAWG17I', + }; + const otherEvents = [ + { + da1Id: 'otherEventText', + dueDate: '2021-05-31T09:51:38.134', + enrollmentId: 'vVtmDlsu3me', + enrollmentStatus: 'ACTIVE', + eventDate: '2021-05-31T00:00:00.000', + eventId: 'BxGzDJK3JqN', + orgUnitId: 'DiszpKrYNg8', + orgUnitName: 'Ngelehun CHC', + programId: 'IpHINAT79UW', + programStageId: 'A03MvHHogjR', + status: 'ACTIVE', + trackedEntityInstanceId: 'vCGpQAWG17I', + occurredAt: '2021-05-31T00:00:00.000', + }, + ]; + const programRuleVariables = [ + { + id: 'lokHHfNPccb', + dataElementId: 'f8j4XDEozvj', + displayName: 'INFECTION_SOURCE', + programId: 'IpHINAT79UW', + programRuleVariableSourceType: 'DATAELEMENT_PREVIOUS_EVENT', + useNameForOptionSet: false, + }, + { + id: 'ZghUnCAulEk.GieVkTxp4HH', + displayName: 'ZghUnCAulEk.GieVkTxp4HH', + programRuleVariableSourceType: 'DATAELEMENT_NEWEST_EVENT_PROGRAM_STAGE', + programStageId: 'A03MvHHogjR', + dataElementId: 'GieVkTxp4HH', + programId: 'IpHINAT79UW', + }, + { + id: 'Zj7UnsdhlEk.GieVkTxp4HH', + displayName: 'Zj7UnsdhlEk.GieVkTxp4HH', + programRuleVariableSourceType: 'DATAELEMENT_NEWEST_EVENT_PROGRAM_STAGE', + dataElementId: 'GieVkTxp4HH', + programId: 'IpHINAT79UW', + }, + { + id: 'Zj7luCAulEk.GieVkTxp4HH', + displayName: 'Zj7luCAulEk.GieVkTxp4HH', + programRuleVariableSourceType: 'DATAELEMENT_NEWEST_EVENT_PROGRAM_STAGE', + programStageId: 'AsdfMvHHgjR', + dataElementId: 'GieVkTxp4HH', + programId: 'IpHINAT79UW', + }, + ]; + + // when + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + selectedOrgUnit: orgUnit, + optionSets, + currentEvent, + dataElements: dataElementsInProgram, + otherEvents, + }); + + // then + expect(rulesEffects).toEqual([ + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Eeb7Ixr4Pvx', message: "d2:left('dhis', 3) = dhi" }, + }, + ]); + }); +});