Skip to content

Commit

Permalink
feat(dpf-18714): inNextMinutes operator
Browse files Browse the repository at this point in the history
  • Loading branch information
vscaiceanu-1a committed Oct 16, 2023
1 parent c38beac commit f37ddb4
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 56 deletions.
86 changes: 44 additions & 42 deletions docs/rules-engine/operators.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,48 +26,50 @@ Example of usage :

`[]` means the variable is an array of primitives

| Type | Operator | Display | Description |
| ----------- | ------------------------- | ---------------- | ----------- |
| All | equals | is equal to | Check if a variable is equal to a specific value |
| All | notEquals | is not equal to | Check if a variable is different than a specific value |
| [All] | allEqual | all equal to | Check if every values of the variable equals a specific value |
| [All] | oneEquals | one equal to | Check if at least one of the values of the variable equals a specific value |
| [All] | oneIn | one in | Check if at least one of the values of the variable is equal to one in a specified list |
| [All] | allIn | all in | Check if every values of the variable are in a specific list |
| [All] | allNotIn | none in | Check if every values of the variable are not in a specific list |
| [All] | arrayContains | contains | Check if any of the variable's value is equal to a specific value |
| [All] | notArrayContains | does not contain | Check if every values of the variable are different than a specific value |
| All | inArray | is in | Check if the variable's value is included in a specified list |
| All | notInArray | is not in | Check if the variable's value is not included in the value list |
| All | isDefined | is defined | Check if the variable and its value is defined |
| All | isUndefined | is not defined | Check if the variable and its value is undefined |
| Date | inRangeDate | is between | Check if a date variable is in a specified date range |
| Date | dateBefore | is before | Check if a date variable is prior than a specified date |
| Date | dateAfter | is after | Check if a date variable is posterior than a specified date |
| Date | dateEquals | is equal to | Check if a date variable is the same as a specified date |
| Date | dateNotEquals | is not equal to | Check if a date variable is different from a specified date |
| Number | lessOrEqual || Check if the number variable is lower or equal to a specific value |
| Number | lessThan | < | Check if the number variable is lower than a specific value |
| Number | greaterThanOrEqual || Check if the number variable is greater or equal to a specific value |
| Number | greaterThan | > | Check if the number variable is greater than a specific value |
| [Number] | allLower | all < | Check if every numerical values of the variable are lower than a specific value |
| [Number] | allGreater | all > | Check if every numerical values of the variable are greater than a specific value |
| [Number] | oneLower | one < | Check if one of the values of the variable is lower than a specific value |
| [Number] | oneGreater | one > | Check if one of the values of the variable is greater than a specific value |
| [Number] | allRangeNumber | all between | Check if every values of the variable are included in a specified range |
| [Number] | oneRangeNumber | one between | Check if one of the values of the variable is included in a specified range |
| String | inString | within | Check if the text variable is part of the specified value |
| String | notInString | not within | Check if the text variable is not part in the specified value |
| String | stringContains | contains | Check if the specified text value is included in the text variable |
| String | notStringContains | does not contain | Check if the specified text value is not included in the text variable |
| [String] | allMatch | all match | Check if every string values of the variable match a specific pattern |
| [String] | oneMatches | one matches | Check if one of the values of the variable matches a specific pattern |
| [All] | lengthEquals | number of = | Check if the number of values of the variable is equal to a specific value |
| [All] | lengthNotEquals | number of ≠ | Check if the number of values of the variable is different from a specific value |
| [All] | lengthLessThanOrEquals | number of ≤ | Check if the number of values of the variable is lower or equal to a specific value |
| [All] | lengthLessThan | number of < | Check if the number of values of the variable is lower than a specific value |
| [All] | lengthGreaterThanOrEquals | number of ≥ | Check if the number of values of the variable is greater or equal to a specific value |
| [All] | lengthGreaterThan | number of > | Check if the number of values of the variable is greater than a specific value |
| Type | Operator | Display | Description |
| ----------- | ------------------------- | ---------------------- | ----------- |
| All | equals | is equal to | Check if a variable is equal to a specific value |
| All | notEquals | is not equal to | Check if a variable is different than a specific value |
| [All] | allEqual | all equal to | Check if every values of the variable equals a specific value |
| [All] | oneEquals | one equal to | Check if at least one of the values of the variable equals a specific value |
| [All] | oneIn | one in | Check if at least one of the values of the variable is equal to one in a specified list |
| [All] | allIn | all in | Check if every values of the variable are in a specific list |
| [All] | allNotIn | none in | Check if every values of the variable are not in a specific list |
| [All] | arrayContains | contains | Check if any of the variable's value is equal to a specific value |
| [All] | notArrayContains | does not contain | Check if every values of the variable are different than a specific value |
| All | inArray | is in | Check if the variable's value is included in a specified list |
| All | notInArray | is not in | Check if the variable's value is not included in the value list |
| All | isDefined | is defined | Check if the variable and its value is defined |
| All | isUndefined | is not defined | Check if the variable and its value is undefined |
| Date | inRangeDate | is between | Check if a date variable is in a specified date range |
| Date | dateInNextMinutes | is in next minutes | Check if a date is in the next specified minutes |
| Date | dateNotInNextMinutes | is not in next minutes | Check if a date is not in the next specified minutes |
| Date | dateBefore | is before | Check if a date variable is prior than a specified date |
| Date | dateAfter | is after | Check if a date variable is posterior than a specified date |
| Date | dateEquals | is equal to | Check if a date variable is the same as a specified date |
| Date | dateNotEquals | is not equal to | Check if a date variable is different from a specified date |
| Number | lessOrEqual || Check if the number variable is lower or equal to a specific value |
| Number | lessThan | < | Check if the number variable is lower than a specific value |
| Number | greaterThanOrEqual || Check if the number variable is greater or equal to a specific value |
| Number | greaterThan | > | Check if the number variable is greater than a specific value |
| [Number] | allLower | all < | Check if every numerical values of the variable are lower than a specific value |
| [Number] | allGreater | all > | Check if every numerical values of the variable are greater than a specific value |
| [Number] | oneLower | one < | Check if one of the values of the variable is lower than a specific value |
| [Number] | oneGreater | one > | Check if one of the values of the variable is greater than a specific value |
| [Number] | allRangeNumber | all between | Check if every values of the variable are included in a specified range |
| [Number] | oneRangeNumber | one between | Check if one of the values of the variable is included in a specified range |
| String | inString | within | Check if the text variable is part of the specified value |
| String | notInString | not within | Check if the text variable is not part in the specified value |
| String | stringContains | contains | Check if the specified text value is included in the text variable |
| String | notStringContains | does not contain | Check if the specified text value is not included in the text variable |
| [String] | allMatch | all match | Check if every string values of the variable match a specific pattern |
| [String] | oneMatches | one matches | Check if one of the values of the variable matches a specific pattern |
| [All] | lengthEquals | number of = | Check if the number of values of the variable is equal to a specific value |
| [All] | lengthNotEquals | number of ≠ | Check if the number of values of the variable is different from a specific value |
| [All] | lengthLessThanOrEquals | number of ≤ | Check if the number of values of the variable is lower or equal to a specific value |
| [All] | lengthLessThan | number of < | Check if the number of values of the variable is lower than a specific value |
| [All] | lengthGreaterThanOrEquals | number of ≥ | Check if the number of values of the variable is greater or equal to a specific value |
| [All] | lengthGreaterThan | number of > | Check if the number of values of the variable is greater than a specific value |


You can create your own operator in your application and add it to the engine.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('Operator helpers', () => {
expect(operator.validateRhs).toHaveBeenCalledTimes(1);
expect(operator.validateLhs).toHaveBeenCalledWith('testLhs');
expect(operator.validateRhs).toHaveBeenCalledWith('testRhs');
expect(operator.evaluator).toHaveBeenCalledWith('testLhs', 'testRhs');
expect(operator.evaluator).toHaveBeenCalledWith('testLhs', 'testRhs', undefined);
});

test('should not evaluate the condition if the checks are not passed', () => {
Expand Down Expand Up @@ -77,7 +77,7 @@ describe('Operator helpers', () => {
};

expect(executeOperator('testLhs', 'testRhs', operator)).toBeTruthy();
expect(operator.evaluator).toHaveBeenCalledWith('testLhs', 'testRhs');
expect(operator.evaluator).toHaveBeenCalledWith('testLhs', 'testRhs', undefined);
});

test('with promise value', () => {
Expand All @@ -87,7 +87,7 @@ describe('Operator helpers', () => {
};

expect(executeOperator('testLhs', 'testRhs', operator)).toBeTruthy();
expect(operator.evaluator).toHaveBeenCalledWith('testLhs', 'testRhs');
expect(operator.evaluator).toHaveBeenCalledWith('testLhs', 'testRhs', undefined);
});

test('with observable value', () => {
Expand All @@ -97,7 +97,7 @@ describe('Operator helpers', () => {
};

expect(executeOperator('testLhs', 'testRhs', operator)).toBeTruthy();
expect(operator.evaluator).toHaveBeenCalledWith('testLhs', 'testRhs');
expect(operator.evaluator).toHaveBeenCalledWith('testLhs', 'testRhs', undefined);
});
});
});
Expand Down
21 changes: 17 additions & 4 deletions packages/@o3r/rules-engine/src/engine/operator/operator.helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {DateInput, Operator, SupportedSimpleTypes} from './operator.interface';
import type {Facts} from '../engine.interface';

/**
* Execute Operator
Expand All @@ -7,16 +8,28 @@ import {DateInput, Operator, SupportedSimpleTypes} from './operator.interface';
* @param rhs Right hand side
* @param operator Operator to compare values
*/
export function executeOperator<L = unknown, R = unknown>(lhs: L, rhs: R, operator: Operator<L, R>) {
export function executeOperator<L = unknown, R = unknown>(lhs: L, rhs: R, operator: Operator<L, R>, operatorFacts?: Record<string, Facts | undefined>) {
const validLhs = (!operator.validateLhs || operator.validateLhs(lhs));
const validRhs = (!operator.validateRhs || operator.validateRhs(rhs));
let operatorFactValues: Record<string, Facts> | undefined;
if (operatorFacts && operator.dependencies) {
operatorFactValues = operator.dependencies.reduce((acc, dep) => {
if (operatorFacts[dep]) {
acc[dep] = operatorFacts[dep]!;
}
return acc;
}, {} as Record<string, Facts>);
if (!(!operator.validateOperatorFactValues || operator.validateOperatorFactValues(operatorFactValues))) {
throw new Error(`Invalid operator fact values: ${JSON.stringify(operatorFactValues)}`);
}
}
if (!validLhs) {
throw new Error(`Invalid left operand : ${JSON.stringify(lhs)}`);
throw new Error(`Invalid left operand: ${JSON.stringify(lhs)}`);
}
if (!validRhs) {
throw new Error(`Invalid right operand : ${JSON.stringify(rhs)}`);
throw new Error(`Invalid right operand: ${JSON.stringify(rhs)}`);
}
const obs = operator.evaluator(lhs, rhs);
const obs = operator.evaluator(lhs, rhs, operatorFactValues);
return obs;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Facts } from '../engine.interface';

/**
* Rule Engine operator
*/
Expand All @@ -10,8 +12,12 @@ export interface Operator<LeftExposed = unknown, RightExposed = unknown, LeftSup
validateLhs?: unknown extends LeftSupported ? (operand: unknown) => boolean : (operand: unknown) => operand is LeftSupported;
/** Right Hand Value validator function */
validateRhs?: unknown extends RightSupported ? (operand: unknown) => boolean : (operand: unknown) => operand is RightSupported;
/** Operator Fact Values validator function */
validateOperatorFactValues?: unknown extends Record<string, Facts> ? (operators: unknown) => boolean : (operators: unknown) => operators is Record<string, Facts>;
/** Evaluate the values */
evaluator: (lhs: LeftSupported, rhs: RightSupported) => boolean;
evaluator: (lhs: LeftSupported, rhs: RightSupported, operatorFactValues?: Record<string, Facts>) => boolean;
/** Dependencies */
dependencies?: string[];
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
/* eslint-disable camelcase */
/* eslint-disable @typescript-eslint/naming-convention */
import { executeOperator } from '..';
import {
dateAfter,
dateBefore,
dateEquals,
dateInNextMinutes,
dateNotEquals,
dateNotInNextMinutes,
inRangeDate
} from './date-based.operators';

Expand All @@ -13,16 +17,16 @@ describe('Operators', () => {
const now = new Date();
const tomorrow = new Date(new Date(now).setHours(0, 0, 0, 0) + millisecondsInADay);
const yesterday = new Date(new Date(now).setHours(0, 0, 0, 0) - millisecondsInADay);
const inTwoDays = new Date(new Date(now).setHours(0, 0, 0, 0) + 2 * millisecondsInADay);

describe('inRangeDate', () => {
it('should have a valid name', () => {
expect(inRangeDate.name).toBe('inRangeDate');
});

it('should invalid when dates is invalid', () => {
it('should invalid when dates are invalid', () => {
expect(() => executeOperator('invalid date' as any, [] as any, inRangeDate)).toThrow();
expect(() => executeOperator(null as any, [] as any, inRangeDate)).toThrow();
expect(() => executeOperator(null as any, [] as any, inRangeDate)).toThrow();
expect(() => executeOperator(now, ['invalid date'] as any, inRangeDate)).toThrow();
expect(() => executeOperator(now, ['invalid date', 'invalid date'], inRangeDate)).toThrow();
});
Expand All @@ -38,6 +42,52 @@ describe('Operators', () => {
});
});

describe('dateInNextMinutes', () => {
it('should have a valid name', () => {
expect(dateInNextMinutes.name).toBe('dateInNextMinutes');
});

it('should invalid when dates are invalid', () => {
expect(() => executeOperator('invalid date' as any, [] as any, dateInNextMinutes)).toThrow();
expect(() => executeOperator(null as any, [] as any, dateInNextMinutes)).toThrow();
expect(() => executeOperator(yesterday, 'invalid date', dateInNextMinutes)).toThrow();
expect(() => executeOperator(yesterday, -1, dateInNextMinutes)).toThrow();

});

it('should correctly check date in next minutes', () => {
expect(executeOperator(tomorrow, (24 * 60 + 1), dateInNextMinutes, {o3r_currentTime: now})).toBeTruthy();
// for past events, the operator should return false
expect(executeOperator(yesterday, 0, dateInNextMinutes, {o3r_currentTime: now})).toBeFalsy();
// range is from now to +24h but event is in two days. The operator returns false
expect(executeOperator(inTwoDays, (24 * 60 + 1), dateInNextMinutes, {o3r_currentTime: now})).toBeFalsy();

});
});

describe('dateNotInNextMinutes', () => {
it('should have a valid name', () => {
expect(dateNotInNextMinutes.name).toBe('dateNotInNextMinutes');
});

it('should invalid when dates are invalid', () => {
expect(() => executeOperator('invalid date' as any, 1, dateNotInNextMinutes)).toThrow();
expect(() => executeOperator(null as any, 0, dateNotInNextMinutes)).toThrow();
expect(() => executeOperator(now, 'invalid minutes' as any, dateNotInNextMinutes)).toThrow();
expect(() => executeOperator(now, 7, dateNotInNextMinutes)).toThrow();

});

it('should correctly check date not in next minutes', () => {
// event occuring tomorrow is not in 0 minutes from now
expect(executeOperator(tomorrow, 0, dateNotInNextMinutes, {o3r_currentTime: now})).toBeTruthy();
// event occuring in two days is not in 24h from now
expect(executeOperator(inTwoDays, (24 * 60 + 1), dateNotInNextMinutes, {o3r_currentTime: now})).toBeTruthy();
// event that occured yesterday is not in 0 minutes from now
expect(executeOperator(yesterday, 10, dateNotInNextMinutes, {o3r_currentTime: now})).toBeFalsy();
});
});

describe('dateBefore', () => {
it('should have a valid name', () => {
expect(dateBefore.name).toBe('dateBefore');
Expand Down
Loading

0 comments on commit f37ddb4

Please sign in to comment.