UNPKG

openhim-core

Version:

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

340 lines (275 loc) 12.8 kB
"use strict"; 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