openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
459 lines (421 loc) • 16 kB
JavaScript
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