diff --git a/Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx b/Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx index a851a93ea..bfc99937a 100644 --- a/Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx +++ b/Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx @@ -4,7 +4,7 @@ import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts"; import PropTypes from "prop-types"; import CustomLabels from "./CustomLabels"; -const DownBarChart = memo(({ stats, type, onBarHover }) => { +const DownBarChart = memo(({ monitor, type, onBarHover }) => { const theme = useTheme(); const [chartHovered, setChartHovered] = useState(false); @@ -19,7 +19,7 @@ const DownBarChart = memo(({ stats, type, onBarHover }) => { { setChartHovered(true); onBarHover({ time: null, totalChecks: 0 }); @@ -40,8 +40,10 @@ const DownBarChart = memo(({ stats, type, onBarHover }) => { y={0} width="100%" height="100%" - firstDataPoint={stats.downChecks?.[0] ?? {}} - lastDataPoint={stats.downChecks?.[stats.downChecks.length - 1] ?? {}} + firstDataPoint={monitor.groupedDownChecks?.[0] ?? {}} + lastDataPoint={ + monitor.groupedDownChecks?.[monitor.groupedDownChecks.length - 1] ?? {} + } type={type} /> } @@ -51,7 +53,7 @@ const DownBarChart = memo(({ stats, type, onBarHover }) => { maxBarSize={7} background={{ fill: "transparent" }} > - {stats.downChecks.map((entry, index) => ( + {monitor.groupedDownChecks.map((entry, index) => ( { DownBarChart.displayName = "DownBarChart"; DownBarChart.propTypes = { - stats: PropTypes.shape({ - downChecks: PropTypes.arrayOf(PropTypes.object), - downChecksAggregate: PropTypes.object, + monitor: PropTypes.shape({ + groupedDownChecks: PropTypes.arrayOf(PropTypes.object), }), type: PropTypes.string, onBarHover: PropTypes.func, diff --git a/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx b/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx index 08c8d778e..091b9fccd 100644 --- a/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx +++ b/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx @@ -4,7 +4,7 @@ import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts"; import PropTypes from "prop-types"; import CustomLabels from "./CustomLabels"; -const UpBarChart = memo(({ stats, type, onBarHover }) => { +const UpBarChart = memo(({ monitor, type, onBarHover }) => { const theme = useTheme(); const [chartHovered, setChartHovered] = useState(false); const [hoveredBarIndex, setHoveredBarIndex] = useState(null); @@ -26,7 +26,7 @@ const UpBarChart = memo(({ stats, type, onBarHover }) => { { setChartHovered(true); onBarHover({ time: null, totalChecks: 0, avgResponseTime: 0 }); @@ -47,8 +47,8 @@ const UpBarChart = memo(({ stats, type, onBarHover }) => { y={0} width="100%" height="100%" - firstDataPoint={stats.upChecks[0]} - lastDataPoint={stats.upChecks[stats.upChecks.length - 1]} + firstDataPoint={monitor.groupedUpChecks[0]} + lastDataPoint={monitor.groupedUpChecks[monitor.groupedUpChecks.length - 1]} type={type} /> } @@ -58,7 +58,7 @@ const UpBarChart = memo(({ stats, type, onBarHover }) => { maxBarSize={7} background={{ fill: "transparent" }} > - {stats.upChecks.map((entry, index) => { + {monitor.groupedUpChecks.map((entry, index) => { let { main, light } = getColorRange(entry.avgResponseTime); return ( { - {monitor?.stats?.latestResponseTime} + {monitor?.latestResponseTime} {"ms"} } @@ -308,7 +308,7 @@ const DetailsPage = () => { {hoveredUptimeData !== null ? hoveredUptimeData.totalChecks - : (monitor.stats?.upChecks?.reduce((count, checkGroup) => { + : (monitor?.groupedUpChecks?.reduce((count, checkGroup) => { return count + checkGroup.totalChecks; }, 0) ?? 0)} @@ -338,8 +338,8 @@ const DetailsPage = () => { {hoveredUptimeData !== null ? Math.floor(hoveredUptimeData?.avgResponseTime ?? 0) : Math.floor( - ((monitor?.stats?.upChecksAggregate?.totalChecks ?? 0) / - (monitor?.stats?.totalChecks ?? 1)) * + ((monitor?.upChecks?.totalChecks ?? 0) / + (monitor?.totalChecks ?? 1)) * 100 )} @@ -349,7 +349,7 @@ const DetailsPage = () => { @@ -366,7 +366,7 @@ const DetailsPage = () => { {hoveredIncidentsData !== null ? hoveredIncidentsData.totalChecks - : (monitor.stats?.downChecks?.reduce((count, checkGroup) => { + : (monitor?.groupedDownChecks?.reduce((count, checkGroup) => { return count + checkGroup.totalChecks; }, 0) ?? 0)} @@ -388,7 +388,7 @@ const DetailsPage = () => { )} @@ -400,9 +400,7 @@ const DetailsPage = () => { Average Response Time - + { Response Times diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 745aecf88..b191bd1bc 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -340,31 +340,21 @@ const getUptimeDetailsById = async (req) => { }; const dateString = formatLookup[dateRange]; - const monitorData = await Check.aggregate( + const results = await Check.aggregate( buildUptimeDetailsPipeline(monitor, dates, dateString) ); + const monitorData = results[0]; const normalizedGroupChecks = NormalizeDataUptimeDetails( - monitorData[0].groupChecks, + monitorData.groupedChecks, 10, 100 ); const monitorStats = { ...monitor.toObject(), - stats: { - avgResponseTime: monitorData[0].avgResponseTime, - totalChecks: monitorData[0].totalChecks, - timeSinceLastCheck: monitorData[0].timeSinceLastCheck, - timeSinceLastFalseCheck: monitorData[0].timeSinceLastFalseCheck, - latestResponseTime: monitorData[0].latestResponseTime, - groupChecks: normalizedGroupChecks, - groupAggregate: monitorData[0].groupAggregate, - upChecksAggregate: monitorData[0].upChecksAggregate, - upChecks: monitorData[0].upChecks, - downChecksAggregate: monitorData[0].downChecksAggregate, - downChecks: monitorData[0].downChecks, - }, + ...monitorData, + groupedChecks: normalizedGroupChecks, }; return monitorStats; diff --git a/Server/db/mongo/modules/monitorModuleQueries.js b/Server/db/mongo/modules/monitorModuleQueries.js index ccafbcbdc..cdbcd51d8 100644 --- a/Server/db/mongo/modules/monitorModuleQueries.js +++ b/Server/db/mongo/modules/monitorModuleQueries.js @@ -19,9 +19,6 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { avgResponseTime: { $avg: "$responseTime", }, - firstCheck: { - $first: "$$ROOT", - }, lastCheck: { $last: "$$ROOT", }, @@ -31,27 +28,52 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, ], - uptimeDuration: [ - { - $match: { - status: false, - }, - }, + uptimeStreak: [ { $sort: { - createdAt: 1, + createdAt: -1, }, }, { $group: { _id: null, - lastFalseCheck: { - $last: "$$ROOT", + checks: { $push: "$$ROOT" }, + }, + }, + { + $project: { + streak: { + $reduce: { + input: "$checks", + initialValue: { checks: [], foundFalse: false }, + in: { + $cond: [ + { + $and: [ + { $not: "$$value.foundFalse" }, // stop reducing if a false check has been found + { $eq: ["$$this.status", true] }, // continue reducing if current check true + ], + }, + // true case + { + checks: { $concatArrays: ["$$value.checks", ["$$this"]] }, + foundFalse: false, // Add the check to the streak + }, + // false case + { + checks: "$$value.checks", + foundFalse: true, // Mark that we found a false + }, + ], + }, + }, }, }, }, ], - groupChecks: [ + // For the response time chart, should return checks for date window + // Grouped by: {day: hour}, {week: day}, {month: day} + groupedChecks: [ { $match: { createdAt: { $gte: dates.start, $lte: dates.end }, @@ -79,7 +101,8 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, ], - groupAggregate: [ + // Average response time for the date window + groupAvgResponseTime: [ { $match: { createdAt: { $gte: dates.start, $lte: dates.end }, @@ -94,10 +117,12 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, ], - upChecksAggregate: [ + // All UpChecks for the date window + upChecks: [ { $match: { status: true, + createdAt: { $gte: dates.start, $lte: dates.end }, }, }, { @@ -112,7 +137,8 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, ], - upChecks: [ + // Up checks grouped by: {day: hour}, {week: day}, {month: day} + groupedUpChecks: [ { $match: { status: true, @@ -139,10 +165,12 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { $sort: { _id: 1 }, }, ], - downChecksAggregate: [ + // All down checks for the date window + downChecks: [ { $match: { status: false, + createdAt: { $gte: dates.start, $lte: dates.end }, }, }, { @@ -157,7 +185,8 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, ], - downChecks: [ + // Down checks grouped by: {day: hour}, {week: day}, {month: day} for the date window + groupedDownChecks: [ { $match: { status: false, @@ -188,6 +217,20 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, { $project: { + uptimeStreak: { + $cond: [ + { $eq: [{ $size: { $first: "$uptimeStreak.streak.checks" } }, 0] }, + 0, + { + $subtract: [ + new Date(), + { + $last: { $first: "$uptimeStreak.streak.checks.createdAt" }, + }, + ], + }, + ], + }, avgResponseTime: { $arrayElemAt: ["$aggregateData.avgResponseTime", 0], }, @@ -217,51 +260,18 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - timeSinceLastFalseCheck: { - $let: { - vars: { - lastFalseCheck: { - $arrayElemAt: ["$uptimeDuration.lastFalseCheck", 0], - }, - firstCheck: { - $arrayElemAt: ["$aggregateData.firstCheck", 0], - }, - }, - in: { - $cond: [ - { - $ifNull: ["$$lastFalseCheck", false], - }, - { - $subtract: [new Date(), "$$lastFalseCheck.createdAt"], - }, - { - $cond: [ - { - $ifNull: ["$$firstCheck", false], - }, - { - $subtract: [new Date(), "$$firstCheck.createdAt"], - }, - 0, - ], - }, - ], - }, - }, + groupedChecks: "$groupedChecks", + groupedAvgResponseTime: { + $arrayElemAt: ["$groupAvgResponseTime", 0], }, - groupChecks: "$groupChecks", - groupAggregate: { - $arrayElemAt: ["$groupAggregate", 0], + upChecks: { + $arrayElemAt: ["$upChecks", 0], }, - upChecksAggregate: { - $arrayElemAt: ["$upChecksAggregate", 0], + groupedUpChecks: "$groupedUpChecks", + downChecks: { + $arrayElemAt: ["$downChecks", 0], }, - upChecks: "$upChecks", - downChecksAggregate: { - $arrayElemAt: ["$downChecksAggregate", 0], - }, - downChecks: "$downChecks", + groupedDownChecks: "$groupedDownChecks", }, }, ];