Skip to content

Commit

Permalink
Add data condition variable (#6239)
Browse files Browse the repository at this point in the history
* Add data condition variable

* Format

---------

Co-authored-by: Artur Arseniev <[email protected]>
  • Loading branch information
mohamedsalem401 and artf authored Oct 22, 2024
1 parent c434b0a commit 5d5ff04
Show file tree
Hide file tree
Showing 14 changed files with 839 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Operator } from './operators';

export class ConditionStatement {
constructor(
private leftValue: any,
private operator: Operator,
private rightValue: any,
) {}

evaluate(): boolean {
return this.operator.evaluate(this.leftValue, this.rightValue);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { NumberOperation } from './operators/NumberOperator';
import { StringOperation } from './operators/StringOperations';
import { GenericOperation } from './operators/GenericOperator';
import { Model } from '../../../common';
import { LogicalOperation } from './operators/LogicalOperator';
import { evaluateCondition } from './evaluateCondition';

export const ConditionalVariableType = 'conditional-variable';
export type Expression = {
left: any;
operator: GenericOperation | StringOperation | NumberOperation;
right: any;
};

export type LogicGroup = {
logicalOperator: LogicalOperation;
statements: (Expression | LogicGroup | boolean)[];
};

export class DataCondition extends Model {
private conditionResult: boolean;

defaults() {
return {
type: ConditionalVariableType,
condition: false,
};
}

constructor(
private condition: Expression | LogicGroup | boolean,
private ifTrue: any,
private ifFalse: any,
) {
super();
this.conditionResult = this.evaluate();
}

evaluate() {
return evaluateCondition(this.condition);
}

getDataValue(): any {
return this.conditionResult ? this.ifTrue : this.ifFalse;
}

reevaluate(): void {
this.conditionResult = this.evaluate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { LogicalOperator } from './operators/LogicalOperator';
import { Expression, LogicGroup } from './DataCondition';
import { evaluateCondition } from './evaluateCondition';

export class LogicalGroupStatement {
constructor(
private operator: LogicalOperator,
private statements: (Expression | LogicGroup | boolean)[],
) {}

evaluate(): boolean {
const results = this.statements.map((statement) => evaluateCondition(statement));
return this.operator.evaluate(results);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { ConditionStatement } from './ConditionStatement';
import { Expression, LogicGroup } from './DataCondition';
import { LogicalGroupStatement } from './LogicalGroupStatement';
import { Operator } from './operators';
import { GenericOperation, GenericOperator } from './operators/GenericOperator';
import { LogicalOperator } from './operators/LogicalOperator';
import { NumberOperation, NumberOperator } from './operators/NumberOperator';
import { StringOperation, StringOperator } from './operators/StringOperations';

export function evaluateCondition(condition: any): boolean {
if (typeof condition === 'boolean') {
return condition;
}

if (isLogicGroup(condition)) {
const { logicalOperator, statements } = condition;
const op = new LogicalOperator(logicalOperator);
const logicalGroup = new LogicalGroupStatement(op, statements);
return logicalGroup.evaluate();
}

if (isCondition(condition)) {
const { left, operator, right } = condition;
const op = operatorFactory(left, operator);
const statement = new ConditionStatement(left, op, right);
return statement.evaluate();
}

throw new Error('Invalid condition type.');
}

function operatorFactory(left: any, operator: string): Operator {
if (isOperatorInEnum(operator, GenericOperation)) {
return new GenericOperator(operator as GenericOperation);
} else if (typeof left === 'number') {
return new NumberOperator(operator as NumberOperation);
} else if (typeof left === 'string') {
return new StringOperator(operator as StringOperation);
}
throw new Error(`Unsupported data type: ${typeof left}`);
}

function isOperatorInEnum(operator: string, enumObject: any): boolean {
return Object.values(enumObject).includes(operator);
}

function isLogicGroup(condition: any): condition is LogicGroup {
return condition && typeof condition.logicalOperator !== 'undefined' && Array.isArray(condition.statements);
}

function isCondition(condition: any): condition is Expression {
return condition && typeof condition.left !== 'undefined' && typeof condition.operator === 'string';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import DataVariable from '../../DataVariable';
import { Operator } from '.';

export enum GenericOperation {
equals = 'equals',
isTruthy = 'isTruthy',
isFalsy = 'isFalsy',
isDefined = 'isDefined',
isNull = 'isNull',
isUndefined = 'isUndefined',
isArray = 'isArray',
isObject = 'isObject',
isString = 'isString',
isNumber = 'isNumber',
isBoolean = 'isBoolean',
isDefaultValue = 'isDefaultValue', // For Datasource variables
}

export class GenericOperator extends Operator {
constructor(private operator: GenericOperation) {
super();
}

evaluate(left: any, right: any): boolean {
switch (this.operator) {
case 'equals':
return left === right;
case 'isTruthy':
return !!left;
case 'isFalsy':
return !left;
case 'isDefined':
return left !== undefined && left !== null;
case 'isNull':
return left === null;
case 'isUndefined':
return left === undefined;
case 'isArray':
return Array.isArray(left);
case 'isObject':
return typeof left === 'object' && left !== null;
case 'isString':
return typeof left === 'string';
case 'isNumber':
return typeof left === 'number';
case 'isBoolean':
return typeof left === 'boolean';
case 'isDefaultValue':
return left instanceof DataVariable && left.get('default') === right;
default:
throw new Error(`Unsupported generic operator: ${this.operator}`);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Operator } from '.';

export enum LogicalOperation {
and = 'and',
or = 'or',
xor = 'xor',
}

export class LogicalOperator extends Operator {
constructor(private operator: LogicalOperation) {
super();
}

evaluate(statements: boolean[]): boolean {
if (!statements.length) throw new Error('Expected one or more statments, got none');

switch (this.operator) {
case LogicalOperation.and:
return statements.every(Boolean);
case LogicalOperation.or:
return statements.some(Boolean);
case LogicalOperation.xor:
return statements.filter(Boolean).length === 1;
default:
throw new Error(`Unsupported logical operator: ${this.operator}`);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Operator } from '.';

export enum NumberOperation {
greaterThan = '>',
lessThan = '<',
greaterThanOrEqual = '>=',
lessThanOrEqual = '<=',
equals = '=',
notEquals = '!=',
}

export class NumberOperator extends Operator {
constructor(private operator: NumberOperation) {
super();
}

evaluate(left: number, right: number): boolean {
switch (this.operator) {
case NumberOperation.greaterThan:
return left > right;
case NumberOperation.lessThan:
return left < right;
case NumberOperation.greaterThanOrEqual:
return left >= right;
case NumberOperation.lessThanOrEqual:
return left <= right;
case NumberOperation.equals:
return left === right;
case NumberOperation.notEquals:
return left !== right;
default:
throw new Error(`Unsupported number operator: ${this.operator}`);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Operator } from '.';

export enum StringOperation {
contains = 'contains',
startsWith = 'startsWith',
endsWith = 'endsWith',
matchesRegex = 'matchesRegex',
}

export class StringOperator extends Operator {
constructor(private operator: StringOperation) {
super();
}

evaluate(left: string, right: string): boolean {
switch (this.operator) {
case StringOperation.contains:
return left.includes(right);
case StringOperation.startsWith:
return left.startsWith(right);
case StringOperation.endsWith:
return left.endsWith(right);
case StringOperation.matchesRegex:
return new RegExp(right).test(left);
default:
throw new Error(`Unsupported string operator: ${this.operator}`);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export abstract class Operator {
abstract evaluate(left: any, right: any): boolean;
}
Loading

0 comments on commit 5d5ff04

Please sign in to comment.