From 003fe4111d879f9f9b95ddd4d90334262c15967e Mon Sep 17 00:00:00 2001 From: Dominic Bosch Date: Sun, 6 Apr 2014 02:22:39 +0200 Subject: [PATCH] Debugging tools and use case implementation --- README.md | 18 +- coffee/components-manager.coffee | 2 +- coffee/dynamic-modules.coffee | 25 ++- examples/action-invokers/continuously.coffee | 6 + examples/action-invokers/probinder.coffee | 142 ++++++++++++++ examples/event-pollers/emailyak.coffee | 26 +++ examples/runscript.coffee | 37 ++++ examples/runscript.js | 39 ++++ js/components-manager.js | 2 +- js/dynamic-modules.js | 26 +-- mod_events/emailyak/emailyak.js | 55 ------ rules/rules.json | 73 -------- webpages/handlers/coffee/forge_rule.coffee | 2 +- webpages/handlers/js/forge_rule.js | 2 +- webpages/handlers/skeleton.html | 5 +- .../templates/forge_action_invoker.html | 173 +++++++++++++++--- .../templates/forge_event_poller.html | 19 +- webpages/handlers/templates/menubar.html | 2 +- webpages/public/index.html | 61 ++++++ webpages/public/style.css | 6 +- 20 files changed, 514 insertions(+), 207 deletions(-) create mode 100644 examples/action-invokers/continuously.coffee create mode 100644 examples/action-invokers/probinder.coffee create mode 100644 examples/event-pollers/emailyak.coffee create mode 100644 examples/runscript.coffee create mode 100644 examples/runscript.js delete mode 100644 mod_events/emailyak/emailyak.js delete mode 100644 rules/rules.json create mode 100644 webpages/public/index.html diff --git a/README.md b/README.md index 41e6d7a..5572ff0 100644 --- a/README.md +++ b/README.md @@ -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 \ No newline at end of file diff --git a/coffee/components-manager.coffee b/coffee/components-manager.coffee index 2045f77..8a787c1 100644 --- a/coffee/components-manager.coffee +++ b/coffee/components-manager.coffee @@ -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 diff --git a/coffee/dynamic-modules.coffee b/coffee/dynamic-modules.coffee index ab7605f..ad2c6ff 100644 --- a/coffee/dynamic-modules.coffee +++ b/coffee/dynamic-modules.coffee @@ -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 diff --git a/examples/action-invokers/continuously.coffee b/examples/action-invokers/continuously.coffee new file mode 100644 index 0000000..51af39a --- /dev/null +++ b/examples/action-invokers/continuously.coffee @@ -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" \ No newline at end of file diff --git a/examples/action-invokers/probinder.coffee b/examples/action-invokers/probinder.coffee new file mode 100644 index 0000000..a6d5a30 --- /dev/null +++ b/examples/action-invokers/probinder.coffee @@ -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 here!'" + 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 \ No newline at end of file diff --git a/examples/event-pollers/emailyak.coffee b/examples/event-pollers/emailyak.coffee new file mode 100644 index 0000000..313fad6 --- /dev/null +++ b/examples/event-pollers/emailyak.coffee @@ -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 + diff --git a/examples/runscript.coffee b/examples/runscript.coffee new file mode 100644 index 0000000..fed190f --- /dev/null +++ b/examples/runscript.coffee @@ -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!" \ No newline at end of file diff --git a/examples/runscript.js b/examples/runscript.js new file mode 100644 index 0000000..d31dc56 --- /dev/null +++ b/examples/runscript.js @@ -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); diff --git a/js/components-manager.js b/js/components-manager.js index a9a992d..2770d7b 100644 --- a/js/components-manager.js +++ b/js/components-manager.js @@ -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); diff --git a/js/dynamic-modules.js b/js/dynamic-modules.js index bf227d8..4ec929b 100644 --- a/js/dynamic-modules.js +++ b/js/dynamic-modules.js @@ -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, diff --git a/mod_events/emailyak/emailyak.js b/mod_events/emailyak/emailyak.js deleted file mode 100644 index ed7fb01..0000000 --- a/mod_events/emailyak/emailyak.js +++ /dev/null @@ -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; diff --git a/rules/rules.json b/rules/rules.json deleted file mode 100644 index 74f720e..0000000 --- a/rules/rules.json +++ /dev/null @@ -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" - } - } - ] - } -] \ No newline at end of file diff --git a/webpages/handlers/coffee/forge_rule.coffee b/webpages/handlers/coffee/forge_rule.coffee index fcabce8..b7e722f 100644 --- a/webpages/handlers/coffee/forge_rule.coffee +++ b/webpages/handlers/coffee/forge_rule.coffee @@ -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 ) -> diff --git a/webpages/handlers/js/forge_rule.js b/webpages/handlers/js/forge_rule.js index 0bf8a28..5b3bf9e 100644 --- a/webpages/handlers/js/forge_rule.js +++ b/webpages/handlers/js/forge_rule.js @@ -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) { diff --git a/webpages/handlers/skeleton.html b/webpages/handlers/skeleton.html index 684928e..4505daa 100644 --- a/webpages/handlers/skeleton.html +++ b/webpages/handlers/skeleton.html @@ -2,8 +2,10 @@ + + - + + + + + +
+
+

+ +
Welcome to the WebAPI ECA-Engine!
+

+ Enjoy creating your own ECA rules, together with the required action invoker modules! +

+
+ + diff --git a/webpages/public/style.css b/webpages/public/style.css index 4d38a85..5d86855 100644 --- a/webpages/public/style.css +++ b/webpages/public/style.css @@ -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;