Debugging tools and use case implementation

This commit is contained in:
Dominic Bosch 2014-04-06 02:22:39 +02:00
parent 8346b8bd7d
commit 003fe4111d
20 changed files with 514 additions and 207 deletions

View file

@ -43,7 +43,7 @@ Apply your settings, for example:
"io-level": "info", # the log-level for the std I/O stream
"file-level": "info", # the log-level for the log file
"file-path": "server.log" # log file path, relative to cwd
( add "nolog": "true" if no log shall be generated at all. Mainly used for unit tests )
"nolog": "false" # false if no log shall be generated at all. Mainly used for unit tests
}
}
@ -54,9 +54,13 @@ Start the server:
*Congratulations, your own WebAPI based ECA engine server is now up and running!*
Optional command line tools:
----------------------------
Optional command line scripts
-----------------------------
Run test suite:
run_tests.sh
Create the doc *(to be accessed via the webserver, e.g.: localhost:8125/doc/)*:
> **WARNING:**
@ -65,8 +69,4 @@ Create the doc *(to be accessed via the webserver, e.g.: localhost:8125/doc/)*:
> *`!!! 5` needs to be relaced with `doctype html`*
run_doc.sh
Run test suite:
run_tests.sh
run_doc.sh

View file

@ -177,7 +177,7 @@ forgeModule = ( user, oPayload, dbMod, callback ) =>
if answ.code is 200
funcs = []
funcs.push name for name, id of cm.module
@log.info "CM | Storing new module with functions #{ funcs.join() }"
@log.info "CM | Storing new module with functions #{ funcs.join( ', ' ) }"
answ.message =
" Module #{ oPayload.id } successfully stored! Found following function(s): #{ funcs }"
oPayload.functions = JSON.stringify funcs

View file

@ -49,20 +49,15 @@ exports.getPublicKey = () =>
issueApiCall = ( logger ) ->
( method, url, credentials, cb ) ->
try
if method is 'get'
func = needle.get
else
func = needle.post
func url, credentials, ( err, resp, body ) =>
( method, url, data, options, cb ) ->
try
needle.request method, url, data, options, ( err, resp, body ) =>
try
cb err, resp, body
catch err
logger 'Error during apicall! ' + err.message
logger 'Error during needle request! ' + err.message
catch err
logger 'Error before apicall! ' + err.message
logger 'Error before needle request! ' + err.message
logFunction = ( uId, rId, mId ) ->
( msg ) ->
@ -108,10 +103,9 @@ exports.compileString = ( src, userId, ruleId, modId, lang, dbMod, cb ) =>
sandbox =
id: userId + '.' + modId + '.vm'
params: params
apicall: issueApiCall logFunc
# needle: needle
needlereq: issueApiCall logFunc
log: logFunc
# debug: console.log
debug: console.log
exports: {}
#TODO child_process to run module!
@ -124,7 +118,10 @@ exports.compileString = ( src, userId, ruleId, modId, lang, dbMod, cb ) =>
# Start Node with the flags nouse_idle_notification and expose_gc, and then when you want to run the GC, just call global.gc().
catch err
answ.code = 400
answ.message = 'Loading Module failed: ' + err.message
msg = err.message
if not msg
msg = 'Try to run the script locally to track the error! Sadly we cannot provide the line number'
answ.message = 'Loading Module failed: ' + msg
cb
answ: answ
module: sandbox.exports

View file

@ -0,0 +1,6 @@
#
# Pushes an event into the system each time the function is polled
#
exports.push = ( pushEvent ) ->
pushEvent
content: "This is an event that will be sent again and again every ten seconds"

View file

@ -0,0 +1,142 @@
###
ProBinder ACTION INVOKER
------------------------
Global variables
This module requires user-specific parameters:
- username
- password
- companyId: company where to post the binder entries
- contextId: context where to post the binder entries
###
urlService = 'https://probinder.com/service/'
credentials =
username: params.username
password: params.password
#
# The standard callback can be used if callback is not provided, e.g. if
# the function is called from outside
#
standardCallback = ( funcName ) ->
( err, resp, body ) ->
if err
log "ERROR: During function '#{ funcName }'"
else
if resp.statusCode is 200
log "Function '#{ funcName }' ran through without error"
else
log "ERROR: During function '#{ funcName }': #{ body.error.message }"
###
Call the ProBinder service with the given parameters.
@param {Object} args the required function arguments object
@param {Object} [args.data] the data to be posted
@param {String} args.service the required service identifier to be appended to the url
@param {String} args.method the required method identifier to be appended to the url
@param {function} [args.callback] the function to receive the request answer
###
exports.call = ( args ) ->
if not args.service or not args.method
log 'ERROR in call function: Missing arguments!'
else
if not args.callback
args.callback = standardCallback 'call'
url = urlService + args.service + '/' + args.method
needlereq 'post', url, args.data, credentials, args.callback
###
Calls the user's unread content service.
@param {Object} [args] the optional object containing the callback function
@param {function} [args.callback] refer to call function
###
exports.getUnreadContents = ( args ) ->
if not args.callback
args.callback = standardCallback 'getUnreadContents'
exports.call
service: '36'
method: 'unreadcontent'
callback: args.callback
###
Calls the content get service with the content id and the service id provided.
@param {Object} args the object containing the service id and the content id,
success and error callback methods
@param {String} args.serviceid the service id that is able to process this content
@param {String} args.contentid the content id
@param {function} [args.callback] receives the needle answer from the "call" function
###
exports.getContent = ( args ) ->
if not args.callback
args.callback = standardCallback 'getContent'
exports.call
service: '2'
method: 'get'
data:
id: args.contentid
service: args.serviceid
callback: args.callback
###
Does everything to post something in a binder
@param {Object} args the object containing the content
@param {String} args.content the content to be posted
###
exports.newContent = ( args ) ->
if not args.callback
args.callback = standardCallback 'newContent'
exports.call
service: '27'
method: 'save'
data:
companyId: params.companyId
context: params.contextId
text: args.content
callback: args.callback
###
Does everything to post a file info in a binder tabe
@param {Object} args the object containing the content
@param {String} args.service the content service
@param {String} args.id the content id
###
exports.makeFileEntry = ( args ) ->
if not args.callback
args.callback = standardCallback 'makeFileEntry'
exports.getContent
serviceid: args.service
contentid: args.id
callback: ( err, resp, body ) ->
exports.call
service: '27'
method: 'save'
data:
companyId: params.companyId
context: params.contextId
text: "New file (#{ body.title }) in tab \"#{ body.context[0].name }\",
find it <a href=\"https://probinder.com/file/#{ body.fileIds[0] }\">here</a>!'"
callback: args.callback
###
Sets the content as read.
@param {Object} args the object containing the content
@param {String} args.content the content to be posted
###
exports.setRead = ( args ) ->
if not args.callback
args.callback = standardCallback 'setRead'
exports.call
service: '2'
method: 'setread'
data:
id: args.id
callback: args.callback

View file

@ -0,0 +1,26 @@
#
# EmailYak EVENT POLLER
# ---------------------
#
# Requires user params:
# - apikey: The user's EmailYak API key
#
url = 'https://api.emailyak.com/v1/' + params.apikey + '/json/get/new/email/'
exports.newMail = ( pushEvent ) ->
# needlereq allows the user to make calls to API's
# Refer to https://github.com/tomas/needle for more information
#
# Syntax: needle.request method, url, data, [options], callback
#
needlereq 'get', url, null, null, ( err, resp, body ) ->
if err
log 'Error in EmailYak EM newMail: ' + err.message
else
if resp.statusCode is 200
mails = JSON.parse( body ).Emails
pushEvent mail for mail in mails

37
examples/runscript.coffee Normal file
View file

@ -0,0 +1,37 @@
###
runscript.js
------------
A script that helps to track errors happening durin coffee
compilation and running of module code
###
if not process.argv[ 2 ]
console.log 'Please provide a path to a coffee file'
process.exit()
fs = require 'fs'
vm = require 'vm'
cs = require 'coffee-script'
issueApiCall = ( method, url, data, options, cb ) ->
cb new Error 'not possible'
params = {}
data = fs.readFileSync process.argv[ 2 ], 'utf8'
src = cs.compile data
sandbox =
id: 'test.vm'
params: params
needlereq: issueApiCall
log: console.log
debug: console.log
exports: {}
vm.runInNewContext src, sandbox, sandbox.id
console.log "If no error happened until here it seems the script
compiled and ran correctly! Congrats!"

39
examples/runscript.js Normal file
View file

@ -0,0 +1,39 @@
// Generated by CoffeeScript 1.7.1
(function() {
var cs, data, fs, issueApiCall, params, sandbox, src, vm;
if (!process.argv[2]) {
console.log('Please provide a path to a coffee file');
process.exit();
}
fs = require('fs');
vm = require('vm');
cs = require('coffee-script');
issueApiCall = function(method, url, data, options, cb) {
return cb(new Error('not possible'));
};
params = {};
data = fs.readFileSync(process.argv[2], 'utf8');
src = cs.compile(data);
sandbox = {
id: 'test.vm',
params: params,
needlereq: issueApiCall,
log: console.log,
debug: console.log,
exports: {}
};
vm.runInNewContext(src, sandbox, sandbox.id);
console.log('If no error happened until here it seems the script compiled and ran correctly! Congrats!');
}).call(this);

View file

@ -246,7 +246,7 @@ Components Manager
id = _ref[name];
funcs.push(name);
}
_this.log.info("CM | Storing new module with functions " + (funcs.join()));
_this.log.info("CM | Storing new module with functions " + (funcs.join(', ')));
answ.message = " Module " + oPayload.id + " successfully stored! Found following function(s): " + funcs;
oPayload.functions = JSON.stringify(funcs);
dbMod.storeModule(user.username, oPayload);

View file

@ -53,27 +53,22 @@ Dynamic Modules
})(this);
issueApiCall = function(logger) {
return function(method, url, credentials, cb) {
var err, func;
return function(method, url, data, options, cb) {
var err;
try {
if (method === 'get') {
func = needle.get;
} else {
func = needle.post;
}
return func(url, credentials, (function(_this) {
return needle.request(method, url, data, options, (function(_this) {
return function(err, resp, body) {
try {
return cb(err, resp, body);
} catch (_error) {
err = _error;
return logger('Error during apicall! ' + err.message);
return logger('Error during needle request! ' + err.message);
}
};
})(this));
} catch (_error) {
err = _error;
return logger('Error before apicall! ' + err.message);
return logger('Error before needle request! ' + err.message);
}
};
};
@ -114,7 +109,7 @@ Dynamic Modules
}
}
fTryToLoad = function(params) {
var logFunc, oDecrypted, sandbox;
var logFunc, msg, oDecrypted, sandbox;
if (params) {
try {
oDecrypted = cryptico.decrypt(params, _this.oPrivateRSAkey);
@ -132,8 +127,9 @@ Dynamic Modules
sandbox = {
id: userId + '.' + modId + '.vm',
params: params,
apicall: issueApiCall(logFunc),
needlereq: issueApiCall(logFunc),
log: logFunc,
debug: console.log,
exports: {}
};
try {
@ -141,7 +137,11 @@ Dynamic Modules
} catch (_error) {
err = _error;
answ.code = 400;
answ.message = 'Loading Module failed: ' + err.message;
msg = err.message;
if (!msg) {
msg = 'Try to run the script locally to track the error! Sadly we cannot provide the line number';
}
answ.message = 'Loading Module failed: ' + msg;
}
return cb({
answ: answ,

View file

@ -1,55 +0,0 @@
'use strict';
/*
* EmailYak EVENT MODULE
*/
var credentials = null;
function loadCredentials(cred) {
if(!cred || !cred.key) {
console.error('ERROR in EmailYak EM: credentials file corrupt');
} else {
credentials = cred;
console.log('Successfully loaded EmailYak EM credentials');
}
}
//FIXME every second mail gets lost?
// 1) for http.request options, set Connection:keep-alive
// 2) set Agent.maxSockets = 1024 (so more connection to play around
// with )
// 3) very critical: DO a timeout for the http.request.
//
// e.g.
// var responseHdr = function (clientResponse) {
// if (clientResposne) {
//
// } else {
// clientRequest.abort();
// }
// };
//
// var timeoutHdr = setTimeout(function() {
// clientRequest.emit('req-timeout');
// }, 5000); // timeout after 5 secs
//
// clientRequest.on("req-timeout", responseHdr);
// clientRequest.on('error', function(e) {
// clearTimeout(timeoutHdr);
// console.error('Ok.. clientrequest error' + myCounter);
// next({err:JSON.stringify(e)});
// });
function newMail(callback) { //FIXME not beautiful to have to set prop each time here
needle.get('https://api.emailyak.com/v1/' + credentials.key + '/json/get/new/email/',
function (error, response, body){
if (!error && response.statusCode == 200) {
var mails = JSON.parse(body).Emails;
for(var i = 0; i < mails.length; i++) callback(mails[i]);
} else console.error('ERROR in EmailYak EM newMail: ' + error);
}
);
}
exports.loadCredentials = loadCredentials;
exports.newMail = newMail;

View file

@ -1,73 +0,0 @@
[
{
"id": "rule_1",
"event": "mail",
"actions": [
{
"module": "probinder->newContent",
"arguments": {
"content": "Rule#1: $X.subject"
}
}
]
},
{
"id": "rule_2",
"event": "mail",
"condition": { "sender": "sender2" },
"actions": [
{
"module": "probinder->newContent",
"arguments": {
"content": "Rule#2: $X.subject"
}
}
]
},
{
"id": "rule_emailyak",
"event": "yakmail",
"condition": { "FromAddress": "dominic.bosch.db@gmail.com" },
"actions": [
{
"module": "probinder->newContent",
"arguments": {
"content": "Received from EmailYak: $X.textbody"
}
}
]
},
{
"id": "rule_pull_emailyak",
"event": "emailyak->newMail",
"condition": { "FromAddress": "dominic.bosch.db@gmail.com" },
"actions": [
{
"module": "probinder->newContent",
"arguments": {
"content": "Received from EmailYak: $X.textbody"
}
}
]
},
{
"id": "rule_pull_probinder",
"event": "probinder->unread",
"condition": { "serviceId": "32" },
"actions": [
{
"module": "probinder->makeFileEntry",
"arguments": {
"service": "$X.serviceId",
"id": "$X.id"
}
},
{
"module": "probinder->setRead",
"arguments": {
"id": "$X.id"
}
}
]
}
]

View file

@ -16,7 +16,7 @@ fOnLoad = () ->
# editor.session.setUseSoftTabs false
document.title = 'Rule Forge!'
$( '#pagetitle' ).text '{{{user.username}}}, forge your rule!'
$( '#pagetitle' ).text '{{{user.username}}}, forge your ECA Rule!'
# Fetch Event Poller user-specific parameters
fFetchEventParams = ( name ) ->

View file

@ -21,7 +21,7 @@
editor.getSession().setMode("ace/mode/json");
editor.setShowPrintMargin(false);
document.title = 'Rule Forge!';
$('#pagetitle').text('{{{user.username}}}, forge your rule!');
$('#pagetitle').text('{{{user.username}}}, forge your ECA Rule!');
fFetchEventParams = function(name) {
var arr, obj;
if (name) {

View file

@ -2,8 +2,10 @@
<html>
<head>
<title></title>
<link href='http://fonts.googleapis.com/css?family=Roboto:300' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Nunito' rel='stylesheet' type='text/css'>
<link rel="stylesheet" type="text/css" href="style.css">
<script src='//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js' type='text/javascript'></script>
<script src='http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js' type='text/javascript'></script>
<script type='text/javascript'>
var checkIncludes = function () {
if( window.$ === undefined ) {
@ -23,6 +25,7 @@
{{{menubar}}}
<div id="info"></div>
<div id="mainbody">
<p></p>
<div id="pagetitle"></div>
{{{content}}}
</div>

View file

@ -7,38 +7,147 @@ Action Invoker Name: <input id="input_id" type="text" />
<tr>
<td id="editor_col" valign="top">
<div id="editor">
#
# ProBinder Action Invoker
#
# Action Invoker requires user params:
# - username: The ProBinder login username
# - password: The ProBinder login password
#
###
ProBinder ACTION INVOKER
------------------------
Global variables
This module requires user-specific parameters:
- username
- password
- companyId: company where to post the binder entries
- contextId: context where to post the binder entries
###
urlService = 'https://probinder.com/service/'
credentials =
username: params.username
password: params.password
#
# createBinderEntry function requires arguments:
# The standard callback can be used if callback is not provided, e.g. if
# the function is called from outside
#
# - company: The ProBidner company of the binder
# - context: The ProBinder context (the binder ID)
#
exports.createBinderEntry = ( evt ) ->
url = 'https://probinder.com/service/27/save'
credentials =
username: params.username
password: params.password
data =
companyId: evt.company
context: evt.context
text: evt.content
needle.post url, data, credentials, ( err, resp, body ) ->
standardCallback = ( funcName ) ->
( err, resp, body ) ->
if err
log err
if resp.statusCode isnt 200
log 'Request not successful:'
log body
log "ERROR: During function '#{ funcName }'"
else
if resp.statusCode is 200
log "Function '#{ funcName }' ran through without error"
else
log "ERROR: During function '#{ funcName }': #{ body.error.message }"
###
Call the ProBinder service with the given parameters.
@param {Object} args the required function arguments object
@param {Object} [args.data] the data to be posted
@param {String} args.service the required service identifier to be appended to the url
@param {String} args.method the required method identifier to be appended to the url
@param {function} [args.callback] the function to receive the request answer
###
exports.call = ( args ) ->
if not args.service or not args.method
log 'ERROR in call function: Missing arguments!'
else
if not args.callback
args.callback = standardCallback 'call'
url = urlService + args.service + '/' + args.method
needlereq 'post', url, args.data, credentials, args.callback
###
Calls the user's unread content service.
@param {Object} [args] the optional object containing the callback function
@param {function} [args.callback] refer to call function
###
exports.getUnreadContents = ( args ) ->
if not args.callback
args.callback = standardCallback 'getUnreadContents'
exports.call
service: '36'
method: 'unreadcontent'
callback: args.callback
###
Calls the content get service with the content id and the service id provided.
@param {Object} args the object containing the service id and the content id,
success and error callback methods
@param {String} args.serviceid the service id that is able to process this content
@param {String} args.contentid the content id
@param {function} [args.callback] receives the needle answer from the "call" function
###
exports.getContent = ( args ) ->
if not args.callback
args.callback = standardCallback 'getContent'
exports.call
service: '2'
method: 'get'
data:
id: args.contentid
service: args.serviceid
callback: args.callback
###
Does everything to post something in a binder
@param {Object} args the object containing the content
@param {String} args.content the content to be posted
###
exports.newContent = ( args ) ->
if not args.callback
args.callback = standardCallback 'newContent'
exports.call
service: '27'
method: 'save'
data:
companyId: params.companyId
context: params.contextId
text: args.content
callback: args.callback
###
Does everything to post a file info in a binder tabe
@param {Object} args the object containing the content
@param {String} args.service the content service
@param {String} args.id the content id
###
exports.makeFileEntry = ( args ) ->
if not args.callback
args.callback = standardCallback 'makeFileEntry'
exports.getContent
serviceid: args.service
contentid: args.id
callback: ( err, resp, body ) ->
exports.call
service: '27'
method: 'save'
data:
companyId: params.companyId
context: params.contextId
text: "New file (#{ body.title }) in tab \"#{ body.context[0].name }\",
find it <a href=\"https://probinder.com/file/#{ body.fileIds[0] }\">here</a>!'"
callback: args.callback
###
Sets the content as read.
@param {Object} args the object containing the content
@param {String} args.content the content to be posted
###
exports.setRead = ( args ) ->
if not args.callback
args.callback = standardCallback 'setRead'
exports.call
service: '2'
method: 'setread'
data:
id: args.id
callback: args.callback
</div>
<button id="but_submit">save</button>
</td>
@ -53,6 +162,14 @@ This action invoker requires user-specific properties:
<td><img src="red_cross_small.png"></td>
<td><input type="text" value="password" /></td>
</tr>
<tr>
<td><img src="red_cross_small.png"></td>
<td><input type="text" value="companyId" /></td>
</tr>
<tr>
<td><img src="red_cross_small.png"></td>
<td><input type="text" value="contextId" /></td>
</tr>
<tr>
<td><img src="red_cross_small.png"></td>
<td><input type="text" /></td>

View file

@ -9,6 +9,7 @@ Event Poller Name: <input id="input_id" type="text" />
<div id="editor">
#
# EmailYak EVENT POLLER
# ---------------------
#
# Requires user params:
# - apikey: The user's EmailYak API key
@ -17,13 +18,19 @@ Event Poller Name: <input id="input_id" type="text" />
url = 'https://api.emailyak.com/v1/' + params.apikey + '/json/get/new/email/'
exports.newMail = ( pushEvent ) ->
needle.get url, ( err, resp, body ) ->
if not err and resp.statusCode is 200
mails = JSON.parse( body ).Emails
pushEvent mail for mail in mails
else
log.error 'Error in EmailYak EM newMail: ' + err.message
# needlereq allows the user to make calls to API's
# Refer to https://github.com/tomas/needle for more information
#
# Syntax: needle.request method, url, data, [options], callback
#
needlereq 'get', url, null, null, ( err, resp, body ) ->
if err
log 'Error in EmailYak EM newMail: ' + err.message
else
if resp.statusCode is 200
mails = JSON.parse( body ).Emails
pushEvent mail for mail in mails
</div>
<button id="but_submit">save</button>
</td>

View file

@ -29,7 +29,7 @@
fCreateLink( 'Edit Modules',
fRedirect( 'forge?page=edit_modules' )
);
fCreateLink( 'Edit rules',
fCreateLink( 'Edit Rules',
fRedirect( 'forge?page=edit_rules' )
);
// fCreateLink( 'admin', fRedirect( 'admin' ) );

View file

@ -0,0 +1,61 @@
<!DOCTYPE HTML>
<html>
<head>
<title></title>
<link href='http://fonts.googleapis.com/css?family=Roboto:300' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Nunito' rel='stylesheet' type='text/css'>
<link rel="stylesheet" type="text/css" href="style.css">
<script src='http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js' type='text/javascript'></script>
</head>
<body>
<div id="menubar">
<script>
var menubar = $( '#menubar' );
var fRedirect = function( url ) {
return function() {
window.location.href = url;
}
};
var fCreateLink = function( text, fAction ) {
var link = $( '<div>' ).text( text );
link.click( fAction );
menubar.append(link);
};
fCreateLink( 'Forge Event Poller',
fRedirect( 'forge?page=forge_event_poller' )
);
fCreateLink( 'Forge Action Invoker',
fRedirect( 'forge?page=forge_action_invoker' )
);
fCreateLink( 'Forge Rule',
fRedirect( 'forge?page=forge_rule' )
);
fCreateLink( 'Invoke Event',
fRedirect( 'forge?page=forge_event' )
);
fCreateLink( 'Edit Modules',
fRedirect( 'forge?page=edit_modules' )
);
fCreateLink( 'Edit Rules',
fRedirect( 'forge?page=edit_rules' )
);
fCreateLink( 'Logout', function() {
$.post( '/logout' ).done( fRedirect( document.URL ) );
});
</script>
</div>
<div id="info"></div>
<div id="mainbody">
<p></p>
<div id="pagetitle">Welcome to the WebAPI ECA-Engine!</div>
<p>
Enjoy creating your own ECA rules, together with the required action invoker modules!
</p>
</div>
</body>
</html>

View file

@ -1,7 +1,7 @@
body {
font-family: sans-serif, "Times New Roman", Georgia, Serif;
font-size: 80%;
font-family: 'Roboto', sans-serif, "Times New Roman", Georgia, Serif;
/*font-size: 0.9em;*/
margin: 0px;
background-color: #EEF;
}
@ -75,7 +75,7 @@ input[type=text]:focus {
}
#pagetitle {
font-size: 1.5em;
font-size: 2em;
font-weight: bold;
padding-top: 5px;
padding-bottom: 5px;