Skip to content

Commit 5d5ff04

Browse files
Add data condition variable (#6239)
* Add data condition variable * Format --------- Co-authored-by: Artur Arseniev <[email protected]>
1 parent c434b0a commit 5d5ff04

File tree

14 files changed

+839
-0
lines changed

14 files changed

+839
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Operator } from './operators';
2+
3+
export class ConditionStatement {
4+
constructor(
5+
private leftValue: any,
6+
private operator: Operator,
7+
private rightValue: any,
8+
) {}
9+
10+
evaluate(): boolean {
11+
return this.operator.evaluate(this.leftValue, this.rightValue);
12+
}
13+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { NumberOperation } from './operators/NumberOperator';
2+
import { StringOperation } from './operators/StringOperations';
3+
import { GenericOperation } from './operators/GenericOperator';
4+
import { Model } from '../../../common';
5+
import { LogicalOperation } from './operators/LogicalOperator';
6+
import { evaluateCondition } from './evaluateCondition';
7+
8+
export const ConditionalVariableType = 'conditional-variable';
9+
export type Expression = {
10+
left: any;
11+
operator: GenericOperation | StringOperation | NumberOperation;
12+
right: any;
13+
};
14+
15+
export type LogicGroup = {
16+
logicalOperator: LogicalOperation;
17+
statements: (Expression | LogicGroup | boolean)[];
18+
};
19+
20+
export class DataCondition extends Model {
21+
private conditionResult: boolean;
22+
23+
defaults() {
24+
return {
25+
type: ConditionalVariableType,
26+
condition: false,
27+
};
28+
}
29+
30+
constructor(
31+
private condition: Expression | LogicGroup | boolean,
32+
private ifTrue: any,
33+
private ifFalse: any,
34+
) {
35+
super();
36+
this.conditionResult = this.evaluate();
37+
}
38+
39+
evaluate() {
40+
return evaluateCondition(this.condition);
41+
}
42+
43+
getDataValue(): any {
44+
return this.conditionResult ? this.ifTrue : this.ifFalse;
45+
}
46+
47+
reevaluate(): void {
48+
this.conditionResult = this.evaluate();
49+
}
50+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { LogicalOperator } from './operators/LogicalOperator';
2+
import { Expression, LogicGroup } from './DataCondition';
3+
import { evaluateCondition } from './evaluateCondition';
4+
5+
export class LogicalGroupStatement {
6+
constructor(
7+
private operator: LogicalOperator,
8+
private statements: (Expression | LogicGroup | boolean)[],
9+
) {}
10+
11+
evaluate(): boolean {
12+
const results = this.statements.map((statement) => evaluateCondition(statement));
13+
return this.operator.evaluate(results);
14+
}
15+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { ConditionStatement } from './ConditionStatement';
2+
import { Expression, LogicGroup } from './DataCondition';
3+
import { LogicalGroupStatement } from './LogicalGroupStatement';
4+
import { Operator } from './operators';
5+
import { GenericOperation, GenericOperator } from './operators/GenericOperator';
6+
import { LogicalOperator } from './operators/LogicalOperator';
7+
import { NumberOperation, NumberOperator } from './operators/NumberOperator';
8+
import { StringOperation, StringOperator } from './operators/StringOperations';
9+
10+
export function evaluateCondition(condition: any): boolean {
11+
if (typeof condition === 'boolean') {
12+
return condition;
13+
}
14+
15+
if (isLogicGroup(condition)) {
16+
const { logicalOperator, statements } = condition;
17+
const op = new LogicalOperator(logicalOperator);
18+
const logicalGroup = new LogicalGroupStatement(op, statements);
19+
return logicalGroup.evaluate();
20+
}
21+
22+
if (isCondition(condition)) {
23+
const { left, operator, right } = condition;
24+
const op = operatorFactory(left, operator);
25+
const statement = new ConditionStatement(left, op, right);
26+
return statement.evaluate();
27+
}
28+
29+
throw new Error('Invalid condition type.');
30+
}
31+
32+
function operatorFactory(left: any, operator: string): Operator {
33+
if (isOperatorInEnum(operator, GenericOperation)) {
34+
return new GenericOperator(operator as GenericOperation);
35+
} else if (typeof left === 'number') {
36+
return new NumberOperator(operator as NumberOperation);
37+
} else if (typeof left === 'string') {
38+
return new StringOperator(operator as StringOperation);
39+
}
40+
throw new Error(`Unsupported data type: ${typeof left}`);
41+
}
42+
43+
function isOperatorInEnum(operator: string, enumObject: any): boolean {
44+
return Object.values(enumObject).includes(operator);
45+
}
46+
47+
function isLogicGroup(condition: any): condition is LogicGroup {
48+
return condition && typeof condition.logicalOperator !== 'undefined' && Array.isArray(condition.statements);
49+
}
50+
51+
function isCondition(condition: any): condition is Expression {
52+
return condition && typeof condition.left !== 'undefined' && typeof condition.operator === 'string';
53+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import DataVariable from '../../DataVariable';
2+
import { Operator } from '.';
3+
4+
export enum GenericOperation {
5+
equals = 'equals',
6+
isTruthy = 'isTruthy',
7+
isFalsy = 'isFalsy',
8+
isDefined = 'isDefined',
9+
isNull = 'isNull',
10+
isUndefined = 'isUndefined',
11+
isArray = 'isArray',
12+
isObject = 'isObject',
13+
isString = 'isString',
14+
isNumber = 'isNumber',
15+
isBoolean = 'isBoolean',
16+
isDefaultValue = 'isDefaultValue', // For Datasource variables
17+
}
18+
19+
export class GenericOperator extends Operator {
20+
constructor(private operator: GenericOperation) {
21+
super();
22+
}
23+
24+
evaluate(left: any, right: any): boolean {
25+
switch (this.operator) {
26+
case 'equals':
27+
return left === right;
28+
case 'isTruthy':
29+
return !!left;
30+
case 'isFalsy':
31+
return !left;
32+
case 'isDefined':
33+
return left !== undefined && left !== null;
34+
case 'isNull':
35+
return left === null;
36+
case 'isUndefined':
37+
return left === undefined;
38+
case 'isArray':
39+
return Array.isArray(left);
40+
case 'isObject':
41+
return typeof left === 'object' && left !== null;
42+
case 'isString':
43+
return typeof left === 'string';
44+
case 'isNumber':
45+
return typeof left === 'number';
46+
case 'isBoolean':
47+
return typeof left === 'boolean';
48+
case 'isDefaultValue':
49+
return left instanceof DataVariable && left.get('default') === right;
50+
default:
51+
throw new Error(`Unsupported generic operator: ${this.operator}`);
52+
}
53+
}
54+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Operator } from '.';
2+
3+
export enum LogicalOperation {
4+
and = 'and',
5+
or = 'or',
6+
xor = 'xor',
7+
}
8+
9+
export class LogicalOperator extends Operator {
10+
constructor(private operator: LogicalOperation) {
11+
super();
12+
}
13+
14+
evaluate(statements: boolean[]): boolean {
15+
if (!statements.length) throw new Error('Expected one or more statments, got none');
16+
17+
switch (this.operator) {
18+
case LogicalOperation.and:
19+
return statements.every(Boolean);
20+
case LogicalOperation.or:
21+
return statements.some(Boolean);
22+
case LogicalOperation.xor:
23+
return statements.filter(Boolean).length === 1;
24+
default:
25+
throw new Error(`Unsupported logical operator: ${this.operator}`);
26+
}
27+
}
28+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Operator } from '.';
2+
3+
export enum NumberOperation {
4+
greaterThan = '>',
5+
lessThan = '<',
6+
greaterThanOrEqual = '>=',
7+
lessThanOrEqual = '<=',
8+
equals = '=',
9+
notEquals = '!=',
10+
}
11+
12+
export class NumberOperator extends Operator {
13+
constructor(private operator: NumberOperation) {
14+
super();
15+
}
16+
17+
evaluate(left: number, right: number): boolean {
18+
switch (this.operator) {
19+
case NumberOperation.greaterThan:
20+
return left > right;
21+
case NumberOperation.lessThan:
22+
return left < right;
23+
case NumberOperation.greaterThanOrEqual:
24+
return left >= right;
25+
case NumberOperation.lessThanOrEqual:
26+
return left <= right;
27+
case NumberOperation.equals:
28+
return left === right;
29+
case NumberOperation.notEquals:
30+
return left !== right;
31+
default:
32+
throw new Error(`Unsupported number operator: ${this.operator}`);
33+
}
34+
}
35+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Operator } from '.';
2+
3+
export enum StringOperation {
4+
contains = 'contains',
5+
startsWith = 'startsWith',
6+
endsWith = 'endsWith',
7+
matchesRegex = 'matchesRegex',
8+
}
9+
10+
export class StringOperator extends Operator {
11+
constructor(private operator: StringOperation) {
12+
super();
13+
}
14+
15+
evaluate(left: string, right: string): boolean {
16+
switch (this.operator) {
17+
case StringOperation.contains:
18+
return left.includes(right);
19+
case StringOperation.startsWith:
20+
return left.startsWith(right);
21+
case StringOperation.endsWith:
22+
return left.endsWith(right);
23+
case StringOperation.matchesRegex:
24+
return new RegExp(right).test(left);
25+
default:
26+
throw new Error(`Unsupported string operator: ${this.operator}`);
27+
}
28+
}
29+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export abstract class Operator {
2+
abstract evaluate(left: any, right: any): boolean;
3+
}

0 commit comments

Comments
 (0)