From bc0df325d1043fc4b2fa0caa414724045a5253e7 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 14:39:16 -0700 Subject: [PATCH] Add route, controller, db operations, and validation for aggregate monitor stats --- Client/src/Utils/NetworkService.js | 2 +- Server/controllers/monitorController.js | 42 +++++++++++- Server/db/mongo/MongoDB.js | 2 + Server/db/mongo/modules/monitorModule.js | 83 ++++++++++++++++++++++++ Server/routes/monitorRoute.js | 1 + Server/validation/joi.js | 9 +++ 6 files changed, 137 insertions(+), 2 deletions(-) diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 31a23b5b1..6dcde82bb 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -116,7 +116,7 @@ class NetworkService { * @param {string} [sortOrder] - The order in which to sort the retrieved statistics. * @param {number} [limit] - The maximum number of statistics to retrieve. * @param {string} [dateRange] - The date range for which to retrieve statistics. - * @param {number} [numToDisplay] - The number of statistics to display. + * @param {number} [numToDisplay] - The number of checks to display. * @param {boolean} [normalize] - Whether to normalize the retrieved statistics. * @returns {Promise} The response from the axios GET request. */ diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index f33ddcf10..0d831f5b6 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -6,12 +6,13 @@ const { editMonitorBodyValidation, getMonitorsByTeamIdQueryValidation, pauseMonitorParamValidation, + getMonitorAggregateStatsParamValidation, + getMonitorAggregateStatsQueryValidation, } = require("../validation/joi"); const sslChecker = require("ssl-checker"); const SERVICE_NAME = "monitorController"; const { errorMessages, successMessages } = require("../utils/messages"); -const { runInNewContext } = require("vm"); /** * Returns all monitors @@ -35,6 +36,44 @@ const getAllMonitors = async (req, res, next) => { } }; +/** + * Returns agregate stats for a monitor + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise} + * @throws {Error} + */ + +const getMonitorAggregateStats = async (req, res, next) => { + try { + await getMonitorAggregateStatsParamValidation.validateAsync(req.params); + await getMonitorAggregateStatsQueryValidation.validateAsync(req.query); + } catch (error) { + error.status = 422; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; + next(error); + return; + } + + try { + const { monitorId } = req.params; + const dateRange = req.query.dateRange; + const aggregateStats = await req.db.getMonitorAggregateStats( + monitorId, + dateRange + ); + return res.json({ + success: true, + msg: successMessages.MONTIOR_STATS_BY_ID, + data: aggregateStats, + }); + } catch (error) { + next(error); + } +}; + /** * Returns monitor stats for monitor with matching ID * @async @@ -376,6 +415,7 @@ const pauseMonitor = async (req, res, next) => { module.exports = { getAllMonitors, + getMonitorAggregateStats, getMonitorStatsById, getMonitorCertificate, getMonitorById, diff --git a/Server/db/mongo/MongoDB.js b/Server/db/mongo/MongoDB.js index 912bfb438..6f959f758 100644 --- a/Server/db/mongo/MongoDB.js +++ b/Server/db/mongo/MongoDB.js @@ -64,6 +64,7 @@ const { const { getAllMonitors, + getMonitorAggregateStats, getMonitorStatsById, getMonitorById, getMonitorsByTeamId, @@ -144,6 +145,7 @@ module.exports = { resetPassword, checkSuperadmin, getAllMonitors, + getMonitorAggregateStats, getMonitorStatsById, getMonitorById, getMonitorsByTeamId, diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 79e8917ea..0bd054000 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -143,6 +143,88 @@ const getStatusBarValues = (monitor, checks) => { } return statusBarValues.reverse(); }; + +/** + * Get aggregate monitor stats for charts + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise} + * @throws {Error} + */ +const getMonitorAggregateStats = async (monitorId, dateRange) => { + const startDates = { + day: new Date(new Date().setDate(new Date().getDate() - 1)), + + week: new Date(new Date().setDate(new Date().getDate() - 7)), + month: new Date(new Date().setMonth(new Date().getMonth() - 1)), + }; + + try { + const startDate = startDates[dateRange]; + if (!startDate) { + throw new Error("Invalid date range specified"); + } + const endDate = new Date(); + + const checks = await Check.find({ + monitorId: monitorId, + createdAt: { + $gte: startDate, + $lte: endDate, + }, + }); + + let groupedChecks; + // Group checks by hour if range is day + if (dateRange === "day") { + groupedChecks = checks.reduce((acc, check) => { + const hour = new Date(check.createdAt).getHours(); + if (!acc[hour]) { + acc[hour] = { hour, checks: [] }; + } + acc[hour].checks.push(check); + return acc; + }, {}); + } + + // Group checks by day if range is week or month + else { + groupedChecks = checks.reduce((acc, check) => { + const day = new Date(check.createdAt).toISOString().split("T")[0]; // Extract the date part + if (!acc[day]) { + acc[day] = { day, checks: [] }; + } + acc[day].checks.push(check); + return acc; + }, {}); + } + + // Map grouped checks to stats + stats = Object.values(groupedChecks).map((group) => { + const totalChecks = group.checks.length; + const totalIncidents = group.checks.filter( + (check) => check.status === false + ).length; + const avgResponseTime = + group.checks.reduce((sum, check) => sum + check.responseTime, 0) / + totalChecks; + + return { + hour: group.hour, + day: group.day, + totalChecks, + totalIncidents, + avgResponseTime: `${avgResponseTime.toFixed(2)}ms`, + }; + }); + + return stats; + } catch (error) { + throw error; + } +}; + /** * Get stats by monitor ID * @async @@ -452,6 +534,7 @@ const editMonitor = async (candidateId, candidateMonitor) => { module.exports = { getAllMonitors, + getMonitorAggregateStats, getMonitorStatsById, getMonitorById, getMonitorsByTeamId, diff --git a/Server/routes/monitorRoute.js b/Server/routes/monitorRoute.js index ee1c08488..7831f42f7 100644 --- a/Server/routes/monitorRoute.js +++ b/Server/routes/monitorRoute.js @@ -3,6 +3,7 @@ const monitorController = require("../controllers/monitorController"); const { isAllowed } = require("../middleware/isAllowed"); router.get("/", monitorController.getAllMonitors); +router.get("/aggregate/:monitorId", monitorController.getMonitorAggregateStats); router.get("/stats/:monitorId", monitorController.getMonitorStatsById); router.get("/certificate/:monitorId", monitorController.getMonitorCertificate); router.get("/:monitorId", monitorController.getMonitorById); diff --git a/Server/validation/joi.js b/Server/validation/joi.js index ccc8da9f6..629deddf5 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -201,6 +201,13 @@ const pauseMonitorParamValidation = joi.object({ monitorId: joi.string().required(), }); +const getMonitorAggregateStatsParamValidation = joi.object({ + monitorId: joi.string().required(), +}); +const getMonitorAggregateStatsQueryValidation = joi.object({ + dateRange: joi.string().valid("day", "week", "month"), +}); + //**************************************** // Alerts //**************************************** @@ -357,6 +364,8 @@ module.exports = { getMonitorByIdQueryValidation, getMonitorsByTeamIdValidation, getMonitorsByTeamIdQueryValidation, + getMonitorAggregateStatsParamValidation, + getMonitorAggregateStatsQueryValidation, editMonitorBodyValidation, pauseMonitorParamValidation, editUserParamValidation,