Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/optional rule name #316

Merged
merged 12 commits into from
Feb 1, 2019
3 changes: 2 additions & 1 deletion CHANGES_NEXT_RELEASE
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
- 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
- Add: new rewire ~4.0.1 dev dependency
- 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
- Upgrade dev dependency sinon-chai from 2.4.0 to ~3.2.0
2 changes: 1 addition & 1 deletion documentation/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -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\"))]",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doubt: at rule creation time (POST /rules, if I'm remembering correctly) it is clear that ruleName is not include in the EPL and it is automatically added.

However, what about of GET /rules/:id? Is would be the automatically added ruleName added there?

Copy link
Contributor Author

@cblanco cblanco Feb 1, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. The ruleName is added during the creation automatically in the 'text' field. If the rule is requested using GET / rules/:id or GET /rules, it will include the ruleName in the 'text' field.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's a good idea to mention it in the documentation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0917cbd

"action" : {
"type" : "email",
"template" : "DCA message",
Expand Down
2 changes: 1 addition & 1 deletion documentation/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.*\"))]"
}

```
Expand Down
18 changes: 9 additions & 9 deletions documentation/plain_rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -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":{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}",
Expand Down Expand Up @@ -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})",
Expand Down Expand Up @@ -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}!",
Expand Down
28 changes: 28 additions & 0 deletions lib/models/rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
*
* For those usages not covered by the GNU Affero General Public License
* please contact with::[[email protected]]
*
* Modified by: Carlos Blanco - Future Internet Consulting and Development Solutions (FICODES)
*/
'use strict';

Expand Down Expand Up @@ -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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation should be changed to explain that now the name is not mandatory and which happens if the name is ommited (i.e. an example of autogenerated name would be great).

Copy link
Contributor Author

@cblanco cblanco Jan 30, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have changed the documentation but I do not know if I have approached it in the right way, please check if the change seems appropriate. b356343

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) {
Expand All @@ -150,6 +170,10 @@ module.exports = {
myutils.logErrorIf(localError);
return callback(localError);
}

// Normalize the rule text
rule = normalizeRuleName(rule);

async.series(
[
function(localCallback) {
Expand Down Expand Up @@ -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),
Expand Down
81 changes: 81 additions & 0 deletions test/component/rules_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down
11 changes: 11 additions & 0 deletions test/data/bad_rules/rule_bad_ruleName_text_rule_post.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
11 changes: 11 additions & 0 deletions test/data/good_rules/no_ruleName_in_text_rule_post.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}