Removed jsseectors from rule, now they can be built into strings by using coffeescript #{ jsselector } syntax! Updated timing throught the system. Looks nice :)

This commit is contained in:
Dominic Bosch 2014-04-22 15:24:34 +02:00
parent 3914b29255
commit b1e4fc9af5
15 changed files with 197 additions and 78 deletions

View file

@ -229,6 +229,8 @@ storeRule = ( user, oPayload, callback ) =>
event_interval: oPayload.event_interval
conditions: oPayload.conditions
actions: oPayload.actions
if oPayload.event_start
rule.timestamp = (new Date()).toISOString()
strRule = JSON.stringify rule
# store the rule
db.storeRule rule.id, strRule

View file

@ -181,6 +181,14 @@ pollQueue = () ->
processEvent obj
setTimeout pollQueue, 20 * numExecutingFunctions #FIXME right way to adapt to load?
oOperators =
'<': ( x, y ) -> x < y
'<=': ( x, y ) -> x <= y
'>': ( x, y ) -> x > y
'>=': ( x, y ) -> x >= y
'==': ( x, y ) -> x is y
'instr': ( x, y ) -> x.indexOf( y ) > -1
###
Checks whether all conditions of the rule are met by the event.
@ -188,11 +196,33 @@ Checks whether all conditions of the rule are met by the event.
@param {Object} evt
@param {Object} rule
###
validConditions = ( evt, rule ) ->
validConditions = ( evt, rule, userId, ruleId ) ->
if rule.conditions.length is 0
return true
for prop in rule.conditions
return false if jsonQuery( evt, prop ).nodes().length is 0
for cond in rule.conditions
selectedProperty = jsonQuery( evt, cond.selector ).nodes()
if selectedProperty.length is 0
db.appendLog userId, ruleId, 'Condition', "Node not found in event: #{ cond.selector }"
return false
op = oOperators[ cond.operator ]
if not op
db.appendLog userId, ruleId, 'Condition', "Unknown operator: #{ cond.operator }.
Use one of #{ Object.keys( oOperators ).join ', ' }"
return false
try
if cond.type is 'string'
val = selectedProperty[ 0 ]
else if cond.type is 'value'
val = parseFloat( selectedProperty[ 0 ] ) || 0
if not op val, cond.compare
return false
catch err
db.appendLog userId, ruleId, 'Condition', "Error: Selector '#{ cond.selector }',
Operator #{ cond.operator }, Compare: #{ cond.compare }"
return true
###
@ -213,10 +243,18 @@ processEvent = ( evt ) =>
arrArgs = []
if node.funcArgs[ funcName ]
for oArg in node.funcArgs[ funcName ]
if oArg.jsselector
arrArgs.push jsonQuery( evt.payload, oArg.value ).nodes()[ 0 ]
else
arrArgs.push oArg.value
arrSelectors = oArg.value.match /#\{(.*?)\}/g
argument = oArg.value
for sel in arrSelectors
selector = sel.substring 2, sel.length - 1
data = jsonQuery( evt.payload, selector ).nodes()[ 0 ]
argument = argument.replace sel, data
if oArg.value is sel
argument = data # if the user wants to pass an object, we allow him to do so
# if oArg.jsselector
arrArgs.push argument #jsonQuery( evt.payload, oArg.value ).nodes()[ 0 ]
# else
# arrArgs.push oArg.value
else
@log.warn "EN | Weird! arguments not loaded for function '#{ funcName }'!"
node.module[ funcName ].apply null, arrArgs
@ -232,7 +270,10 @@ processEvent = ( evt ) =>
@log.info 'EN | processing event: ' + evt.event + '(' + evt.eventid + ')'
for userName, oUser of listUserRules
for ruleName, oMyRule of oUser
if evt.event is oMyRule.rule.event and validConditions evt, oMyRule.rule
ruleEvent = oMyRule.rule.event
if oMyRule.rule.timestamp
ruleEvent += '_created:' + oMyRule.rule.timestamp
if evt.event is ruleEvent and validConditions evt, oMyRule.rule, userName, ruleName
@log.info 'EN | EVENT FIRED: ' + evt.event + '(' + evt.eventid + ') for rule ' + ruleName
for action in oMyRule.rule.actions
arr = action.split ' -> '

View file

@ -35,10 +35,12 @@ log.info 'EP | Event Poller starts up'
db logger: log
dynmod
logger: log
db.selectDatabase parseInt( process.argv[ 7 ] ) || 0
encryption
logger: log
keygen: process.argv[ 7 ]
keygen: process.argv[ 8 ]
# Initialize module local variables and
listUserModules = {}
@ -94,34 +96,37 @@ fLoadModule = ( msg ) ->
listUserModules[msg.user] = {}
oUser = listUserModules[msg.user]
ts = new Date()
# We open up a new object for the rule it
oUser[msg.rule.id] =
id: msg.rule.event
timestamp: msg.rule.timestamp
pollfunc: arrName[1]
funcArgs: result.funcArgs
event_interval: msg.rule.event_interval * 60 * 1000
module: result.module
logger: result.logger
timestamp: ts
oUser[msg.rule.id].module.pushEvent = fPushEvent msg.user, msg.rule.id, oUser[msg.rule.id]
start = new Date msg.rule.event_start
nd = new Date()
now = new Date()
if start < nd
# If the engine restarts start could be from last year even
start.setYear nd.getYear()
start.setMonth nd.getMonth()
start.setDate nd.getDate()
nd.setMilliseconds 0
nd.setSeconds 0
nd.setMinutes start.getMinutes()
nd.setHours start.getHours()
# if it's still smaller we add one day
if start < nd
start.setDate nd.getDate() + 1
if nd < now
nd.setDate nd.getDate() + 1
else
nd = start
log.info "EP | New event module '#{ arrName[0] }' loaded for user #{ msg.user },
in rule #{ msg.rule.id }, registered at UTC|#{ ts.toISOString() },
starting at UTC|#{ start.toISOString() } ( which is in #{ ( start - nd ) / 1000 / 60 } minutes )
in rule #{ msg.rule.id }, registered at UTC|#{ msg.rule.timestamp },
starting at UTC|#{ start.toISOString() } ( which is in #{ ( nd - now ) / 1000 / 60 } minutes )
and polling every #{ msg.rule.event_interval } minutes"
setTimeout fCheckAndRun( msg.user, msg.rule.id, ts ), start - nd
setTimeout fCheckAndRun( msg.user, msg.rule.id, msg.rule.timestamp ), nd - now
if msg.event is 'new' or
not listUserModules[msg.user] or
@ -130,11 +135,10 @@ fLoadModule = ( msg ) ->
fPushEvent = ( userId, ruleId, oRule ) ->
( obj ) ->
db.pushEvent
event: oRule.id
eventid: "polled #{ oRule.id } #{ userId }_UTC|#{ ( new Date() ).toISOString() }"
payload: obj
db.pushEvent
event: oRule.id + '_created:' + oRule.timestamp
eventid: "polled #{ oRule.id } #{ userId }_UTC|#{ ( new Date() ).toISOString() }"
payload: obj
fCheckAndRun = ( userId, ruleId, timestamp ) ->
() ->
log.info "EP | Check and run user #{ userId }, rule #{ ruleId }"
@ -148,20 +152,21 @@ fCheckAndRun = ( userId, ruleId, timestamp ) ->
setTimeout fCheckAndRun( userId, ruleId, timestamp ), oRule.event_interval
else
log.info "EP | We found a newer polling interval and discontinue this one which
was created at UTC|#{ timestamp.toISOString() }"
was created at UTC|#{ timestamp }"
# We have to register the poll function in belows anonymous function
# because we're fast iterating through the listUserModules and references will
# eventually not be what they are expected to be
fCallFunction = ( userId, ruleId, oRule ) ->
try
arrArgs = []
if oRule.funcArgs
arrArgs = []
if oRule.funcArgs and oRule.funcArgs[oRule.pollfunc]
for oArg in oRule.funcArgs[oRule.pollfunc]
arrArgs.push oArg.value
oRule.module[oRule.pollfunc].apply null, arrArgs
catch err
log.info "EP | ERROR in module when polled: #{ oRule.id } #{ userId }: #{err.message}"
throw err
oRule.logger err.message
###
This function will loop infinitely every 10 seconds until isRunning is set to false

View file

@ -255,6 +255,7 @@ class IndexedModules
getModule: ( userId, mId, cb ) =>
@log.info "DB | (IdxedMods) #{ @setname }.getModule( #{ userId }, #{ mId } )"
@log.info "hgetall user:#{ userId }:#{ @setname }:#{ mId }"
@db.hgetall "user:#{ userId }:#{ @setname }:#{ mId }", cb
getModuleField: ( userId, mId, field, cb ) =>

View file

@ -187,6 +187,8 @@ init = =>
args.logconf[ 'file-path' ]
# - whether a log file shall be written at all [true|false]
args.logconf[ 'nolog' ]
# - The selected database
args[ 'db-select' ]
# - The keygen phrase, this has to be handled differently in the future!
args[ 'keygen' ]
]

View file

@ -319,6 +319,9 @@ Components Manager
conditions: oPayload.conditions,
actions: oPayload.actions
};
if (oPayload.event_start) {
rule.timestamp = (new Date()).toISOString();
}
strRule = JSON.stringify(rule);
db.storeRule(rule.id, strRule);
db.linkRule(rule.id, user.username);

View file

@ -10,7 +10,7 @@ Engine
*/
(function() {
var db, dynmod, exports, isRunning, jsonQuery, listUserRules, numExecutingFunctions, pollQueue, processEvent, updateActionModules, validConditions;
var db, dynmod, exports, isRunning, jsonQuery, listUserRules, numExecutingFunctions, oOperators, pollQueue, processEvent, updateActionModules, validConditions;
db = require('./persistence');
@ -219,6 +219,27 @@ Engine
}
};
oOperators = {
'<': function(x, y) {
return x < y;
},
'<=': function(x, y) {
return x <= y;
},
'>': function(x, y) {
return x > y;
},
'>=': function(x, y) {
return x >= y;
},
'==': function(x, y) {
return x === y;
},
'instr': function(x, y) {
return x.indexOf(y) > -1;
}
};
/*
Checks whether all conditions of the rule are met by the event.
@ -228,17 +249,37 @@ Engine
@param {Object} rule
*/
validConditions = function(evt, rule) {
var prop, _i, _len, _ref;
validConditions = function(evt, rule, userId, ruleId) {
var cond, err, op, selectedProperty, val, _i, _len, _ref;
if (rule.conditions.length === 0) {
return true;
}
_ref = rule.conditions;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
prop = _ref[_i];
if (jsonQuery(evt, prop).nodes().length === 0) {
cond = _ref[_i];
selectedProperty = jsonQuery(evt, cond.selector).nodes();
if (selectedProperty.length === 0) {
db.appendLog(userId, ruleId, 'Condition', "Node not found in event: " + cond.selector);
return false;
}
op = oOperators[cond.operator];
if (!op) {
db.appendLog(userId, ruleId, 'Condition', "Unknown operator: " + cond.operator + ". Use one of " + (Object.keys(oOperators).join(', ')));
return false;
}
try {
if (cond.type === 'string') {
val = selectedProperty[0];
} else if (cond.type === 'value') {
val = parseFloat(selectedProperty[0]) || 0;
}
if (!op(val, cond.compare)) {
return false;
}
} catch (_error) {
err = _error;
db.appendLog(userId, ruleId, 'Condition', "Error: Selector '" + cond.selector + "', Operator " + cond.operator + ", Compare: " + cond.compare);
}
}
return true;
};
@ -253,9 +294,9 @@ Engine
processEvent = (function(_this) {
return function(evt) {
var action, arr, fSearchAndInvokeAction, oMyRule, oUser, ruleName, userName, _results;
var action, arr, fSearchAndInvokeAction, oMyRule, oUser, ruleEvent, ruleName, userName, _results;
fSearchAndInvokeAction = function(node, arrPath, funcName, evt, depth) {
var arrArgs, err, oArg, _i, _len, _ref;
var argument, arrArgs, arrSelectors, data, err, oArg, sel, selector, _i, _j, _len, _len1, _ref;
if (!node) {
_this.log.error("EN | Didn't find property in user rule list: " + arrPath.join(', ') + " at depth " + depth);
return;
@ -269,11 +310,18 @@ Engine
_ref = node.funcArgs[funcName];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
oArg = _ref[_i];
if (oArg.jsselector) {
arrArgs.push(jsonQuery(evt.payload, oArg.value).nodes()[0]);
} else {
arrArgs.push(oArg.value);
arrSelectors = oArg.value.match(/#\{(.*?)\}/g);
argument = oArg.value;
for (_j = 0, _len1 = arrSelectors.length; _j < _len1; _j++) {
sel = arrSelectors[_j];
selector = sel.substring(2, sel.length - 1);
data = jsonQuery(evt.payload, selector).nodes()[0];
argument = argument.replace(sel, data);
if (oArg.value === sel) {
argument = data;
}
}
arrArgs.push(argument);
}
} else {
_this.log.warn("EN | Weird! arguments not loaded for function '" + funcName + "'!");
@ -301,7 +349,11 @@ Engine
_results1 = [];
for (ruleName in oUser) {
oMyRule = oUser[ruleName];
if (evt.event === oMyRule.rule.event && validConditions(evt, oMyRule.rule)) {
ruleEvent = oMyRule.rule.event;
if (oMyRule.rule.timestamp) {
ruleEvent += '_created:' + oMyRule.rule.timestamp;
}
if (evt.event === ruleEvent && validConditions(evt, oMyRule.rule, userName, ruleName)) {
this.log.info('EN | EVENT FIRED: ' + evt.event + '(' + evt.eventid + ') for rule ' + ruleName);
_results1.push((function() {
var _i, _len, _ref, _results2;

View file

@ -47,9 +47,11 @@ Dynamic Modules
logger: log
});
db.selectDatabase(parseInt(process.argv[7]) || 0);
encryption({
logger: log,
keygen: process.argv[7]
keygen: process.argv[8]
});
listUserModules = {};
@ -84,7 +86,7 @@ Dynamic Modules
return log.warn("EP | Strange... no module retrieved: " + arrName[0]);
} else {
return dynmod.compileString(obj.data, msg.user, msg.rule.id, arrName[0], obj.lang, db.eventPollers, function(result) {
var nd, oUser, start, ts;
var nd, now, oUser, start;
if (!result.answ === 200) {
log.error("EP | Compilation of code failed! " + msg.user + ", " + msg.rule.id + ", " + arrName[0]);
}
@ -92,29 +94,32 @@ Dynamic Modules
listUserModules[msg.user] = {};
}
oUser = listUserModules[msg.user];
ts = new Date();
oUser[msg.rule.id] = {
id: msg.rule.event,
timestamp: msg.rule.timestamp,
pollfunc: arrName[1],
funcArgs: result.funcArgs,
event_interval: msg.rule.event_interval * 60 * 1000,
module: result.module,
logger: result.logger,
timestamp: ts
logger: result.logger
};
oUser[msg.rule.id].module.pushEvent = fPushEvent(msg.user, msg.rule.id, oUser[msg.rule.id]);
start = new Date(msg.rule.event_start);
nd = new Date();
now = new Date();
if (start < nd) {
start.setYear(nd.getYear());
start.setMonth(nd.getMonth());
start.setDate(nd.getDate());
if (start < nd) {
start.setDate(nd.getDate() + 1);
nd.setMilliseconds(0);
nd.setSeconds(0);
nd.setMinutes(start.getMinutes());
nd.setHours(start.getHours());
if (nd < now) {
nd.setDate(nd.getDate() + 1);
}
} else {
nd = start;
}
log.info("EP | New event module '" + arrName[0] + "' loaded for user " + msg.user + ", in rule " + msg.rule.id + ", registered at UTC|" + (ts.toISOString()) + ", starting at UTC|" + (start.toISOString()) + " ( which is in " + ((start - nd) / 1000 / 60) + " minutes ) and polling every " + msg.rule.event_interval + " minutes");
return setTimeout(fCheckAndRun(msg.user, msg.rule.id, ts), start - nd);
log.info("EP | New event module '" + arrName[0] + "' loaded for user " + msg.user + ", in rule " + msg.rule.id + ", registered at UTC|" + msg.rule.timestamp + ", starting at UTC|" + (start.toISOString()) + " ( which is in " + ((nd - now) / 1000 / 60) + " minutes ) and polling every " + msg.rule.event_interval + " minutes");
return setTimeout(fCheckAndRun(msg.user, msg.rule.id, msg.rule.timestamp), nd - now);
});
}
});
@ -127,7 +132,7 @@ Dynamic Modules
fPushEvent = function(userId, ruleId, oRule) {
return function(obj) {
return db.pushEvent({
event: oRule.id,
event: oRule.id + '_created:' + oRule.timestamp,
eventid: "polled " + oRule.id + " " + userId + "_UTC|" + ((new Date()).toISOString()),
payload: obj
});
@ -144,7 +149,7 @@ Dynamic Modules
fCallFunction(userId, ruleId, oRule);
return setTimeout(fCheckAndRun(userId, ruleId, timestamp), oRule.event_interval);
} else {
return log.info("EP | We found a newer polling interval and discontinue this one which was created at UTC|" + (timestamp.toISOString()));
return log.info("EP | We found a newer polling interval and discontinue this one which was created at UTC|" + timestamp);
}
}
};
@ -154,7 +159,7 @@ Dynamic Modules
var arrArgs, err, oArg, _i, _len, _ref;
try {
arrArgs = [];
if (oRule.funcArgs) {
if (oRule.funcArgs && oRule.funcArgs[oRule.pollfunc]) {
_ref = oRule.funcArgs[oRule.pollfunc];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
oArg = _ref[_i];
@ -165,6 +170,7 @@ Dynamic Modules
} catch (_error) {
err = _error;
log.info("EP | ERROR in module when polled: " + oRule.id + " " + userId + ": " + err.message);
throw err;
return oRule.logger(err.message);
}
};

View file

@ -301,6 +301,7 @@ Persistence
IndexedModules.prototype.getModule = function(userId, mId, cb) {
this.log.info("DB | (IdxedMods) " + this.setname + ".getModule( " + userId + ", " + mId + " )");
this.log.info("hgetall user:" + userId + ":" + this.setname + ":" + mId);
return this.db.hgetall("user:" + userId + ":" + this.setname + ":" + mId, cb);
};

View file

@ -167,7 +167,7 @@ WebAPI-ECA Engine
_this.log.info('RS | Initialzing engine');
engine(args);
_this.log.info('RS | Forking a child process for the event poller');
cliArgs = [args.logconf['mode'], args.logconf['io-level'], args.logconf['file-level'], args.logconf['file-path'], args.logconf['nolog'], args['keygen']];
cliArgs = [args.logconf['mode'], args.logconf['io-level'], args.logconf['file-level'], args.logconf['file-path'], args.logconf['nolog'], args['db-select'], args['keygen']];
poller = cp.fork(path.resolve(__dirname, nameEP), cliArgs);
_this.log.info('RS | Initialzing module manager');
cm(args);

View file

@ -81,13 +81,21 @@
"id": "ruleReal",
"event": "test_1",
"event_interval": 1,
"conditions": [".more:val(\"really nested\")"],
"conditions":
[
{
"selector": ".more",
"type": "string",
"operator": "instr",
"compare": "really nested"
}
],
"actions": ["aiOne -> printToLog"],
"action_functions": {
"aiOne -> printToLog": [
{
"argument": "evt",
"value": "*",
"value": "#{*}",
"jsselector": true
}
]

View file

@ -175,7 +175,7 @@ fOnLoad = () ->
tr = $( "tr", par ).filter () ->
$( '.funcarg', this ).text() is "#{ oFunc.argument }"
$( "input[type=text]", tr ).val oFunc.value
$( "input[type=checkbox]", tr ).prop 'checked', oFunc.jsselector
# $( "input[type=checkbox]", tr ).prop 'checked', oFunc.jsselector
# ACTIONS
obj =
@ -265,9 +265,9 @@ fOnLoad = () ->
td = $( '<td>' ).appendTo tr
td.append $( '<input>' ).attr 'type', 'text'
tr.append td
td = $( '<td>' ).appendTo tr
td.append $( '<input>' ).attr( 'type', 'checkbox' )
.attr 'title', 'js-select expression to be resolved on event?'
# td = $( '<td>' ).appendTo tr
# td.append $( '<input>' ).attr( 'type', 'checkbox' )
# .attr 'title', 'js-select expression to be resolved on event?'
.fail fFailedRequest 'Error fetching action invoker function params'
fFillActionFunction = ( name ) ->
@ -307,7 +307,7 @@ fOnLoad = () ->
tr = $( "tr", par ).filter () ->
$( '.funcarg', this ).text() is "#{ oFunc.argument }"
$( "input[type=text]", tr ).val oFunc.value
$( "input[type=checkbox]", tr ).prop 'checked', oFunc.jsselector
# $( "input[type=checkbox]", tr ).prop 'checked', oFunc.jsselector
$( '#select_actions' ).on 'change', () ->
opt = $ 'option:selected', this
@ -413,7 +413,7 @@ fOnLoad = () ->
actFuncs[ actionName ].push
argument: $( 'div.funcarg', this ).text()
value: $( 'input[type=text]', this ).val()
jsselector: $( 'input[type=checkbox]', this ).is( ':checked' )
# jsselector: $( 'input[type=checkbox]', this ).is( ':checked' )
try
conds = JSON.parse editor.getValue()
@ -429,7 +429,6 @@ fOnLoad = () ->
if not txtStart
start.setHours 12
start.setMinutes 0
console.log 'setting to 12:00: ' + start.toString()
else
arrInp = txtStart.split ':'
# There's only one string entered: hour
@ -443,7 +442,7 @@ fOnLoad = () ->
start.setMinutes m
intHour = parseInt( txtHr ) || 12
h = Math.max 0, Math.min intHour, 12
h = Math.max 0, Math.min intHour, 24
start.setHours h
start.setSeconds 0

View file

@ -241,8 +241,7 @@
tr = $("tr", par).filter(function() {
return $('.funcarg', this).text() === ("" + oFunc.argument);
});
$("input[type=text]", tr).val(oFunc.value);
_results1.push($("input[type=checkbox]", tr).prop('checked', oFunc.jsselector));
_results1.push($("input[type=text]", tr).val(oFunc.value));
}
return _results1;
})());
@ -370,9 +369,7 @@
tr.append(td);
td = $('<td>').appendTo(tr);
td.append($('<input>').attr('type', 'text'));
tr.append(td);
td = $('<td>').appendTo(tr);
_results.push(td.append($('<input>').attr('type', 'checkbox')).attr('title', 'js-select expression to be resolved on event?'));
_results.push(tr.append(td));
}
return _results;
}
@ -435,8 +432,7 @@
tr = $("tr", par).filter(function() {
return $('.funcarg', this).text() === ("" + oFunc.argument);
});
$("input[type=text]", tr).val(oFunc.value);
_results1.push($("input[type=checkbox]", tr).prop('checked', oFunc.jsselector));
_results1.push($("input[type=text]", tr).val(oFunc.value));
}
return _results1;
})());
@ -554,8 +550,7 @@
return $('.funcMappings tr', par).each(function() {
return actFuncs[actionName].push({
argument: $('div.funcarg', this).text(),
value: $('input[type=text]', this).val(),
jsselector: $('input[type=checkbox]', this).is(':checked')
value: $('input[type=text]', this).val()
});
});
});
@ -573,7 +568,6 @@
if (!txtStart) {
start.setHours(12);
start.setMinutes(0);
console.log('setting to 12:00: ' + start.toString());
} else {
arrInp = txtStart.split(':');
if (arrInp.length === 1) {
@ -587,7 +581,7 @@
}
}
intHour = parseInt(txtHr) || 12;
h = Math.max(0, Math.min(intHour, 12));
h = Math.max(0, Math.min(intHour, 24));
start.setHours(h);
start.setSeconds(0);
start.setMilliseconds(0);

View file

@ -12,7 +12,12 @@ Refer to <a target="_blank" href="https://github.com/harthur/js-select#selectors
<tr><td>
<div id="editor_conditions">
[
".nested_property:val(\"has this value\")"
{
"selector": ".nested_property",
"type": "string",
"operator": "<=",
"compare": "has this value"
}
]
</div>
</td></tr>

View file

@ -108,7 +108,7 @@ input[type=password]:focus {
float: left;
border: 1px solid lightgray;
margin: auto;
height: 100px;
height: 150px;
width: 400px;
}