UNPKG

openhim-core

Version:

The OpenHIM core application that provides logging and routing of http requests

459 lines (421 loc) 16 kB
var Alert, Channel, Channels, ContactGroup, Event, Q, User, _, afterSendAlert, alertingTask, authorisation, calcDateFromForUser, config, contact, countTotalTransactionsForChannel, findGroup, findOneAlert, findTransactions, findTransactionsMatchingCondition, findTransactionsMatchingStatus, findTransactionsMaxRetried, getAllChannels, getTransactionsForAlert, logger, maxRetriesTemplate, moment, sendAlert, sendAlerts, setupAgenda, statusTemplate, trxURL, userAlreadyReceivedAlert, utils; config = require("./config/config"); config.alerts = config.get('alerts'); logger = require("winston"); contact = require('./contact'); moment = require('moment'); Q = require('q'); Channels = require('./model/channels'); Channel = Channels.Channel; Event = require('./model/events').Event; ContactGroup = require('./model/contactGroups').ContactGroup; Alert = require('./model/alerts').Alert; User = require('./model/users').User; authorisation = require('./middleware/authorisation'); utils = require('./utils'); _ = require('lodash'); trxURL = function(trx) { return config.alerts.consoleURL + "/#/transactions/" + trx.transactionID; }; statusTemplate = function(transactions, channel, alert) { return { plain: function() { return "OpenHIM Transactions Alert\n\nThe following transaction(s) have completed with status " + alert.status + " on the OpenHIM instance running on " + config.alerts.himInstance + ":\nChannel - " + channel.name + "\n" + ((transactions.map(function(trx) { return trxURL(trx); })).join('\n')) + "\n"; }, html: function() { var text; text = "<html>\n <head></head>\n <body>\n <h1>OpenHIM Transactions Alert</h1>\n <div>\n <p>The following transaction(s) have completed with status <b>" + alert.status + "</b> on the OpenHIM instance running on <b>" + config.alerts.himInstance + "</b>:</p>\n <table>\n <tr><td>Channel - <b>" + channel.name + "</b></td></td>\n"; text += (transactions.map(function(trx) { return " <tr><td><a href='" + (trxURL(trx)) + "'>" + (trxURL(trx)) + "</a></td></tr>"; })).join('\n'); text += '\n'; return text += " </table>\n </div>\n </body>\n</html>"; }, sms: function() { var text; text = "Alert - "; if (transactions.length > 1) { text += transactions.length + " transactions have"; } else if (transactions.length === 1) { text += "1 transaction has"; } else { text += "no transactions have"; } return text += " completed with status " + alert.status + " on the OpenHIM running on " + config.alerts.himInstance + " (" + channel.name + ")"; } }; }; maxRetriesTemplate = function(transactions, channel, alert) { return { plain: function() { return "OpenHIM Transactions Alert - " + config.alerts.himInstance + "\n\nThe following transaction(s) have been retried " + channel.autoRetryMaxAttempts + " times, but are still failing:\n\nChannel - " + channel.name + "\n" + ((transactions.map(function(trx) { return trxURL(trx); })).join('\n')) + "\n\nPlease note that they will not be retried any further by the OpenHIM automatically."; }, html: function() { var text; text = "<html>\n <head></head>\n <body>\n <h1>OpenHIM Transactions Alert - " + config.alerts.himInstance + "</h1>\n <div>\n <p>The following transaction(s) have been retried <b>" + channel.autoRetryMaxAttempts + "</b> times, but are still failing:</p>\n <table>\n <tr><td>Channel - <b>" + channel.name + "</b></td></td>\n"; text += (transactions.map(function(trx) { return " <tr><td><a href='" + (trxURL(trx)) + "'>" + (trxURL(trx)) + "</a></td></tr>"; })).join('\n'); text += '\n'; return text += " </table>\n <p>Please note that they will not be retried any further by the OpenHIM automatically.</p>\n </div>\n </body>\n</html>"; }, sms: function() { var text; text = "Alert - "; if (transactions.length > 1) { text += transactions.length + " transactions have"; } else if (transactions.length === 1) { text += "1 transaction has"; } return text += " been retried " + channel.autoRetryMaxAttempts + " times but are still failing on the OpenHIM on " + config.alerts.himInstance + " (" + channel.name + ")"; } }; }; getAllChannels = function(callback) { return Channel.find({}, callback); }; findGroup = function(groupID, callback) { return ContactGroup.findOne({ _id: groupID }, callback); }; findTransactions = function(channel, dateFrom, status, callback) { return Event.find({ created: { $gte: dateFrom }, channelID: channel._id, event: 'end', status: status }, { 'transactionID': 'transactionID' }).hint({ created: 1 }).exec(callback); }; countTotalTransactionsForChannel = function(channel, dateFrom, callback) { return Event.count({ created: { $gte: dateFrom }, channelID: channel._id, event: 'end' }, callback); }; findOneAlert = function(channel, alert, dateFrom, user, alertStatus, callback) { var criteria; criteria = { timestamp: { "$gte": dateFrom }, channelID: channel._id, condition: alert.condition, status: alert.condition === 'auto-retry-max-attempted' ? '500' : alert.status, alertStatus: alertStatus }; if (user) { criteria.user = user; } return Alert.findOne(criteria).exec(callback); }; findTransactionsMatchingCondition = function(channel, alert, dateFrom, callback) { if (!alert.condition || alert.condition === 'status') { return findTransactionsMatchingStatus(channel, alert, dateFrom, callback); } else if (alert.condition === 'auto-retry-max-attempted') { return findTransactionsMaxRetried(channel, alert, dateFrom, callback); } else { return callback(new Error("Unsupported condition '" + alert.condition + "'")); } }; findTransactionsMatchingStatus = function(channel, alert, dateFrom, callback) { var dateToCheck, pat, statusMatch; pat = /\dxx/.exec(alert.status); if (pat) { statusMatch = { "$gte": alert.status[0] * 100, "$lt": alert.status[0] * 100 + 100 }; } else { statusMatch = alert.status; } dateToCheck = dateFrom; if (alert.failureRate != null) { dateToCheck = moment().subtract(1, 'hours').toDate(); } return findTransactions(channel, dateToCheck, statusMatch, function(err, results) { var _countStart; if (!err && (results != null) && (alert.failureRate != null)) { _countStart = new Date(); return countTotalTransactionsForChannel(channel, dateToCheck, function(err, count) { var failureRatio; logger.debug(".countTotalTransactionsForChannel: " + (new Date() - _countStart) + " ms"); if (err) { return callback(err, null); } failureRatio = results.length / count * 100.0; if (failureRatio >= alert.failureRate) { return findOneAlert(channel, alert, dateToCheck, null, 'Completed', function(err, userAlert) { if (err) { return callback(err, null); } if (userAlert != null) { return callback(err, []); } else { return callback(err, utils.uniqArray(results)); } }); } else { return callback(err, []); } }); } else { return callback(err, results); } }); }; findTransactionsMaxRetried = function(channel, alert, dateFrom, callback) { return Event.find({ created: { $gte: dateFrom }, channelID: channel._id, event: 'end', status: 500, autoRetryAttempt: channel.autoRetryMaxAttempts }, { 'transactionID': 'transactionID' }).hint({ created: 1 }).exec(function(err, transactions) { if (err) { return callback(err); } return callback(null, _.uniqWith(transactions, function(a, b) { return a.transactionID.equals(b.transactionID); })); }); }; calcDateFromForUser = function(user) { var dateFrom; if (user.maxAlerts === '1 per hour') { return dateFrom = moment().subtract(1, 'hours').toDate(); } else if (user.maxAlerts === '1 per day') { return dateFrom = moment().startOf('day').toDate(); } else { return null; } }; userAlreadyReceivedAlert = function(channel, alert, user, callback) { var dateFrom; if (!user.maxAlerts || user.maxAlerts === 'no max') { return callback(null, false); } else { dateFrom = calcDateFromForUser(user); if (!dateFrom) { return callback("Unsupported option 'maxAlerts=" + user.maxAlerts + "'"); } return findOneAlert(channel, alert, dateFrom, user.user, 'Completed', function(err, userAlert) { return callback(err != null ? err : null, userAlert ? true : false); }); } }; getTransactionsForAlert = function(channel, alert, user, transactions, callback) { var dateFrom; if (!user.maxAlerts || user.maxAlerts === 'no max') { return callback(null, transactions); } else { dateFrom = calcDateFromForUser(user); if (!dateFrom) { return callback("Unsupported option 'maxAlerts=" + user.maxAlerts + "'"); } return findTransactionsMatchingCondition(channel, alert, dateFrom, callback); } }; sendAlert = function(channel, alert, user, transactions, contactHandler, done) { return User.findOne({ email: user.user }, function(err, dbUser) { if (err) { return done(err); } if (!dbUser) { return done("Cannot send alert: Unknown user '" + user.user + "'"); } return userAlreadyReceivedAlert(channel, alert, user, function(err, received) { if (err) { return done(err, true); } if (received) { return done(null, true); } logger.info("Sending alert for user '" + user.user + "' using method '" + user.method + "'"); return getTransactionsForAlert(channel, alert, user, transactions, function(err, transactionsForAlert) { var htmlMsg, plainMsg, smsMsg, template; template = statusTemplate(transactionsForAlert, channel, alert); if (alert.condition === 'auto-retry-max-attempted') { template = maxRetriesTemplate(transactionsForAlert, channel, alert); } if (user.method === 'email') { plainMsg = template.plain(); htmlMsg = template.html(); return contactHandler('email', user.user, 'OpenHIM Alert', plainMsg, htmlMsg, done); } else if (user.method === 'sms') { if (!dbUser.msisdn) { return done("Cannot send alert: MSISDN not specified for user '" + user.user + "'"); } smsMsg = template.sms(); return contactHandler('sms', dbUser.msisdn, 'OpenHIM Alert', smsMsg, null, done); } else { return done("Unknown method '" + user.method + "' specified for user '" + user.user + "'"); } }); }); }); }; afterSendAlert = function(err, channel, alert, user, transactions, skipSave, done) { if (err) { logger.error(err); } if (!skipSave) { alert = new Alert({ user: user.user, method: user.method, channelID: channel._id, condition: alert.condition, status: alert.condition === 'auto-retry-max-attempted' ? '500' : alert.status, alertStatus: err ? 'Failed' : 'Completed' }); return alert.save(function(err) { if (err) { logger.error(err); } return done(); }); } else { return done(); } }; sendAlerts = function(channel, alert, transactions, contactHandler, done) { var _alertStart, fn, group, groupDefer, i, j, len, len1, promises, ref, ref1, user; promises = []; _alertStart = new Date(); if (alert.groups) { ref = alert.groups; for (i = 0, len = ref.length; i < len; i++) { group = ref[i]; groupDefer = Q.defer(); findGroup(group, function(err, result) { var fn, groupUserPromises, j, len1, ref1, user; if (err) { logger.error(err); return groupDefer.resolve(); } else { groupUserPromises = []; ref1 = result.users; fn = function(user) { var groupUserDefer; groupUserDefer = Q.defer(); sendAlert(channel, alert, user, transactions, contactHandler, function(err, skipSave) { return afterSendAlert(err, channel, alert, user, transactions, skipSave, function() { return groupUserDefer.resolve(); }); }); return groupUserPromises.push(groupUserDefer.promise); }; for (j = 0, len1 = ref1.length; j < len1; j++) { user = ref1[j]; fn(user); } return (Q.all(groupUserPromises)).then(function() { return groupDefer.resolve(); }); } }); promises.push(groupDefer.promise); } } if (alert.users) { ref1 = alert.users; fn = function(user) { var userDefer; userDefer = Q.defer(); sendAlert(channel, alert, user, transactions, contactHandler, function(err, skipSave) { return afterSendAlert(err, channel, alert, user, transactions, skipSave, function() { return userDefer.resolve(); }); }); return promises.push(userDefer.promise); }; for (j = 0, len1 = ref1.length; j < len1; j++) { user = ref1[j]; fn(user); } } return (Q.all(promises)).then(function() { logger.debug(".sendAlerts: " + (new Date() - _alertStart) + " ms"); return done(); }); }; alertingTask = function(job, contactHandler, done) { var _taskStart, lastAlertDate, ref; if (!job.attrs.data) { job.attrs.data = {}; } lastAlertDate = (ref = job.attrs.data.lastAlertDate) != null ? ref : new Date(); _taskStart = new Date(); return getAllChannels(function(err, results) { var alert, channel, fn, i, j, len, len1, promises, ref1; promises = []; for (i = 0, len = results.length; i < len; i++) { channel = results[i]; if (Channels.isChannelEnabled(channel)) { ref1 = channel.alerts; fn = function(channel, alert) { var _findStart, deferred; deferred = Q.defer(); _findStart = new Date(); findTransactionsMatchingCondition(channel, alert, lastAlertDate, function(err, results) { logger.debug(".findTransactionsMatchingStatus: " + (new Date() - _findStart) + " ms"); if (err) { logger.error(err); return deferred.resolve(); } else if ((results != null) && results.length > 0) { return sendAlerts(channel, alert, results, contactHandler, function() { return deferred.resolve(); }); } else { return deferred.resolve(); } }); return promises.push(deferred.promise); }; for (j = 0, len1 = ref1.length; j < len1; j++) { alert = ref1[j]; fn(channel, alert); } } } return (Q.all(promises)).then(function() { job.attrs.data.lastAlertDate = new Date(); logger.debug("Alerting task total time: " + (new Date() - _taskStart) + " ms"); return done(); }); }); }; setupAgenda = function(agenda) { agenda.define('generate transaction alerts', function(job, done) { return alertingTask(job, contact.contactUser, done); }); return agenda.every(config.alerts.pollPeriodMinutes + " minutes", 'generate transaction alerts'); }; exports.setupAgenda = setupAgenda; if (process.env.NODE_ENV === "test") { exports.findTransactionsMatchingStatus = findTransactionsMatchingStatus; exports.findTransactionsMaxRetried = findTransactionsMaxRetried; exports.alertingTask = alertingTask; } //# sourceMappingURL=alerts.js.map