Skip to content

Commit

Permalink
feat: [DHIS2-12544] Add verbose logging to rules engine (#3480)
Browse files Browse the repository at this point in the history
  • Loading branch information
eirikhaugstulen authored Dec 18, 2023
1 parent 4935fa0 commit 2a6d4a8
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 7 deletions.
22 changes: 22 additions & 0 deletions packages/rules-engine/src/RulesEngine.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
IConvertInputRulesValue,
IConvertOutputRulesEffectsValue,
IDateUtils,
Flag,
} from './rulesEngine.types';
import { getRulesEffectsProcessor } from './processors/rulesEffectsProcessor/rulesEffectsProcessor';
import { effectActions, typeof environmentTypes } from './constants';
Expand All @@ -21,18 +22,21 @@ export class RulesEngine {
variableService: VariableService;
dateUtils: IDateUtils;
userRoles: Array<string>;
flags: Flag;

constructor(
inputConverter: IConvertInputRulesValue,
outputConverter: IConvertOutputRulesEffectsValue,
dateUtils: IDateUtils,
environment: $Values<environmentTypes>,
flags?: Flag,
) {
this.inputConverter = inputConverter;
this.outputConverter = outputConverter;
this.valueProcessor = new ValueProcessor(inputConverter);
this.variableService = new VariableService(this.valueProcessor.processValue, dateUtils, environment);
this.dateUtils = dateUtils;
this.flags = flags ?? {};
}

/**
Expand Down Expand Up @@ -114,10 +118,15 @@ export class RulesEngine {
expression,
dhisFunctions,
variablesHash,
flags: this.flags,
onError: (error, injectedExpression) => log.warn(
`Expression with id rule:${rule.id} could not be run. ` +
`Original condition was: ${expression} - ` +
`Evaluation ended up as:${injectedExpression} - error message:${error}`),
onVerboseLog: injectedExpression => console.log(
`Expression with id rule:${rule.id} was run. ` +
`Original condition was: ${expression} - ` +
`Evaluation ended up as:${injectedExpression}`),
});
} else {
log.warn(`Rule id:'${rule.id}' and name:'${rule.displayName}' ` +
Expand Down Expand Up @@ -149,10 +158,15 @@ export class RulesEngine {
expression: actionExpression,
dhisFunctions,
variablesHash,
flags: this.flags,
onError: (error, injectedExpression) => log.warn(
`Expression with id rule: action:${id} could not be run. ` +
`Original condition was: ${actionExpression} - ` +
`Evaluation ended up as:${injectedExpression} - error message:${error}`),
onVerboseLog: injectedExpression => log.info(
`Expression with id rule: action:${id} was run. ` +
`Original condition was: ${actionExpression} - ` +
`Evaluation ended up as: ${injectedExpression}`),
});
}

Expand Down Expand Up @@ -196,4 +210,12 @@ export class RulesEngine {
setSelectedUserRoles(userRoles: Array<string>) {
this.userRoles = userRoles;
}

setFlags(flags: Flag) {
this.flags = flags;
}

getFlags(): Flag {
return this.flags;
}
}
2 changes: 1 addition & 1 deletion packages/rules-engine/src/rulesEngine.types.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,5 +230,5 @@ export type IConvertOutputRulesEffectsValue = {|
|};

export type Flag = {
debug: boolean
verbose: boolean,
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,9 @@ export const executeExpression = ({
expression,
dhisFunctions,
variablesHash,
flags = {},
onError,
onVerboseLog,
}: ExecuteExpressionInput) => {
const expressionWithInjectedVariableValues = injectVariableValues(expression, variablesHash);

Expand All @@ -191,6 +193,10 @@ export const executeExpression = ({
removeNewLinesFromNonStrings(expressionWithInjectedVariableValues, expressionModuloStrings),
onError,
);

if (flags.verbose) {
onVerboseLog(expressionWithInjectedVariableValues);
}
} catch (error) {
onError(error.message, expressionWithInjectedVariableValues);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @flow
import type { RuleVariables } from '../../rulesEngine.types';
import type { Flag, RuleVariables } from '../../rulesEngine.types';
import type { D2Functions, D2FunctionConfig } from '../../d2Functions';

export type ExpressionSet = $ReadOnly<{|
Expand All @@ -23,5 +23,7 @@ export type ExecuteExpressionInput = $ReadOnly<{|
expression: string,
dhisFunctions: D2Functions,
variablesHash: RuleVariables,
flags?: Flag,
onError: ErrorHandler,
onVerboseLog: (expressionWithInjectedVariableValues: string) => void,
|}>;
10 changes: 8 additions & 2 deletions src/components/App/App.component.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
// @flow
/* eslint-disable import/first */
import './app.css';
import * as React from 'react';
import React from 'react';
import { Provider } from 'react-redux';
import D2UIApp from '@dhis2/d2-ui-app';
import { AppContents } from './AppContents.component';
import {
RulesEngineVerboseInitializer,
} from '../../core_modules/capture-core/components/RulesEngineVerboseInitializer';


type Props = {
store: ReduxStore,
Expand All @@ -16,7 +20,9 @@ export const App = ({ store }: Props) => (
store={store}
>
<D2UIApp>
<AppContents />
<RulesEngineVerboseInitializer>
<AppContents />
</RulesEngineVerboseInitializer>
</D2UIApp>
</Provider>
</React.Fragment>
Expand Down
4 changes: 2 additions & 2 deletions src/components/App/AppContents.component.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

// @flow
import React from 'react';
import React, { memo } from 'react';
import { withStyles } from '@material-ui/core/styles';
import { systemSettingsStore } from 'capture-core/metaDataMemoryStores';
import { FeedbackBar } from 'capture-core/components/FeedbackBar';
Expand Down Expand Up @@ -30,4 +30,4 @@ const Index = ({ classes }: Props) => (
);
Index.displayName = 'AppContents';

export const AppContents = withStyles(getStyles)(Index);
export const AppContents = withStyles(getStyles)(memo(Index));
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// @flow
import { useRuleEngineFlags } from '../../rules/useRuleEngineFlags';

type Props = {|
children: React$Node,
|};
export const RulesEngineVerboseInitializer = ({ children }: Props) => {
useRuleEngineFlags();

return children;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// @flow
export { RulesEngineVerboseInitializer } from './RulesEngineVerboseInitializer';
50 changes: 50 additions & 0 deletions src/core_modules/capture-core/rules/__tests__/rulesEngine.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,56 @@ describe('Rules engine', () => {
// then
expect(rulesEffects).toEqual([]);
});

test('The rules engine can enable verbose logging', () => {
// When
rulesEngine.setFlags({ verbose: true });

// Then
expect(rulesEngine.getFlags()).toEqual({ verbose: true });
});

test('Rules are calculated when verbose is set', () => {
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',
},
],
},
];

// When
rulesEngine.setFlags({ verbose: true });
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 ',
},
},
]);
});
});

describe('Program Rule Variables corner cases', () => {
Expand Down
7 changes: 6 additions & 1 deletion src/core_modules/capture-core/rules/rulesEngine.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,9 @@ import {
dateUtils,
} from './converters';

export const rulesEngine = new RulesEngine(inputConverter, outputConverter, dateUtils, environmentTypes.WebClient);
export const rulesEngine = new RulesEngine(
inputConverter,
outputConverter,
dateUtils,
environmentTypes.WebClient,
);
23 changes: 23 additions & 0 deletions src/core_modules/capture-core/rules/useRuleEngineFlags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// @flow
import { useLayoutEffect } from 'react';
import { useLocationQuery } from '../utils/routing';
import { rulesEngine } from './rulesEngine';

export const useRuleEngineFlags = () => {
// This hook is used to set the verbose flag on the rules engine
// based on the verbose query param in the URL

const { verbose } = useLocationQuery();

const updateFlags = (flags) => {
rulesEngine.setFlags({ ...rulesEngine.getFlags(), ...flags });
};

useLayoutEffect(() => {
if (verbose === 'true') {
updateFlags({ verbose: true });
} else {
updateFlags({ verbose: false });
}
}, [verbose]);
};

0 comments on commit 2a6d4a8

Please sign in to comment.