Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [DHIS2-12544] Add verbose logging to rules engine #3480

Merged
merged 11 commits into from
Dec 18, 2023
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';
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]);
};
Loading