-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Showing
21 changed files
with
1,526 additions
and
35 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
exports.up = function (knex) { | ||
return knex.schema | ||
.alterTable("monitor", function (table) { | ||
table.text("conditions").notNullable().defaultTo("[]"); | ||
}); | ||
}; | ||
|
||
exports.down = function (knex) { | ||
return knex.schema.alterTable("monitor", function (table) { | ||
table.dropColumn("conditions"); | ||
}); | ||
}; |
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 |
---|---|---|
@@ -0,0 +1,71 @@ | ||
const { ConditionExpressionGroup, ConditionExpression, LOGICAL } = require("./expression"); | ||
const { operatorMap } = require("./operators"); | ||
|
||
/** | ||
* @param {ConditionExpression} expression Expression to evaluate | ||
* @param {object} context Context to evaluate against; These are values for variables in the expression | ||
* @returns {boolean} Whether the expression evaluates true or false | ||
* @throws {Error} | ||
*/ | ||
function evaluateExpression(expression, context) { | ||
/** | ||
* @type {import("./operators").ConditionOperator|null} | ||
*/ | ||
const operator = operatorMap.get(expression.operator) || null; | ||
if (operator === null) { | ||
throw new Error("Unexpected expression operator ID '" + expression.operator + "'. Expected one of [" + operatorMap.keys().join(",") + "]"); | ||
} | ||
|
||
if (!Object.prototype.hasOwnProperty.call(context, expression.variable)) { | ||
throw new Error("Variable missing in context: " + expression.variable); | ||
} | ||
|
||
return operator.test(context[expression.variable], expression.value); | ||
} | ||
|
||
/** | ||
* @param {ConditionExpressionGroup} group Group of expressions to evaluate | ||
* @param {object} context Context to evaluate against; These are values for variables in the expression | ||
* @returns {boolean} Whether the group evaluates true or false | ||
* @throws {Error} | ||
*/ | ||
function evaluateExpressionGroup(group, context) { | ||
if (!group.children.length) { | ||
throw new Error("ConditionExpressionGroup must contain at least one child."); | ||
} | ||
|
||
let result = null; | ||
|
||
for (const child of group.children) { | ||
let childResult; | ||
|
||
if (child instanceof ConditionExpression) { | ||
childResult = evaluateExpression(child, context); | ||
} else if (child instanceof ConditionExpressionGroup) { | ||
childResult = evaluateExpressionGroup(child, context); | ||
} else { | ||
throw new Error("Invalid child type in ConditionExpressionGroup. Expected ConditionExpression or ConditionExpressionGroup"); | ||
} | ||
|
||
if (result === null) { | ||
result = childResult; // Initialize result with the first child's result | ||
} else if (child.andOr === LOGICAL.OR) { | ||
result = result || childResult; | ||
} else if (child.andOr === LOGICAL.AND) { | ||
result = result && childResult; | ||
} else { | ||
throw new Error("Invalid logical operator in child of ConditionExpressionGroup. Expected 'and' or 'or'. Got '" + group.andOr + "'"); | ||
} | ||
} | ||
|
||
if (result === null) { | ||
throw new Error("ConditionExpressionGroup did not result in a boolean."); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
module.exports = { | ||
evaluateExpression, | ||
evaluateExpressionGroup, | ||
}; |
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,111 @@ | ||
/** | ||
* @readonly | ||
* @enum {string} | ||
*/ | ||
const LOGICAL = { | ||
AND: "and", | ||
OR: "or", | ||
}; | ||
|
||
/** | ||
* Recursively processes an array of raw condition objects and populates the given parent group with | ||
* corresponding ConditionExpression or ConditionExpressionGroup instances. | ||
* @param {Array} conditions Array of raw condition objects, where each object represents either a group or an expression. | ||
* @param {ConditionExpressionGroup} parentGroup The parent group to which the instantiated ConditionExpression or ConditionExpressionGroup objects will be added. | ||
* @returns {void} | ||
*/ | ||
function processMonitorConditions(conditions, parentGroup) { | ||
conditions.forEach(condition => { | ||
const andOr = condition.andOr === LOGICAL.OR ? LOGICAL.OR : LOGICAL.AND; | ||
|
||
if (condition.type === "group") { | ||
const group = new ConditionExpressionGroup([], andOr); | ||
|
||
// Recursively process the group's children | ||
processMonitorConditions(condition.children, group); | ||
|
||
parentGroup.children.push(group); | ||
} else if (condition.type === "expression") { | ||
const expression = new ConditionExpression(condition.variable, condition.operator, condition.value, andOr); | ||
parentGroup.children.push(expression); | ||
} | ||
}); | ||
} | ||
|
||
class ConditionExpressionGroup { | ||
/** | ||
* @type {ConditionExpressionGroup[]|ConditionExpression[]} Groups and/or expressions to test | ||
*/ | ||
children = []; | ||
|
||
/** | ||
* @type {LOGICAL} Connects group result with previous group/expression results | ||
*/ | ||
andOr; | ||
|
||
/** | ||
* @param {ConditionExpressionGroup[]|ConditionExpression[]} children Groups and/or expressions to test | ||
* @param {LOGICAL} andOr Connects group result with previous group/expression results | ||
*/ | ||
constructor(children = [], andOr = LOGICAL.AND) { | ||
this.children = children; | ||
this.andOr = andOr; | ||
} | ||
|
||
/** | ||
* @param {Monitor} monitor Monitor instance | ||
* @returns {ConditionExpressionGroup|null} A ConditionExpressionGroup with the Monitor's conditions | ||
*/ | ||
static fromMonitor(monitor) { | ||
const conditions = JSON.parse(monitor.conditions); | ||
if (conditions.length === 0) { | ||
return null; | ||
} | ||
|
||
const root = new ConditionExpressionGroup(); | ||
processMonitorConditions(conditions, root); | ||
|
||
return root; | ||
} | ||
} | ||
|
||
class ConditionExpression { | ||
/** | ||
* @type {string} ID of variable | ||
*/ | ||
variable; | ||
|
||
/** | ||
* @type {string} ID of operator | ||
*/ | ||
operator; | ||
|
||
/** | ||
* @type {string} Value to test with the operator | ||
*/ | ||
value; | ||
|
||
/** | ||
* @type {LOGICAL} Connects expression result with previous group/expression results | ||
*/ | ||
andOr; | ||
|
||
/** | ||
* @param {string} variable ID of variable to test against | ||
* @param {string} operator ID of operator to test the variable with | ||
* @param {string} value Value to test with the operator | ||
* @param {LOGICAL} andOr Connects expression result with previous group/expression results | ||
*/ | ||
constructor(variable, operator, value, andOr = LOGICAL.AND) { | ||
this.variable = variable; | ||
this.operator = operator; | ||
this.value = value; | ||
this.andOr = andOr; | ||
} | ||
} | ||
|
||
module.exports = { | ||
LOGICAL, | ||
ConditionExpressionGroup, | ||
ConditionExpression, | ||
}; |
Oops, something went wrong.