diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 391e02146..e3bdfbb57 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -56,7 +56,7 @@ const calculateUptimeDuration = (checks) => { const latestCheck = new Date(checks[0].createdAt); let latestDownCheck = 0; - for (let i = checks.length; i <= 0; i--) { + for (let i = checks.length; i >= 0; i--) { if (checks[i].status === false) { latestDownCheck = new Date(checks[i].createdAt); break; @@ -143,6 +143,98 @@ const getIncidents = (checks) => { }, 0); }; +/** + * Get date range parameters + * @param {string} dateRange - 'day' | 'week' | 'month' + * @returns {Object} Start and end dates + */ +const getDateRange = (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)), + }; + return { + start: startDates[dateRange], + end: new Date(), + }; +}; + +/** + * Get checks for a monitor + * @param {string} monitorId - Monitor ID + * @param {Object} model - Check model to use + * @param {Object} dateRange - Date range parameters + * @param {number} sortOrder - Sort order (1 for ascending, -1 for descending) + * @returns {Promise} All checks and date-ranged checks + */ +const getMonitorChecks = async (monitorId, model, dateRange, sortOrder) => { + const [checksAll, checksForDateRange] = await Promise.all([ + model.find({ monitorId }).sort({ createdAt: sortOrder }), + model + .find({ + monitorId, + createdAt: { $gte: dateRange.start, $lte: dateRange.end }, + }) + .sort({ createdAt: sortOrder }), + ]); + return { checksAll, checksForDateRange }; +}; + +/** + * Process checks for display + * @param {Array} checks - Checks to process + * @param {number} numToDisplay - Number of checks to display + * @param {boolean} normalize - Whether to normalize the data + * @returns {Array} Processed checks + */ +const processChecksForDisplay = (checks, numToDisplay, normalize) => { + let processedChecks = checks; + if (numToDisplay && checks.length > numToDisplay) { + const n = Math.ceil(checks.length / numToDisplay); + processedChecks = checks.filter((_, index) => index % n === 0); + } + return normalize ? NormalizeData(processedChecks, 1, 100) : processedChecks; +}; + +/** + * Get time-grouped checks based on date range + * @param {Array} checks Array of check objects + * @param {string} dateRange 'day' | 'week' | 'month' + * @returns {Object} Grouped checks by time period + */ +const groupChecksByTime = (checks, dateRange) => { + return checks.reduce((acc, check) => { + const time = + dateRange === "day" + ? new Date(check.createdAt).setMinutes(0, 0, 0) + : new Date(check.createdAt).toISOString().split("T")[0]; + + if (!acc[time]) { + acc[time] = { time, checks: [] }; + } + acc[time].checks.push(check); + return acc; + }, {}); +}; + +/** + * Calculate aggregate stats for a group of checks + * @param {Object} group Group of checks + * @returns {Object} Stats for the group + */ +const calculateGroupStats = (group) => { + const totalChecks = group.checks.length; + return { + time: group.time, + uptimePercentage: getUptimePercentage(group.checks), + totalChecks, + totalIncidents: group.checks.filter((check) => !check.status).length, + avgResponseTime: + group.checks.reduce((sum, check) => sum + check.responseTime, 0) / totalChecks, + }; +}; + /** * Get stats by monitor ID * @async @@ -152,129 +244,48 @@ const getIncidents = (checks) => { * @throws {Error} */ const getMonitorStatsById = async (req) => { - 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)), - }; - const endDate = new Date(); try { - // Get monitor const { monitorId } = req.params; - let { limit, sortOrder, dateRange, numToDisplay, normalize } = req.query; + + // Get monitor, if we can't find it, abort with error const monitor = await Monitor.findById(monitorId); if (monitor === null || monitor === undefined) { throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)); } - // This effectively removes limit, returning all checks - if (limit === undefined) limit = 0; - // Default sort order is newest -> oldest - sortOrder = sortOrder === "asc" ? 1 : -1; - let model = CHECK_MODEL_LOOKUP[monitor.type]; + // Get query params + let { limit, sortOrder, dateRange, numToDisplay, normalize } = req.query; + const sort = sortOrder === "asc" ? 1 : -1; + // Get Checks for monitor in date range requested + const model = CHECK_MODEL_LOOKUP[monitor.type]; + const dates = getDateRange(dateRange); + const { checksAll, checksForDateRange } = await getMonitorChecks( + monitorId, + model, + dates, + sort + ); + + // Build monitor stats const monitorStats = { ...monitor.toObject(), + uptimeDuration: calculateUptimeDuration(checksAll), + lastChecked: getLastChecked(checksAll), + latestResponseTime: getLatestResponseTime(checksAll), + periodIncidents: getIncidents(checksForDateRange), + periodTotalChecks: checksForDateRange.length, + checks: processChecksForDisplay(checksForDateRange, numToDisplay, normalize), }; - // Build checks query - const checksQuery = { monitorId: monitor._id }; - - // Get all checks - const checksAll = await model.find(checksQuery).sort({ - createdAt: sortOrder, - }); - - const checksQueryForDateRange = { - ...checksQuery, - createdAt: { - $gte: startDates[dateRange], - $lte: endDate, - }, - }; - - const checksForDateRange = await model - .find(checksQueryForDateRange) - .sort({ createdAt: sortOrder }); - if (monitor.type === "http" || monitor.type === "ping") { // HTTP/PING Specific stats monitorStats.periodAvgResponseTime = getAverageResponseTime(checksForDateRange); monitorStats.periodUptime = getUptimePercentage(checksForDateRange); - - // Aggregate data - let groupedChecks; - // Group checks by hour if range is day - if (dateRange === "day") { - groupedChecks = checksForDateRange.reduce((acc, check) => { - const time = new Date(check.createdAt); - time.setMinutes(0, 0, 0); - if (!acc[time]) { - acc[time] = { time, checks: [] }; - } - acc[time].checks.push(check); - return acc; - }, {}); - } else { - groupedChecks = checksForDateRange.reduce((acc, check) => { - const time = new Date(check.createdAt).toISOString().split("T")[0]; // Extract the date part - if (!acc[time]) { - acc[time] = { time, checks: [] }; - } - acc[time].checks.push(check); - return acc; - }, {}); - } - - // Map grouped checks to stats - const aggregateData = Object.values(groupedChecks).map((group) => { - const totalChecks = group.checks.length; - const uptimePercentage = getUptimePercentage(group.checks); - const totalIncidents = group.checks.filter( - (check) => check.status === false - ).length; - const avgResponseTime = - group.checks.reduce((sum, check) => sum + check.responseTime, 0) / totalChecks; - - return { - time: group.time, - uptimePercentage, - totalChecks, - totalIncidents, - avgResponseTime, - }; - }); - monitorStats.aggregateData = aggregateData; + const groupedChecks = groupChecksByTime(checksForDateRange, dateRange); + monitorStats.aggregateData = Object.values(groupedChecks).map(calculateGroupStats); } - monitorStats.periodIncidents = getIncidents(checksForDateRange); - monitorStats.periodTotalChecks = checksForDateRange.length; - - // If more than numToDisplay checks, pick every nth check - - let nthChecks = checksForDateRange; - - if ( - numToDisplay !== undefined && - checksForDateRange && - checksForDateRange.length > numToDisplay - ) { - const n = Math.ceil(checksForDateRange.length / numToDisplay); - nthChecks = checksForDateRange.filter((_, index) => index % n === 0); - } - - // Normalize checks if requested - if (normalize !== undefined) { - const normailzedChecks = NormalizeData(nthChecks, 1, 100); - monitorStats.checks = normailzedChecks; - } else { - monitorStats.checks = nthChecks; - } - - monitorStats.uptimeDuration = calculateUptimeDuration(checksAll); - monitorStats.lastChecked = getLastChecked(checksAll); - monitorStats.latestResponseTime = getLatestResponseTime(checksAll); - return monitorStats; } catch (error) { error.service = SERVICE_NAME;