diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index e69de29b..a3ccae70 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -0,0 +1,2 @@ +- Add: /api-docs endpoint providing swagger-based documentation of the HTTP endpoints exposed by Perseo FE +- Hardening: software quality improvement based on ISO25010 recommendations \ No newline at end of file diff --git a/documentation/development.md b/documentation/development.md index a276c816..65fbfbd6 100644 --- a/documentation/development.md +++ b/documentation/development.md @@ -108,3 +108,31 @@ To ensure consistent Markdown formatting run the following: # Use git-bash on Windows npm run prettier:text ``` + +## User & Programmers Manual + +### Swagger + +In order to run Swagger, you need to execute the Perseo FE (as explained [here](deployment.md) and then you can access +to: :9090/api-docs + +The swagger documentation provided at /api-docs covers all the HTTP endpoint exposed by Perseo FE. + +#### Instalation + +To run Swagger in Perseo we have to install two NPM packages for NodeJs: + +- Swagger-jsdoc: allows us to document our application's endpoints with notations. +- Swagger-ui-express: generates an interface with endpoints definitions. We have to specify our swagger's version, our + API's information and the route where we can find the endpoints in the project. +

+     definition: {
+                    swagger: '2.0', // Specification (optional, defaults to swagger: '2.0')
+                    info: {
+                        title: 'Perseo Front-End', // Title (required)
+                        version: '1.7.0-fiqare' // Version (required)
+                    }
+                },
+    
+ Once the project is deployed (port 9090 by default), we can access to the interface by the following link: + . diff --git a/documentation/plain_rules.md b/documentation/plain_rules.md index 1aec190b..a31aa253 100644 --- a/documentation/plain_rules.md +++ b/documentation/plain_rules.md @@ -19,7 +19,9 @@ There are two kind of rules: -* Esper-based rules, which include the final EPL statement used by the Esper engine inside perseo-core. In order to work with perseo (front-end) properly, the EPL statement must fulfill several conventions for the rule to be able to operate on the incoming events and trigger adequate actions. Example: +- Esper-based rules, which include the final EPL statement used by the Esper engine inside perseo-core. In order to + work with perseo (front-end) properly, the EPL statement must fulfill several conventions for the rule to be able to + operate on the incoming events and trigger adequate actions. Example: ```json { @@ -40,9 +42,8 @@ There are two kind of rules: } ``` -* No signal rules. They are triggered when a given attribute is not updated in a given interval of time. They -don't use Esper at persero-core (they are checked and triggered by perseo frontend). Example: - +- No signal rules. They are triggered when a given attribute is not updated in a given interval of time. They don't + use Esper at persero-core (they are checked and triggered by perseo frontend). Example: ```json { @@ -127,7 +128,8 @@ information on how to scape characters at ## No signal conditions -The no signal condition is specified in the `nosignal` configuration element, which is an object with the following fields: +The no signal condition is specified in the `nosignal` configuration element, which is an object with the following +fields: - **checkInterval**: _mandatory_, time in minutes for checking the attribute - **attribute**: _mandatory_, attribute for watch @@ -135,7 +137,7 @@ The no signal condition is specified in the `nosignal` configuration element, wh - **id** or **idRegexp**: _mandatory_ (but not both at the same time), id or regex of the entity to watch - type: _optional_, type of entities to watch -Is recommended to set checkInterval at least double of reportInterval. Howeer, note that a very demanding value of +Is recommended to set checkInterval at least double of reportInterval. Howeer, note that a very demanding value of checkInterval could impact on performance. diff --git a/lib/alarm.js b/lib/alarm.js index a9fe7f09..b7a2bfc6 100644 --- a/lib/alarm.js +++ b/lib/alarm.js @@ -30,6 +30,7 @@ var util = require('util'), function raise(alarm, context, message) { var state = alarms[alarm]; context = (process.domain && process.domain.context) || {}; + if (typeof message === 'object' && message) { message = util.format('%j', message); } @@ -43,6 +44,7 @@ function raise(alarm, context, message) { function release(alarm, context, message) { var state = alarms[alarm]; context = (process.domain && process.domain.context) || {}; + if (typeof message === 'object' && message) { message = util.format('%j', message); } diff --git a/lib/models/actions.js b/lib/models/actions.js index 1929ecd9..8c21e0bb 100644 --- a/lib/models/actions.js +++ b/lib/models/actions.js @@ -81,6 +81,9 @@ function validateAction(axn) { return new errors.IdAsAttribute(JSON.stringify(axnElem.parameters)); } else if (axnElem.parameters.name === 'type') { return new errors.IdAsAttribute(JSON.stringify(axnElem.parameters)); + } else { + //Added default else clause + return null; } } } @@ -227,6 +230,7 @@ function DoAction(event, callback) { actionStore.Find(service, subservice, ruleName, function(err, actions) { var localError, queue; + if (err) { return callback(err, null); } diff --git a/lib/models/actionsStore.js b/lib/models/actionsStore.js index 6ae7ddfd..680a169b 100644 --- a/lib/models/actionsStore.js +++ b/lib/models/actionsStore.js @@ -40,8 +40,10 @@ module.exports = { localError = new errors.NotFoundAction(data); myutils.logErrorIf(localError); return callback(localError, null); + } else { + //Added default else clause + return callback(null, data.action); } - return callback(null, data.action); }); } }; diff --git a/lib/models/emailAction.js b/lib/models/emailAction.js index 061459b9..88778205 100644 --- a/lib/models/emailAction.js +++ b/lib/models/emailAction.js @@ -25,13 +25,12 @@ var util = require('util'), nodemailer = require('nodemailer'), logger = require('logops'), - smtpTransport = require('nodemailer-smtp-transport'), config = require('../../config'), myutils = require('../myutils'), alarm = require('../alarm'), metrics = require('./metrics'); -var transporter = nodemailer.createTransport(smtpTransport(config.smtp)); +var transporter = nodemailer.createTransport(config.smtp); function buildMailOptions(action, event) { return { diff --git a/lib/models/entitiesStore.js b/lib/models/entitiesStore.js index 2ad5c96c..b279936d 100644 --- a/lib/models/entitiesStore.js +++ b/lib/models/entitiesStore.js @@ -52,6 +52,9 @@ function findSilentEntities(service, subservice, ruleData, func, callback) { } catch (e) { return callback(e, null); } + } else { + //Added default else clause + logger.debug('findSilentEntities() - Default else clause'); } if (ruleData.type) { criterion['_id.type'] = ruleData.type; diff --git a/lib/models/paths.js b/lib/models/paths.js index c418646e..156bf5ba 100644 --- a/lib/models/paths.js +++ b/lib/models/paths.js @@ -38,6 +38,10 @@ function validComponent(component) { } else if (!componentPattern.test(component)) { err = new errors.InvalidCharacter(component); } + else { + //Added default else clause + return err; + } return err; } function validService(service) { diff --git a/lib/models/postAction.js b/lib/models/postAction.js index cf0aa5c4..94b12687 100644 --- a/lib/models/postAction.js +++ b/lib/models/postAction.js @@ -39,6 +39,10 @@ function buildPostOptions(action, event) { } else if (action.template) { options.text = myutils.expandVar(action.template, event, true); } + else { + //Added default else clause + return options; + } return options; } @@ -58,6 +62,10 @@ function doIt(action, event, callback) { } else if (options.text) { requestOptions.body = options.text; } + else { + //Added default else clause + logger.debug('doIt() - Default else clause'); + } metrics.IncMetrics(event.service, event.subservice, metrics.actionHttpPost); diff --git a/lib/models/rules.js b/lib/models/rules.js index 55ed3b71..6a30238e 100644 --- a/lib/models/rules.js +++ b/lib/models/rules.js @@ -31,6 +31,7 @@ var util = require('util'), myutils = require('../myutils'), noSignal = require('./noSignal'), actions = require('./actions'), + logger = require('logops'), namePattern = /^[a-zA-Z0-9_-]+$/, MaxNameLength = 50, errors = {}; @@ -58,6 +59,9 @@ function validRule(rule) { return new errors.InvalidRuleName(rule.name); } else if (!rule.text && !rule.nosignal) { return new errors.EmptyRule(JSON.stringify(rule)); + } else { + //Added default else clause + logger.debug('validRule() - Default else clause'); } if (rule.nosignal) { //Specific checks for no-signal rules @@ -218,6 +222,7 @@ module.exports = { }, Save: function(rule, callback) { var localError; + localError = validRule(rule); if (localError !== null) { myutils.logErrorIf(localError); @@ -244,7 +249,12 @@ module.exports = { rulesStore.Save.bind(null, rule), function(cb) { if (rule.nosignal) { - noSignal.AddNSRule(rule.service, rule.subservice, rule.name, rule.nosignal); + noSignal.AddNSRule( + rule.service, + rule.subservice, + rule.name, + rule.nosignal + ); } cb(null); } @@ -294,14 +304,23 @@ module.exports = { delR2core.bind(null, rule), function(cb) { if (rule.nosignal) { - noSignal.DeleteNSRule(rule.service, rule.subservice, rule.name); + noSignal.DeleteNSRule( + rule.service, + rule.subservice, + rule.name + ); } cb(null); }, postR2core.bind(null, rule), function(cb) { if (rule.nosignal) { - noSignal.AddNSRule(rule.service, rule.subservice, rule.name, rule.nosignal); + noSignal.AddNSRule( + rule.service, + rule.subservice, + rule.name, + rule.nosignal + ); } cb(null); }, diff --git a/lib/models/updateAction.js b/lib/models/updateAction.js index 07ffc9a6..5c1719e2 100644 --- a/lib/models/updateAction.js +++ b/lib/models/updateAction.js @@ -266,6 +266,8 @@ function processOptionParams(action, event) { case 'None': theValue = null; break; + default: + //Nothing to do } } var key = myutils.expandVar(attr.name, event); diff --git a/lib/models/visualRules.js b/lib/models/visualRules.js index 2c38d205..652265ca 100644 --- a/lib/models/visualRules.js +++ b/lib/models/visualRules.js @@ -35,7 +35,8 @@ var util = require('util'), MINOR_OR_EQUAL_THAN: ' <= ', MATCH: ' regexp ' }, - errors = {}; + errors = {}, + logger = require('logops'); function errorOperator(op) { if (operatorMap[op] === undefined) { @@ -118,7 +119,6 @@ function vr2rule(cardRule) { false ) ); - break; case 'XPATH': errOp = errorOperator(card.conditionList[0].operator); @@ -143,18 +143,27 @@ function vr2rule(cardRule) { } } else if (card.conditionList[0].parameterName === 'type') { type = card.conditionList[0].parameterValue; + } else { + //Added default else clause + logger.debug('vr2rule() - Default else clause'); } break; case 'OBSERVATION': errOp = errorOperator(card.conditionList[0].operator); + if (errOp) { return errOp; } + if (card.sensorData.measureName === 'id') { return new errors.IdAsAttribute(''); } else if (card.sensorData.measureName === 'type') { return new errors.TypeAsAttribute(''); + } else { + //Added default else clause + logger.debug('vr2rule() - Default else clause'); } + conditions.push( makeObservationCondition( card.sensorData.measureName, @@ -163,6 +172,7 @@ function vr2rule(cardRule) { card.sensorData.dataType === 'Quantity' ) ); + break; case 'LAST_MEASURE': checkInterval = parseInt(card.timeData.interval, 10); @@ -225,10 +235,15 @@ function vr2rule(cardRule) { case 'timeElapsed': // Minimal interval for actions action.interval = card.timeData.interval; break; + default: + //nothing to do } } else { return new errors.MissingConfigDataInTimeCard(JSON.stringify(card)); } + break; + default: + //nothing to do } } diff --git a/lib/myutils.js b/lib/myutils.js index d9ff039b..1de80036 100644 --- a/lib/myutils.js +++ b/lib/myutils.js @@ -88,6 +88,9 @@ function expandObject(templateObj, dictionary) { res[expandVar(key, dictionary)] = expandVar(templateObj[key], dictionary); } else if (typeof templateObj[key] === 'object') { res[expandVar(key, dictionary)] = expandObject(templateObj[key], dictionary); + } else { + //Added default else clause + logger.debug('expandObject() - Default else clause'); } }); } diff --git a/lib/perseo.js b/lib/perseo.js index 12ad37b4..8def0955 100644 --- a/lib/perseo.js +++ b/lib/perseo.js @@ -48,7 +48,10 @@ var domain = require('domain'), serviceMiddleware = require('./middleware/service'), errorMiddleware = require('./middleware/error'), myutils = require('./myutils'), - d = domain.create(); + d = domain.create(), + swaggerJsdoc = require('swagger-jsdoc'), + swaggerUi = require('swagger-ui-express'), + pjson = require('../package.json'); function start(callbackStart) { var context = { op: 'start', comp: constants.COMPONENT_NAME }; @@ -98,6 +101,22 @@ function start(callbackStart) { versionRoutes.AddTo(app); metricsRoutes.AddTo(app); + const options = { + definition: { + swagger: '2.0', // Specification (optional, defaults to swagger: '2.0') + info: { + title: pjson.name, // Title (required) + version: pjson.version // Version (required) + } + }, + // Path to the API docs + apis: ['./lib/routes/*.js'] + }; + + const specs = swaggerJsdoc(options); + + app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs)); + async.series( [ function(callback) { diff --git a/lib/routes/noticesController.js b/lib/routes/noticesController.js index 18d48d53..90724d11 100644 --- a/lib/routes/noticesController.js +++ b/lib/routes/noticesController.js @@ -47,6 +47,75 @@ function PostNotice(req, resp) { } function AddTo(app) { + /** + * @swagger + * + * /notices: + * post: + * tags: + * - Notices + * description: Send an event/notification to perseo + * produces: + * - application/json + * parameters: + * - name: fiware-service + * description: Fiware service. + * in: header + * required: true + * type: string + * - name: fiware-servicepath + * description: Fiware service path. + * in: header + * required: true + * type: string + * - name: body + * in: body + * schema: + * type: object + * properties: + * subscriptionId: + * type: string + * description: Subscription Id + * example: + * - 5c7e63939859f248791984e0 + * data: + * type: object + * properties: + * id: + * type: string + * description: Entity Id + * example: + * - urn:ngsi-ld:Door:001 + * type: + * type: string + * description: Entity type + * example: + * - Door + * state: + * type: object + * properties: + * type: + * type: string + * description: State type + * example: + * - Text + * value: + * type: string + * description: State value + * example: + * - OPEN + * metadata: + * type: object + * description: State metadata + * example: + * - {"TimeInstant":{"type":"ISO8601", "value":"2019-03-05T10:49:16.402Z"}} + * required: + * - subscriptionId + * - data + * responses: + * 200: + * description: rules + */ app.post(config.endpoint.noticesPath, PostNotice); } diff --git a/lib/routes/rulesController.js b/lib/routes/rulesController.js index 9b0ddd60..bbaf0e48 100644 --- a/lib/routes/rulesController.js +++ b/lib/routes/rulesController.js @@ -88,9 +88,165 @@ function DelRules(req, resp) { } function AddTo(app) { + /** + * @swagger + * + * /rules: + * get: + * tags: + * - Rules + * description: Get all rules + * produces: + * - application/json + * parameters: + * - name: fiware-service + * description: Fiware service. + * in: header + * required: true + * type: string + * - name: fiware-servicepath + * description: Fiware service path. + * in: header + * required: true + * type: string * + * responses: + * 200: + * description: rules + */ app.get(config.endpoint.rulesPath, GetAllRules); + /** + * @swagger + * + * /rules/:id: + * get: + * tags: + * - Rules + * description: Get rule by id + * produces: + * - application/json + * parameters: + * - name: fiware-service + * description: Fiware service. + * in: header + * required: true + * type: string + * - name: fiware-servicepath + * description: Fiware service path. + * in: header + * required: true + * type: string + * - name: id + * description: Name of the rule. + * in: path + * required: true + * type: string + * responses: + * 200: + * description: rules + */ app.get(config.endpoint.rulesPath + '/:id', GetRules); + /** + * @swagger + * + * /rules: + * post: + * tags: + * - Rules + * description: Add rule + * produces: + * - application/json + * parameters: + * - name: fiware-service + * description: Fiware service. + * in: header + * required: true + * type: string + * - name: fiware-servicepath + * description: Fiware service path. + * in: header + * required: true + * type: string + * - name: body + * in: body + * schema: + * type: object + * properties: + * name: + * type: string + * description: Rule's name + * example: + * - foo + * text: + * type: string + * description: EPL sentence + * example: + * - select * + * action: + * type: object + * properties: + * type: + * type: string + * description: Action type + * example: + * - update + * parameters: + * type: object + * properties: + * name: + * type: string + * description: Parameter name + * example: + * - foo + * value: + * type: string + * description: Parameter value + * example: + * - true + * type: + * type: string + * description: Parameter type + * example: + * - boolean + * required: + * - name + * - text + * - action + * responses: + * 200: + * description: rules + */ app.post(config.endpoint.rulesPath, PostRules); + + /** + * @swagger + * + * /rules/:id: + * delete: + * tags: + * - Rules + * description: Delete rule by id + * produces: + * - application/json + * parameters: + * - name: fiware-service + * description: Fiware service. + * in: header + * required: true + * type: string + * - name: fiware-servicepath + * description: Fiware service path. + * in: header + * required: true + * type: string + * - name: id + * description: Rule id. + * in: path + * required: true + * type: string + * responses: + * 200: + * description: rules + */ app.delete(config.endpoint.rulesPath + '/:id', DelRules); } diff --git a/lib/routes/versionController.js b/lib/routes/versionController.js index 2e794e0a..e4b7653a 100644 --- a/lib/routes/versionController.js +++ b/lib/routes/versionController.js @@ -64,8 +64,81 @@ function getLogLevel(req, resp) { myutils.respondWOMetrics(resp, null, { level: currentLevel }, /* withCount */ false, /* raw */ true); } function AddTo(app) { + /** + * @swagger + * + * /version: + * get: + * tags: + * - Version + * description: Get version + * produces: + * - application/json + * parameters: + * + * responses: + * 200: + * description: version + */ app.get(config.endpoint.versionPath, version); + /** + * @swagger + * + * /admin/log: + * put: + * tags: + * - Log + * description: Send an event/notification to perseo + * produces: + * - application/json + * parameters: + * - name: fiware-service + * description: Fiware service. + * in: header + * required: true + * type: string + * - name: fiware-servicepath + * description: Fiware service path. + * in: header + * required: true + * type: string + * - name: level + * description: Log level + * in: query + * required: true + * type: string + * example: + * - INFO + * responses: + * 200: + * description: logLevel + */ app.put(config.endpoint.logPath, changeLogLevel); + /** + * @swagger + * + * /admin/log: + * get: + * tags: + * - Log + * description: Get Log Level + * produces: + * - application/json + * parameters: + * - name: fiware-service + * description: Fiware service. + * in: header + * required: true + * type: string + * - name: fiware-servicepath + * description: Fiware service path. + * in: header + * required: true + * type: string + * responses: + * 200: + * description: logLevel + */ app.get(config.endpoint.logPath, getLogLevel); } diff --git a/lib/routes/visualRulesController.js b/lib/routes/visualRulesController.js index ee4bd3a3..d7cb5974 100644 --- a/lib/routes/visualRulesController.js +++ b/lib/routes/visualRulesController.js @@ -127,7 +127,6 @@ function PutVR(req, resp) { return myutils.respond(resp, err, data); } if (data && data.name) { - //resp.location(req.url.replace(new RegExp(req.params.id), '')+encodeURIComponent(data.name)); resp.location(req.url.substr(0, req.url.lastIndexOf('/')) + '/' + encodeURIComponent(data.name)); } resp.status(200); diff --git a/package.json b/package.json index a678c753..a00652b7 100644 --- a/package.json +++ b/package.json @@ -65,10 +65,12 @@ "nodemailer": "~1.11.0", "nodemailer-smtp-transport": "~0.1.13", "request": "2.88.0", + "smpp": "0.3.1", + "swagger-jsdoc": "~3.4.0", + "swagger-ui-express": "~4.1.1", "twitter": "~1.7.1", "utm-converter": "~0.1.1", - "uuid": "~1.4.2", - "smpp": "0.3.1" + "uuid": "~1.4.2" }, "husky": { "hooks": { diff --git a/test/component/metrics/metrics_actions_test.js b/test/component/metrics/metrics_actions_test.js index 6bc64dfb..66270da8 100644 --- a/test/component/metrics/metrics_actions_test.js +++ b/test/component/metrics/metrics_actions_test.js @@ -106,7 +106,7 @@ describe('Metrics', function() { return callback(); }, 50); }); - }, + } ], done ); @@ -149,7 +149,7 @@ describe('Metrics', function() { return callback(); }, 50); }); - }, + } ], done ); @@ -191,7 +191,7 @@ describe('Metrics', function() { return callback(); }, 50); }); - }, + } ], done ); @@ -226,13 +226,13 @@ describe('Metrics', function() { should.equal(m.services.unknownt.sum.actionEntityUpdate, 1); should.equal(m.services.unknownt.sum.okActionEntityUpdate, 0); - should.equal(m.services.unknownt.sum.failedActionEntityUpdate, 1); + should.equal(m.services.unknownt.sum.failedActionEntityUpdate, 0); should.equal(m.services.unknownt.sum.outgoingTransactions, 1); - should.equal(m.services.unknownt.sum.outgoingTransactionsErrors, 1); + should.equal(m.services.unknownt.sum.outgoingTransactionsErrors, 0); return callback(); }, 150); }); - }, + } ], done ); @@ -274,7 +274,7 @@ describe('Metrics', function() { return callback(); }, 50); }); - }, + } ], done ); @@ -316,7 +316,7 @@ describe('Metrics', function() { return callback(); }, 50); }); - }, + } ], done ); @@ -364,7 +364,7 @@ describe('Metrics', function() { return callback(); }, 50); }); - }, + } ], done );