From 0b803da5fb93d2242d4451bb1cac20b0afd3df26 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 7 Jan 2025 16:27:35 -0800 Subject: [PATCH 1/4] rewrite query to use aggregation pipeline --- Server/db/mongo/modules/monitorModule.js | 144 ++++++++++++++++++----- Server/utils/dataUtils.js | 3 +- 2 files changed, 115 insertions(+), 32 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index c296de34b..84c7ff971 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -12,6 +12,7 @@ import { buildUptimeDetailsPipeline, buildHardwareDetailsPipeline, } from "./monitorModuleQueries.js"; +import { ObjectId } from "mongodb"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -29,6 +30,15 @@ const CHECK_MODEL_LOOKUP = { hardware: HardwareCheck, }; +const MODEL_TYPE_MAPPING = { + http: "checks", + ping: "checks", + docker: "checks", + port: "checks", + pagespeed: "pagespeedchecks", + hardware: "hardwarechecks", +}; + /** * Get all monitors * @async @@ -570,6 +580,7 @@ const getMonitorsByTeamId = async (req, res) => { } = req.query; const monitorQuery = { teamId: req.params.teamId }; + const monitorCount = await Monitor.countDocuments(monitorQuery); if (type !== undefined) { monitorQuery.type = Array.isArray(type) ? { $in: type } : type; @@ -582,46 +593,117 @@ const getMonitorsByTeamId = async (req, res) => { { url: { $regex: filter, $options: "i" } }, ]; } - const monitorCount = await Monitor.countDocuments(monitorQuery); // Pagination const skip = page && rowsPerPage ? page * rowsPerPage : 0; // Build Sort option - const sort = field ? { [field]: order === "asc" ? 1 : -1 } : {}; + const sort = { [field]: order === "asc" ? 1 : -1 }; - const monitors = await Monitor.find(monitorQuery) - .skip(skip) - .limit(rowsPerPage) - .sort(sort); - - // Early return if limit is set to -1, indicating we don't want any checks - if (limit === "-1") { - return { monitors, monitorCount }; + const matchStage = { teamId: new ObjectId(req.params.teamId) }; + if (type !== undefined) { + matchStage.type = Array.isArray(type) ? { $in: type } : type; + } + if (filter !== undefined) { + matchStage.$or = [ + { name: { $regex: filter, $options: "i" } }, + { url: { $regex: filter, $options: "i" } }, + ]; } - // Map each monitor to include its associated checks - const monitorsWithChecks = await Promise.all( - monitors.map(async (monitor) => { - let model = CHECK_MODEL_LOOKUP[monitor.type]; - // Checks are order newest -> oldest - let checks = await model - .find({ - monitorId: monitor._id, - ...(status && { status }), - }) - .sort({ createdAt: checkOrder === "asc" ? 1 : -1 }) + let result = await Monitor.aggregate([ + { $match: matchStage }, + { $skip: parseInt(skip) }, + ...(rowsPerPage ? [{ $limit: parseInt(rowsPerPage) }] : []), + { $sort: sort }, + { + $lookup: { + from: "checks", + let: { monitorId: "$_id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + ...(status && { status }), + }, + }, + { $sort: { createdAt: checkOrder === "asc" ? 1 : -1 } }, + { $limit: parseInt(limit) || 0 }, + ], + as: "standardchecks", + }, + }, + { + $lookup: { + from: "pagespeedchecks", + let: { monitorId: "$_id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + ...(status && { status }), + }, + }, + { $sort: { createdAt: checkOrder === "asc" ? 1 : -1 } }, + { $limit: parseInt(limit) || 0 }, + ], + as: "pagespeedchecks", + }, + }, + { + $lookup: { + from: "hardwarechecks", + let: { monitorId: "$_id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + ...(status && { status }), + }, + }, + { $sort: { createdAt: checkOrder === "asc" ? 1 : -1 } }, + { $limit: parseInt(limit) || 0 }, + ], + as: "hardwarechecks", + }, + }, + { + $addFields: { + checks: { + $switch: { + branches: [ + { + case: { $in: ["$type", ["http", "ping", "docker", "port"]] }, + then: "$standardchecks", + }, + { + case: { $eq: ["$type", "pagespeed"] }, + then: "$pagespeedchecks", + }, + { + case: { $eq: ["$type", "hardware"] }, + then: "$hardwarechecks", + }, + ], + default: [], + }, + }, + }, + }, + { + $project: { + standardchecks: 0, + pagespeedchecks: 0, + hardwarechecks: 0, + }, + }, + ]); + result = result.map((monitor) => { + monitor.checks = NormalizeData(monitor.checks, 10, 100); + return monitor; + }); - .limit(limit || 0); - - //Normalize checks if requested - if (normalize !== undefined) { - checks = NormalizeData(checks, 10, 100); - } - return { ...monitor.toObject(), checks }; - }) - ); - return { monitors: monitorsWithChecks, monitorCount }; + return { monitors: result, monitorCount }; } catch (error) { error.service = SERVICE_NAME; error.method = "getMonitorsByTeamId"; diff --git a/Server/utils/dataUtils.js b/Server/utils/dataUtils.js index fd10a4257..8fbb22821 100644 --- a/Server/utils/dataUtils.js +++ b/Server/utils/dataUtils.js @@ -26,6 +26,7 @@ const NormalizeData = (checks, rangeMin, rangeMax) => { const min = calculatePercentile(checks, 0); const max = calculatePercentile(checks, 95); const normalizedChecks = checks.map((check) => { + console.log(check); const originalResponseTime = check.responseTime; // Normalize the response time between 1 and 100 let normalizedResponseTime = @@ -38,7 +39,7 @@ const NormalizeData = (checks, rangeMin, rangeMax) => { Math.min(rangeMax, normalizedResponseTime) ); return { - ...check._doc, + ...check, responseTime: normalizedResponseTime, originalResponseTime: originalResponseTime, }; From ef56283fa9547e8cecc6bed1ac2ef245f308df8f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 8 Jan 2025 10:05:34 -0800 Subject: [PATCH 2/4] Fix merge conflict, remove unused dispatch --- Client/src/Pages/Uptime/Configure/index.jsx | 2 -- Client/src/Pages/Uptime/Home/index.jsx | 8 +------- Server/db/mongo/modules/monitorModule.js | 9 --------- Server/utils/dataUtils.js | 1 - 4 files changed, 1 insertion(+), 19 deletions(-) diff --git a/Client/src/Pages/Uptime/Configure/index.jsx b/Client/src/Pages/Uptime/Configure/index.jsx index 76c030776..6cef97e0f 100644 --- a/Client/src/Pages/Uptime/Configure/index.jsx +++ b/Client/src/Pages/Uptime/Configure/index.jsx @@ -11,7 +11,6 @@ import { updateUptimeMonitor, pauseUptimeMonitor, getUptimeMonitorById, - getUptimeSummaryByTeamId, deleteUptimeMonitor, } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; import TextInput from "../../../Components/Inputs/TextInput"; @@ -157,7 +156,6 @@ const Configure = () => { const action = await dispatch(updateUptimeMonitor({ authToken, monitor: monitor })); if (action.meta.requestStatus === "fulfilled") { createToast({ body: "Monitor updated successfully!" }); - dispatch(getUptimeSummaryByTeamId(authToken)); } else { createToast({ body: "Failed to update monitor." }); } diff --git a/Client/src/Pages/Uptime/Home/index.jsx b/Client/src/Pages/Uptime/Home/index.jsx index 50ab2572b..7e1f056ca 100644 --- a/Client/src/Pages/Uptime/Home/index.jsx +++ b/Client/src/Pages/Uptime/Home/index.jsx @@ -14,7 +14,6 @@ import { getUptimeSummaryByTeamId, getUptimeMonitorsByTeamId, } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; -import { setRowsPerPage } from "../../../Features/UI/uiSlice"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; import { useSelector, useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; @@ -26,15 +25,13 @@ const BREADCRUMBS = [{ name: `Uptime`, path: "/uptime" }]; const UptimeMonitors = () => { // Redux state - const { isLoading, monitorsSummary } = useSelector((state) => state.uptimeMonitors); - const authState = useSelector((state) => state.auth); - const { rowsPerPage } = useSelector((state) => state.ui.monitors); // Local state const [monitors, setMonitors] = useState([]); const [sort, setSort] = useState({}); const [search, setSearch] = useState(""); const [page, setPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(10); const [isSearching, setIsSearching] = useState(false); const [monitorUpdateTrigger, setMonitorUpdateTrigger] = useState(false); @@ -46,9 +43,6 @@ const UptimeMonitors = () => { const isAdmin = useIsAdmin(); const { isLoading, monitorsSummary } = useSelector((state) => state.uptimeMonitors); const authState = useSelector((state) => state.auth); - const dispatch = useDispatch({}); - const [monitorUpdateTrigger, setMonitorUpdateTrigger] = useState(false); - const fetchParams = useMemo( () => ({ diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 84c7ff971..457e1ad87 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -30,15 +30,6 @@ const CHECK_MODEL_LOOKUP = { hardware: HardwareCheck, }; -const MODEL_TYPE_MAPPING = { - http: "checks", - ping: "checks", - docker: "checks", - port: "checks", - pagespeed: "pagespeedchecks", - hardware: "hardwarechecks", -}; - /** * Get all monitors * @async diff --git a/Server/utils/dataUtils.js b/Server/utils/dataUtils.js index 8fbb22821..008578158 100644 --- a/Server/utils/dataUtils.js +++ b/Server/utils/dataUtils.js @@ -26,7 +26,6 @@ const NormalizeData = (checks, rangeMin, rangeMax) => { const min = calculatePercentile(checks, 0); const max = calculatePercentile(checks, 95); const normalizedChecks = checks.map((check) => { - console.log(check); const originalResponseTime = check.responseTime; // Normalize the response time between 1 and 100 let normalizedResponseTime = From 35796dccd470a95648aa52cbad500787a87940f0 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 8 Jan 2025 10:06:50 -0800 Subject: [PATCH 3/4] add normalize check --- Server/db/mongo/modules/monitorModule.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 457e1ad87..189243295 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -689,10 +689,12 @@ const getMonitorsByTeamId = async (req, res) => { }, }, ]); - result = result.map((monitor) => { - monitor.checks = NormalizeData(monitor.checks, 10, 100); - return monitor; - }); + if (normalize) { + result = result.map((monitor) => { + monitor.checks = NormalizeData(monitor.checks, 10, 100); + return monitor; + }); + } return { monitors: result, monitorCount }; } catch (error) { From 2694f836c7c5d3c0971164c921ebccc608967f86 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 8 Jan 2025 10:17:51 -0800 Subject: [PATCH 4/4] fix destructuring --- Server/db/mongo/modules/monitorModule.js | 1 - Server/utils/dataUtils.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 189243295..aa4cafbb7 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -695,7 +695,6 @@ const getMonitorsByTeamId = async (req, res) => { return monitor; }); } - return { monitors: result, monitorCount }; } catch (error) { error.service = SERVICE_NAME; diff --git a/Server/utils/dataUtils.js b/Server/utils/dataUtils.js index 008578158..ca6ca66df 100644 --- a/Server/utils/dataUtils.js +++ b/Server/utils/dataUtils.js @@ -47,7 +47,7 @@ const NormalizeData = (checks, rangeMin, rangeMax) => { return normalizedChecks; } else { return checks.map((check) => { - return { ...check._doc, originalResponseTime: check.responseTime }; + return { ...check, originalResponseTime: check.responseTime }; }); } };