-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
52d7d75
commit 9dd5786
Showing
61 changed files
with
9,930 additions
and
7,030 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ import { StartBlock } from "../startBlock"; | |
import { EndBlock } from "../endBlock"; | ||
import { InputBlock } from "../inputBlock"; | ||
import { OutputBlock } from "../outputBlock"; | ||
import { LogicBlock } from ".."; | ||
|
||
type BlockConstructor = new (config: IBlockConfig, logger: ILogger) => IBlock; | ||
Check warning on line 10 in packages/blocks/src/blockFactory/index.ts GitHub Actions / build
|
||
|
||
|
@@ -13,6 +14,7 @@ const blockRegistry: Record<string, BlockConstructor> = { | |
"blocks/[email protected]": EndBlock, | ||
"blocks/[email protected]": InputBlock, | ||
"blocks/[email protected]": OutputBlock, | ||
"blocks/[email protected]": LogicBlock, | ||
}; | ||
|
||
export function createBlock(config: IBlockConfig, logger: ILogger): IBlock { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { ICondition } from "@data-river/shared/interfaces/ICondition"; | ||
import { ILogger } from "@data-river/shared/interfaces/ILogger"; | ||
import _ from "lodash"; | ||
import { ValueResolver } from "@/utils/valueResolver"; | ||
import { DateHandler } from "./dateHandler"; | ||
import { Comparator } from "@/utils/comparator"; | ||
|
||
export class ConditionEvaluator { | ||
constructor(private logger: ILogger) {} | ||
|
||
evaluateCondition( | ||
condition: ICondition, | ||
inputs: Record<string, unknown>, | ||
): boolean { | ||
const leftValue = ValueResolver.resolveValue(condition.left, inputs); | ||
const rightValue = ValueResolver.resolveValue(condition.right, inputs); | ||
|
||
const leftDate = DateHandler.parseDateIfValid(leftValue); | ||
const rightDate = DateHandler.parseDateIfValid(rightValue); | ||
|
||
if (leftDate && rightDate) { | ||
return DateHandler.compareDates(leftDate, rightDate, condition.operator); | ||
} | ||
|
||
switch (condition.operator) { | ||
case "==": | ||
return Comparator.looseEquals(leftValue, rightValue); | ||
case "===": | ||
return Comparator.strictEquals(leftValue, rightValue); | ||
case "!=": | ||
return !Comparator.looseEquals(leftValue, rightValue); | ||
case "!==": | ||
return !Comparator.strictEquals(leftValue, rightValue); | ||
case ">": | ||
return Comparator.compare(leftValue, rightValue) > 0; | ||
case ">=": | ||
return Comparator.compare(leftValue, rightValue) >= 0; | ||
case "<": | ||
return Comparator.compare(leftValue, rightValue) < 0; | ||
case "<=": | ||
return Comparator.compare(leftValue, rightValue) <= 0; | ||
case "contains": | ||
return ( | ||
_.isString(leftValue) && | ||
String(leftValue).includes(String(rightValue)) | ||
); | ||
case "not_contains": | ||
return ( | ||
_.isString(leftValue) && | ||
!String(leftValue).includes(String(rightValue)) | ||
); | ||
case "starts_with": | ||
return ( | ||
_.isString(leftValue) && | ||
String(leftValue).startsWith(String(rightValue)) | ||
); | ||
case "ends_with": | ||
return ( | ||
_.isString(leftValue) && | ||
String(leftValue).endsWith(String(rightValue)) | ||
); | ||
case "is_empty": | ||
return Comparator.isEmpty(leftValue); | ||
case "is_not_empty": | ||
return !Comparator.isEmpty(leftValue); | ||
default: | ||
this.logger.warn(`Unknown operator: ${condition.operator}`); | ||
return false; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import _ from "lodash"; | ||
|
||
export class DateHandler { | ||
static parseDateIfValid(value: unknown): Date | null { | ||
if (_.isDate(value)) { | ||
return value; | ||
} | ||
if (_.isString(value) || _.isNumber(value)) { | ||
const timestamp = _.toNumber(new Date(value)); | ||
return isNaN(timestamp) ? null : new Date(timestamp); | ||
} | ||
return null; | ||
} | ||
|
||
static compareDates(a: Date, b: Date, operator: string): boolean { | ||
const aTime = a.getTime(); | ||
const bTime = b.getTime(); | ||
switch (operator) { | ||
case ">": | ||
return aTime > bTime; | ||
case ">=": | ||
return aTime >= bTime; | ||
case "<": | ||
return aTime < bTime; | ||
case "<=": | ||
return aTime <= bTime; | ||
case "==": | ||
return aTime === bTime; | ||
case "!=": | ||
return aTime !== bTime; | ||
default: | ||
return false; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,63 @@ | ||
import { IBlockConfig } from "@data-river/shared/interfaces"; | ||
import { ILogger } from "@data-river/shared/interfaces/ILogger"; | ||
import { Block } from "../block"; | ||
import { Block } from "@/block"; | ||
import { CustomEvaluatorStrategy } from "@/utils/customEvaluatorStrategy"; | ||
import { CustomEvaluatorFunction } from "@data-river/shared/types"; | ||
import { ICondition } from "@data-river/shared/interfaces"; | ||
import { ConditionEvaluator } from "./conditionEvaluator"; | ||
|
||
export interface LogicBlockConfig { | ||
conditions: ICondition[]; | ||
logicOperator: "AND" | "OR"; | ||
customEvaluatorCode?: string; | ||
} | ||
|
||
export class LogicBlock extends Block { | ||
condition: string; | ||
conditions: ICondition[]; | ||
logicOperator: "AND" | "OR"; | ||
customEvaluator?: CustomEvaluatorFunction; | ||
private conditionEvaluator: ConditionEvaluator; | ||
|
||
constructor(config: IBlockConfig & { condition?: string }, logger: ILogger) { | ||
constructor(config: IBlockConfig, logger: ILogger) { | ||
super(config, logger); | ||
this.condition = config.condition || ""; | ||
const magicConfig = config.config as unknown as LogicBlockConfig; | ||
this.conditions = magicConfig.conditions || []; | ||
this.logicOperator = magicConfig.logicOperator || "AND"; | ||
this.conditionEvaluator = new ConditionEvaluator(logger); | ||
if (magicConfig.customEvaluatorCode) { | ||
this.setCustomEvaluator(magicConfig.customEvaluatorCode); | ||
} | ||
} | ||
|
||
setCustomEvaluator(code: string, timeout?: number) { | ||
this.customEvaluator = CustomEvaluatorStrategy.createEvaluator( | ||
code, | ||
timeout, | ||
); | ||
} | ||
|
||
async execute( | ||
inputs: Record<string, unknown>, | ||
): Promise<Record<string, unknown>> { | ||
const conditionResult = evaluateCondition(this.condition, inputs); | ||
this.logger.debug(`Condition result: ${conditionResult}`); | ||
return { conditionResult }; | ||
const conditionResults = this.conditions.map((condition) => | ||
this.evaluateCondition(condition, inputs), | ||
); | ||
|
||
const result = | ||
this.logicOperator === "AND" | ||
? conditionResults.every((result) => result) | ||
: conditionResults.some((result) => result); | ||
|
||
this.logger.debug(`Condition result: ${result}`); | ||
return { result }; | ||
} | ||
} | ||
|
||
function evaluateCondition( | ||
condition: string, | ||
inputs: Record<string, unknown>, | ||
): boolean { | ||
// Implement condition evaluation logic | ||
// For POC, randomly return true or false | ||
return Math.random() >= 0.5; | ||
private evaluateCondition( | ||
condition: ICondition, | ||
inputs: Record<string, unknown>, | ||
): boolean { | ||
return this.customEvaluator | ||
? this.customEvaluator(condition, inputs) | ||
: this.conditionEvaluator.evaluateCondition(condition, inputs); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import _ from "lodash"; | ||
|
||
export class Comparator { | ||
static looseEquals(a: unknown, b: unknown): boolean { | ||
return _.eq(a, b); | ||
} | ||
|
||
static strictEquals(a: unknown, b: unknown): boolean { | ||
return _.isEqual(a, b); | ||
} | ||
|
||
static compare(a: unknown, b: unknown): number { | ||
if (_.isNumber(a) && _.isNumber(b)) { | ||
return a - b; | ||
} | ||
if (_.isBoolean(a) && _.isBoolean(b)) { | ||
return a === b ? 0 : a ? 1 : -1; | ||
} | ||
if (_.isString(a) && _.isString(b)) { | ||
return a === b ? 0 : a > b ? 1 : -1; | ||
} | ||
return String(a) > String(b) ? 1 : -1; | ||
} | ||
|
||
static isEmpty(value: unknown): boolean { | ||
return _.isEmpty(value); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { ICondition } from "@data-river/shared/interfaces"; | ||
import { ILogger } from "@data-river/shared/interfaces/ILogger"; | ||
import _ from "lodash"; | ||
import { ValueResolver } from "./valueResolver"; | ||
import { DateHandler } from "../logicBlock/dateHandler"; | ||
import { Comparator } from "./comparator"; | ||
|
||
export class ConditionEvaluator { | ||
constructor(private logger: ILogger) {} | ||
|
||
evaluateCondition( | ||
condition: ICondition, | ||
inputs: Record<string, unknown>, | ||
): boolean { | ||
const leftValue = ValueResolver.resolveValue(condition.left, inputs); | ||
const rightValue = ValueResolver.resolveValue(condition.right, inputs); | ||
|
||
const leftDate = DateHandler.parseDateIfValid(leftValue); | ||
const rightDate = DateHandler.parseDateIfValid(rightValue); | ||
|
||
if (leftDate && rightDate) { | ||
return DateHandler.compareDates(leftDate, rightDate, condition.operator); | ||
} | ||
|
||
switch (condition.operator) { | ||
case "==": | ||
return Comparator.looseEquals(leftValue, rightValue); | ||
case "===": | ||
return Comparator.strictEquals(leftValue, rightValue); | ||
case "!=": | ||
return !Comparator.looseEquals(leftValue, rightValue); | ||
case "!==": | ||
return !Comparator.strictEquals(leftValue, rightValue); | ||
case ">": | ||
return Comparator.compare(leftValue, rightValue) > 0; | ||
case ">=": | ||
return Comparator.compare(leftValue, rightValue) >= 0; | ||
case "<": | ||
return Comparator.compare(leftValue, rightValue) < 0; | ||
case "<=": | ||
return Comparator.compare(leftValue, rightValue) <= 0; | ||
case "contains": | ||
return ( | ||
_.isString(leftValue) && | ||
String(leftValue).includes(String(rightValue)) | ||
); | ||
case "not_contains": | ||
return ( | ||
_.isString(leftValue) && | ||
!String(leftValue).includes(String(rightValue)) | ||
); | ||
case "starts_with": | ||
return ( | ||
_.isString(leftValue) && | ||
String(leftValue).startsWith(String(rightValue)) | ||
); | ||
case "ends_with": | ||
return ( | ||
_.isString(leftValue) && | ||
String(leftValue).endsWith(String(rightValue)) | ||
); | ||
case "is_empty": | ||
return Comparator.isEmpty(leftValue); | ||
case "is_not_empty": | ||
return !Comparator.isEmpty(leftValue); | ||
default: | ||
this.logger.warn(`Unknown operator: ${condition.operator}`); | ||
return false; | ||
} | ||
} | ||
} |
Oops, something went wrong.