diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index 833d1cfd..1c106cbd 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -7,6 +7,7 @@ - Remove: old unused development dependencies * grunt and grunt related module * closure-linter-wrapper +- Add 'ruleName' as variable automatically in rule text field (EPL) on rule creation time (#307) - Change on the PERSEO_ORION_URL env var behaviour. Now it represents Context Broker base URL instead of the updateContext endpoint - Add: new ngsijs ~1.2.0 dependency @@ -14,4 +15,4 @@ - Add: NGSIv2 support in both notification reception and CB update action - Upgrade dev dependency chai from ~1.8.0 to ~4.1.2 - Upgrade dev dependency sinon from ~1.7.3 to ~6.1.0 -- Upgrade dev dependency sinon-chai from 2.4.0 to ~3.2.0 \ No newline at end of file +- Upgrade dev dependency sinon-chai from 2.4.0 to ~3.2.0 diff --git a/documentation/architecture.md b/documentation/architecture.md index 272dd447..d5e346f6 100644 --- a/documentation/architecture.md +++ b/documentation/architecture.md @@ -191,7 +191,7 @@ A simplified format in JSON can be used to represent rules. The former visual ru ```json { "name" : "prueba-test", - "text" : "select *,\"prueba-test\" as ruleName from pattern [every ev=iotEvent((cast(id?, String) regexp \"asd\"))]", + "text" : "select * from pattern [every ev=iotEvent((cast(id?, String) regexp \"asd\"))]", "action" : { "type" : "email", "template" : "DCA message", diff --git a/documentation/models.md b/documentation/models.md index 167b872f..e62f8ed5 100644 --- a/documentation/models.md +++ b/documentation/models.md @@ -49,7 +49,7 @@ Example: }, "subservice" : "/", "service" : "unknownt", - "text" : "select *,\"ReglaId\" as ruleName from pattern [every ev=iotEvent((cast(`id`?, String) regexp \"^value.*\"))]" + "text" : "select * from pattern [every ev=iotEvent((cast(`id`?, String) regexp \"^value.*\"))]" } ``` diff --git a/documentation/plain_rules.md b/documentation/plain_rules.md index 5f36e5fe..853b1d18 100644 --- a/documentation/plain_rules.md +++ b/documentation/plain_rules.md @@ -7,7 +7,7 @@ The “anatomy” of a rule is as follows ```json { "name":"blood_rule_update", - "text":"select *,\"blood_rule_update\" as ruleName, *, ev.BloodPressure? as Pressure, ev.id? as Meter from pattern [every ev=iotEvent(cast(cast(BloodPressure?,String),float)>1.5 and type=\"BloodMeter\")]", + "text":"select *, *, ev.BloodPressure? as Pressure, ev.id? as Meter from pattern [every ev=iotEvent(cast(cast(BloodPressure?,String),float)>1.5 and type=\"BloodMeter\")]", "action":{ "type":"update", "parameters":{ @@ -36,16 +36,16 @@ EPL is documented in [Esper website](http://www.espertech.com/esper/esper-docume A EPL statement to use with perseo could be: ``` -select *, "blood_rule_update" as ruleName, - ev.BloodPressure? as Pressure, ev.id? as Meter +select *, ev.BloodPressure? as Pressure, ev.id? as Meter from pattern - [every ev=iotEvent(cast(cast(BloodPressure?,String),float)>1.5 and type="BloodMeter")] + [every ev=iotEvent(cast(cast(BloodPressure?,String),float)>1.5 and type="BloodMeter")] ``` - -* The rule name must be present with **ruleName** alias. It must be equal to the ‘name’ field of the rule object * The *from* pattern must name the event as **ev** and the event stream from which take events must be **iotEvent** * A *type=* condition must be concatenated for avoiding mixing different kinds of entities +* The variable 'ruleName' in automatically added to the action, even if it is not present in the EPL text. The ruleName automatically added this way is retrieved as part of the EPL text when the rule is recovered using GET /rules or GET /rules/{name}. + +**Backward compatibility note:** since version 1.8.0 it is not mandatory to specify the name of the rule as part of the EPL text. In fact, it is not recommendable to do that. However, for backward compatibility, it can be present as *ruleName* alias (`e.g: select *, "blood_rule_update" as ruleName...`) in the select clause. If present, it must be equal to the ‘name’ field of the rule object. The used entity's attributes must be cast to `float` in case of being numeric (like in the example). Alphanumeric values must be cast to `String`. Nested cast to string and to float is something we are analyzing, and could be @@ -527,7 +527,7 @@ could be used by a rule so ```json { "name": "blood_rule_email_md", - "text": "select *,\"blood_rule_email_md\" as ruleName, *,ev.BloodPressure? as Pression, ev.id? as Meter from pattern [every ev=iotEvent(cast(BloodPressure__metadata__crs__system?,String)=\"WGS84\" and type=\"BloodMeter\")]", + "text": "select *, *,ev.BloodPressure? as Pression, ev.id? as Meter from pattern [every ev=iotEvent(cast(BloodPressure__metadata__crs__system?,String)=\"WGS84\" and type=\"BloodMeter\")]", "action": { "type": "email", "template": "Meter ${Meter} has pression ${Pression} (GEN RULE) and system is ${BloodPressure__metadata__crs__system}", @@ -666,7 +666,7 @@ An example of rule taking advantage of these derived attributes could be: ```json { "name": "rule_distance", - "text": "select *, \"rule_distance\" as ruleName from pattern [every ev=iotEvent(Math.pow((cast(cast(position__x?,String),float) - 618618.8286057833), 2) + Math.pow((cast(cast(position__y?,String),float) - 9764160.736945232), 2) < Math.pow(5e3,2))]", + "text": "select *, from pattern [every ev=iotEvent(Math.pow((cast(cast(position__x?,String),float) - 618618.8286057833), 2) + Math.pow((cast(cast(position__y?,String),float) - 9764160.736945232), 2) < Math.pow(5e3,2))]", "action": { "type": "email", "template": "${id} (${type}) is at ${position__lat}, ${position__lon} (${position__x}, ${position__y})", @@ -846,7 +846,7 @@ A rule that will check if the employee has been hired in the last half hour, cou ```json { "name": "rule_time", - "text": "select *, \"rule_time\" as ruleName from pattern [every ev=iotEvent(cast(cast(hire__ts?,String),float) > current_timestamp - 30*60*1000)]", + "text": "select *, from pattern [every ev=iotEvent(cast(cast(hire__ts?,String),float) > current_timestamp - 30*60*1000)]", "action": { "type": "email", "template": "So glad with our new ${role}, ${id}!", diff --git a/lib/models/rules.js b/lib/models/rules.js index 5e9ec496..091f4d11 100644 --- a/lib/models/rules.js +++ b/lib/models/rules.js @@ -19,6 +19,8 @@ * * For those usages not covered by the GNU Affero General Public License * please contact with::[contacto@tid.es] + * + * Modified by: Carlos Blanco - Future Internet Consulting and Development Solutions (FICODES) */ 'use strict'; @@ -132,6 +134,24 @@ function putR2core(rules, callback) { myutils.requestHelperWOMetrics('put', {url: config.perseoCore.rulesURL, json: rulesAndContexts}, callback); } +/** + * Add ruleName if necessary in rule text field + * + * @param rule The rule object + */ +function normalizeRuleName(rule) { + + var newAs = '"' + rule.name + '" as ruleName'; + // Add "name as ruleName" if not exist + if (rule.text && rule.text.indexOf(' as ruleName') === -1) { + var offset = 'select'.length + 1; + var idx = rule.text.toLowerCase().indexOf('select') + offset; + rule.text = rule.text = rule.text.slice(0, idx) + newAs + ', ' + rule.text.slice(idx); + } + + return rule; +} + module.exports = { FindAll: function(service, subservice, callback) { rulesStore.FindAll(service, subservice, function(err, data) { @@ -150,6 +170,10 @@ module.exports = { myutils.logErrorIf(localError); return callback(localError); } + + // Normalize the rule text + rule = normalizeRuleName(rule); + async.series( [ function(localCallback) { @@ -208,6 +232,10 @@ module.exports = { myutils.logErrorIf(localError); return callback(localError); } + + // Normalize the rule text + rule = normalizeRuleName(rule); + async.series( [ delR2core.bind(null, rule), diff --git a/test/component/rules_test.js b/test/component/rules_test.js index e30329bf..9f54ef70 100644 --- a/test/component/rules_test.js +++ b/test/component/rules_test.js @@ -19,6 +19,8 @@ * * For those usages not covered by the GNU Affero General Public License * please contact with iot_support at tid dot es + * + * Modified by: Carlos Blanco - Future Internet Consulting and Development Solutions (FICODES) */ 'use strict'; @@ -87,6 +89,85 @@ describe('Rules', function() { } ], done); }); + it('should add ruleName automatically when the rule text does not include "rule as ruleName"', function(done) { + var rule = utilsT.loadExample('./test/data/good_rules/no_ruleName_in_text_rule_post.json'); + async.series([ + function(callback) { + clients.PostRule(rule, function(error, data) { + should.not.exist(error); + data.should.have.property('statusCode', 200); + return callback(null); + }); + }, + function(callback) { + clients.GetRule(rule.name, function(error, data) { + should.not.exist(error); + data.should.have.property('statusCode', 200); + data.should.have.property('body'); + data.body.should.have.property('data'); + data.body.data.should.have.property('text', + 'select "x_post_auto" as ruleName, *, ev.xPressure? as Pression, ev.id? as Meter from ' + + 'pattern [every ev=iotEvent(cast(cast(xPressure?,String),float)>1.5 and type="xMeter")]'); + data.body.data.should.have.property('name', rule.name); + + return callback(); + }); + } + ], done); + }); + it('should preserve user ruleName', function(done) { + var rule = utilsT.loadExample('./test/data/good_rules/blood_rule_post.json'); + async.series([ + function(callback) { + clients.PostRule(rule, function(error, data) { + should.not.exist(error); + data.should.have.property('statusCode', 200); + return callback(null); + }); + }, + function(callback) { + clients.GetRule(rule.name, function(error, data) { + should.not.exist(error); + data.should.have.property('statusCode', 200); + data.should.have.property('body'); + data.body.should.have.property('data'); + data.body.data.should.have.property('text', + 'select *,\"blood_post\" as ruleName,ev.BloodPressure? as Pression, ev.id? as Meter from' + + ' pattern [every ev=iotEvent(cast(cast(BloodPressure?,String),float)>1.5 and ' + + 'type=\"BloodMeter\")]'); + data.body.data.should.have.property('name', rule.name); + + return callback(); + }); + } + ], done); + }); + it('should preserve user incorrect ruleName', function(done) { + var rule = utilsT.loadExample('./test/data/bad_rules/rule_bad_ruleName_text_rule_post.json'); + async.series([ + function(callback) { + clients.PostRule(rule, function(error, data) { + should.not.exist(error); + data.should.have.property('statusCode', 200); + return callback(null); + }); + }, + function(callback) { + clients.GetRule(rule.name, function(error, data) { + should.not.exist(error); + data.should.have.property('statusCode', 200); + data.should.have.property('body'); + data.body.should.have.property('data'); + data.body.data.should.have.property('text', + 'select *, "badRuleleName" as ruleName, ev.xPressure? as Pression, ev.id? as Meter from' + + ' pattern [every ev=iotEvent(cast(cast(xPressure?,String),float)>1.5 and type="xMeter")]'); + data.body.data.should.have.property('name', rule.name); + + return callback(); + }); + } + ], done); + }); it('should return an error if core endpoint is not working', function(done) { var cases = utilsT.loadDirExamples('./test/data/good_rules'); utilsT.setServerCode(400); diff --git a/test/data/bad_rules/rule_bad_ruleName_text_rule_post.json b/test/data/bad_rules/rule_bad_ruleName_text_rule_post.json new file mode 100644 index 00000000..f27e925a --- /dev/null +++ b/test/data/bad_rules/rule_bad_ruleName_text_rule_post.json @@ -0,0 +1,11 @@ +{ + "name": "theCorrectName", + "text": "select *, \"badRuleleName\" as ruleName, ev.xPressure? as Pression, ev.id? as Meter from pattern [every ev=iotEvent(cast(cast(xPressure?,String),float)>1.5 and type=\"xMeter\")]", + "action": { + "type": "post", + "template": "Meter ${Meter} has pression ${Pression}.", + "parameters": { + "url": "localhost:1111" + } + } +} \ No newline at end of file diff --git a/test/data/good_rules/no_ruleName_in_text_rule_post.json b/test/data/good_rules/no_ruleName_in_text_rule_post.json new file mode 100644 index 00000000..a0d9b495 --- /dev/null +++ b/test/data/good_rules/no_ruleName_in_text_rule_post.json @@ -0,0 +1,11 @@ +{ + "name": "x_post_auto", + "text": "select *, ev.xPressure? as Pression, ev.id? as Meter from pattern [every ev=iotEvent(cast(cast(xPressure?,String),float)>1.5 and type=\"xMeter\")]", + "action": { + "type": "post", + "template": "Meter ${Meter} has pression ${Pression}.", + "parameters": { + "url": "localhost:1111" + } + } +} \ No newline at end of file