openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
340 lines (275 loc) • 12.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.setupAgenda = setupAgenda;
var _handlebars = _interopRequireDefault(require("handlebars"));
var _fs = _interopRequireDefault(require("fs"));
var _winston = _interopRequireDefault(require("winston"));
var _moment = _interopRequireDefault(require("moment"));
var authorisation = _interopRequireWildcard(require("./api/authorisation"));
var _config = require("./config");
var contact = _interopRequireWildcard(require("./contact"));
var metrics = _interopRequireWildcard(require("./metrics"));
var _users = require("./model/users");
var utils = _interopRequireWildcard(require("./utils"));
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
_config.config.reports = _config.config.get('reports');
const utcOffset = _config.config.reports.utcOffset || 0;
const dateTimeFormat = 'dddd Do MMMM YYYY h:mm:ss A Z'; // Function Sends the reports
function sendReports(job, flag, done) {
let fetchUsers;
let reportPeriodStart;
let reportPeriodEnd;
const reportMap = {};
const channelReportMap = {};
const channelMap = {};
if (flag === 'dailyReport') {
reportPeriodStart = (0, _moment.default)().utcOffset(utcOffset).startOf('day').subtract(1, 'day');
reportPeriodEnd = (0, _moment.default)().utcOffset(utcOffset).endOf('day').subtract(1, 'day');
} else {
reportPeriodStart = (0, _moment.default)().utcOffset(utcOffset).startOf('isoWeek').subtract(1, 'weeks');
reportPeriodEnd = (0, _moment.default)().utcOffset(utcOffset).endOf('isoWeek').subtract(1, 'weeks');
} // Select the right subscribers for the report
if (flag === 'dailyReport') {
fetchUsers = fetchDailySubscribers;
}
if (flag === 'weeklyReport') {
fetchUsers = fetchWeeklySubscribers;
}
return fetchUsers((err, users) => {
if (err) {
return done(err);
}
const usersArray = [];
const promises = Array.from(users).map((user, userIndex) => {
return authorisation.getUserViewableChannels(user).then(channels => {
usersArray[userIndex] = user;
usersArray[userIndex].allowedChannels = channels;
for (const channel of Array.from(usersArray[userIndex].allowedChannels)) {
channelMap[channel._id] = {
user,
channel
};
}
});
}); // Loop through the enriched user array
return Promise.all(promises).then(() => {
// Pre-Fetch report data into Channel Map
const innerPromises = Object.entries(channelMap).map(([key, obj]) => {
return new Promise((resolve, reject) => {
fetchChannelReport(obj.channel, obj.user, flag, reportPeriodStart, reportPeriodEnd, (err, item) => {
if (err) {
return reject(err);
}
channelReportMap[key] = item;
return resolve();
});
});
});
return Promise.all(innerPromises).then(() => {
for (const user of Array.from(usersArray)) {
const userKey = user.email;
for (const channel of Array.from(user.allowedChannels)) {
if (reportMap[userKey]) {// Do nothing since object already exists
} else {
// Create the object
reportMap[userKey] = {
email: user.email,
data: []
};
} // If report has been fetched get it from the map
if (channelReportMap[channel._id]) {
const data = channelReportMap[channel._id]; // add report - always add if the channel is enabled (treating undefined status as enabled), otherwise only if there is data
if (data.channel.status == null || data.channel.status === 'enabled' || data.data.length !== 0) {
reportMap[userKey].data.push(data);
}
} else {
return _winston.default.error('should never be here since channels have been pre-fetched');
}
}
} // Iterate over reports and send the emails
for (const key in reportMap) {
const report = reportMap[key];
if (flag === 'dailyReport') {
report.type = 'Daily';
report.isDaily = true;
} else {
report.type = 'Weekly';
report.isDaily = false;
}
report.instance = _config.config.alerts.himInstance;
report.consoleURL = _config.config.alerts.consoleURL;
report.from = reportPeriodStart.clone().format(dateTimeFormat);
report.to = reportPeriodEnd.clone().format(dateTimeFormat);
report.reportStartDate = reportPeriodStart.toISOString();
report.reportEndDate = reportPeriodEnd.toISOString();
try {
for (let i = 0; i < report.data.length; i++) {
const data = report.data[i];
const colorGrey = 'color: grey;';
let rowColor = 'background-color: #d9ead3';
if (i % 2) {
rowColor = 'background-color: #b6d7a8;';
}
const totals = calculateTotalsFromGrouping(data);
for (const key in totals) {
report.data[i][key] = totals[key];
}
report.data[i].totalStyle = report.data[i].total > 0 ? '' : colorGrey;
report.data[i].avgRespStyle = report.data[i].avgResp > 0 ? '' : colorGrey;
report.data[i].failedStyle = report.data[i].failed > 0 ? 'color: red;' : colorGrey;
report.data[i].successfulStyle = report.data[i].successful > 0 ? '' : colorGrey;
report.data[i].processingStyle = report.data[i].processing > 0 ? '' : colorGrey;
report.data[i].completedStyle = report.data[i].completed > 0 ? 'color: orange;' : colorGrey;
report.data[i].completedWErrorsStyle = report.data[i].completedWErrors > 0 ? 'color: orangered;' : colorGrey;
report.data[i].rowColor = rowColor;
}
sendUserEmail(report);
} catch (err) {
_winston.default.error(err);
job.fail(`Failed to send report reason: ${err}`);
}
}
return done();
}).catch(done);
});
});
}
function calculateTotalsFromGrouping(data) {
const reduced = data.data.reduce((totals, metric, index) => ({
requests: totals.requests + metric.requests,
responseTime: totals.responseTime + metric.responseTime,
successful: totals.successful + metric.successful,
failed: totals.failed + metric.failed,
processing: totals.processing + metric.processing,
completed: totals.completed + metric.completed,
completedWithErrors: totals.completedWithErrors + metric.completedWithErrors
}), {
requests: 0,
responseTime: 0,
successful: 0,
failed: 0,
processing: 0,
completed: 0,
completedWithErrors: 0
});
return {
total: reduced.requests,
avgResp: Math.round(calculateAverage(reduced.responseTime, reduced.requests) / 1000),
failed: reduced.failed,
successful: reduced.successful,
processing: reduced.processing,
completed: reduced.completed,
completedWErrors: reduced.completedWithErrors
};
}
function calculateAverage(total, count) {
if (count === 0) {
return 0;
}
return total / count;
}
function sendUserEmail(report) {
report.date = (0, _moment.default)().utcOffset(utcOffset).toString();
return renderTemplate('report/html.handlebars', report, reportHtml => contact.contactUser('email', report.email, `${report.type} report for: ${report.instance}`, plainTemplate(report), reportHtml, err => afterEmail(err, report.type, report.email)));
}
function fetchChannelReport(channel, user, flag, from, to, callback) {
let period;
if (flag === 'dailyReport') {
period = 'day';
} else {
period = 'week';
}
const item = {};
_winston.default.info(`fetching ${flag} for #${channel.name} ${user.email} ${channel._id}`);
const filters = {
startDate: from.toDate(),
endDate: to.toDate(),
timeSeries: period,
channels: [channel._id]
};
return metrics.calculateMetrics(filters).then(data => {
item.channel = channel;
item.data = data;
return callback(null, item);
}).catch(err => {
_winston.default.error('Error calculating metrics: ', err);
return callback(err);
});
}
const fetchDailySubscribers = callback => {
_users.UserModel.find({
dailyReport: true
}, callback);
};
const fetchWeeklySubscribers = callback => {
_users.UserModel.find({
weeklyReport: true
}, callback);
};
function plainTemplate(report) {
let text = `Generated on: ${!utcOffset ? (0, _moment.default)().format(dateTimeFormat) : (0, _moment.default)().utcOffset(utcOffset).format(dateTimeFormat)}`;
text += `\n\nReport period: ${report.from} to ${report.to}\n`;
for (const data of Array.from(report.data)) {
text += ` \r\n \r\n <---------- Start Channel ${data.channel.name} ---------------------------> \r\n \r\n \
Channel Name: ${data.channel.name} \r\n \
Channel total: ${(data.data[0] != null ? data.data[0].total : undefined) != null ? data.data[0].total : 0} transactions \r\n \
Ave response time: ${(data.data[0] != null ? data.data[0].avgResp : undefined) != null ? data.data[0].avgResp : 0} \r\n \
Failed: ${(data.data[0] != null ? data.data[0].failed : undefined) != null ? data.data[0].failed : 0} \r\n \
Successful: ${(data.data[0] != null ? data.data[0].successful : undefined) != null ? data.data[0].successful : 0} \r\n \
Processing: ${(data.data[0] != null ? data.data[0].processing : undefined) != null ? data.data[0].processing : 0} \r\n \
Completed: ${(data.data[0] != null ? data.data[0].completed : undefined) != null ? data.data[0].completed : 0} \r\n \
Completed with errors: ${(data.data[0] != null ? data.data[0].completedWErrors : undefined) != null ? data.data[0].completedWErrors : 0} \r\n \r\n \
<---------- End Channel -------------------------------------------------> \r\n \r\n \
\r\n \
\r\n\
`;
}
return text;
}
function renderTemplate(templateName, templateData, callback) {
const templateDir = `${_config.appRoot}/templates/${templateName}`;
_fs.default.readFile(templateDir, (err, data) => {
if (!err) {
// make the buffer into a string
const source = data.toString(); // call the render function
renderToString(source, templateData, callback);
} else {
// handle file read error
_winston.default.error('Error reading report template');
}
});
}
const renderToString = (source, data, callback) => {
const template = _handlebars.default.compile(source);
const htmlResult = template(data);
return callback(htmlResult.toString());
};
const afterEmail = (err, type, email) => {
if (err) {
return _winston.default.error(`Failed sending email: ${err}`);
}
_winston.default.info(`${type} report email sent to ${email}`);
};
function setupAgenda(agenda) {
agenda.define('send weekly channel metrics', (job, done) => sendReports(job, 'weeklyReport', done));
agenda.define('send daily channel metrics', (job, done) => sendReports(job, 'dailyReport', done));
agenda.every(_config.config.reports.weeklyReportAt, 'send weekly channel metrics', null, {
timezone: utils.serverTimezone()
});
return agenda.every(_config.config.reports.dailyReportAt, 'send daily channel metrics', null, {
timezone: utils.serverTimezone()
});
}
if (process.env.NODE_ENV === 'test') {
exports.sendReports = sendReports;
exports.fetchDailySubscribers = fetchDailySubscribers;
exports.fetchWeeklySubscribers = fetchWeeklySubscribers;
exports.fetchChannelReport = fetchChannelReport;
exports.sendUserEmail = sendUserEmail;
exports.calculateTotalsFromGrouping = calculateTotalsFromGrouping;
}
//# sourceMappingURL=reports.js.map