From 4398010a9f9672cc071529e701ada70a20fd07a7 Mon Sep 17 00:00:00 2001 From: Jasneet Singh Date: Mon, 6 Jan 2025 16:38:58 -0500 Subject: [PATCH 01/11] Inform Admin incase of server issue. --- Client/src/Utils/NetworkService.js | 45 +++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 4812947d0..edef40f0b 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -1,14 +1,20 @@ +import { toast } from 'react-toastify'; import axios from "axios"; const BASE_URL = import.meta.env.VITE_APP_API_BASE_URL; const FALLBACK_BASE_URL = "http://localhost:5000/api/v1"; import { clearAuthState } from "../Features/Auth/authSlice"; import { clearUptimeMonitorState } from "../Features/UptimeMonitors/uptimeMonitorsSlice"; + class NetworkService { constructor(store, dispatch, navigate) { this.store = store; this.dispatch = dispatch; this.navigate = navigate; let baseURL = BASE_URL; + this.lastToastTimestamp = 0; + this.errorOccurred = false; + this.user = this.store.getState().auth?.user; + this.isAdmin = (this.user?.role?.includes("admin") ?? false) || (this.user?.role?.includes("superadmin") ?? false); this.axiosInstance = axios.create(); this.setBaseUrl(baseURL); this.unsubscribe = store.subscribe(() => { @@ -21,15 +27,17 @@ class NetworkService { baseURL = FALLBACK_BASE_URL; } this.setBaseUrl(baseURL); - }); + }); this.axiosInstance.interceptors.response.use( - (response) => response, - (error) => { - if (error.response && error.response.status === 401) { - dispatch(clearAuthState()); - dispatch(clearUptimeMonitorState()); - navigate("/login"); + (response) => { + if (this.isAdmin && this.errorOccurred && response.status >= 200 && response.status < 300) { + this.debounceToast("Checkmate server is running properly.", "success"); + this.errorOccurred = false;// Reset error flag if a successful response is received after errors } + return response; + }, + (error) => { + this.handleError(error); return Promise.reject(error); } ); @@ -39,6 +47,29 @@ class NetworkService { this.axiosInstance.defaults.baseURL = url; }; + handleError(error) { + if (error.response) { + const status = error.response.status; + if (status === 401) { + this.dispatch(clearAuthState()); + this.dispatch(clearUptimeMonitorState()); + this.navigate("/login"); + } else if (this.isAdmin && status >= 500) { + this.debounceToast("Checkmate server is not running or has issues. Please check.", "error"); + this.errorOccurred = true; + } + } + } + + debounceToast(message, type, debounceDuration = 5000) { + const now = Date.now(); + if (now - this.lastToastTimestamp > debounceDuration) { + this.lastToastTimestamp = now; + type === "success" ? toast.success(message) : toast.error(message); + } + } + + cleanup() { if (this.unsubscribe) { this.unsubscribe(); From 83b43d060a3270a19296bca330e7fd95e4f4667f Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 9 Jan 2025 15:21:09 +0000 Subject: [PATCH 02/11] Update material-ui monorepo to v7.23.6 --- Client/package-lock.json | 56 +++++++++++++--------------------------- Client/package.json | 4 +-- 2 files changed, 20 insertions(+), 40 deletions(-) diff --git a/Client/package-lock.json b/Client/package-lock.json index 843e964f6..272af35dd 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -15,8 +15,8 @@ "@mui/lab": "6.0.0-beta.22", "@mui/material": "6.3.1", "@mui/x-charts": "^7.5.1", - "@mui/x-data-grid": "7.23.5", - "@mui/x-date-pickers": "7.23.3", + "@mui/x-data-grid": "7.23.6", + "@mui/x-date-pickers": "7.23.6", "@reduxjs/toolkit": "2.5.0", "axios": "^1.7.4", "dayjs": "1.11.13", @@ -1390,15 +1390,15 @@ "license": "MIT" }, "node_modules/@mui/x-charts": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.23.2.tgz", - "integrity": "sha512-wLeogvQZZtyrAOdG06mDzIQSHBSAB09Uy16AYRUcMxVObi7Fs0i3TJUMpQHMYz1/1DvE1u8zstDgVpVfk8/iCA==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.23.6.tgz", + "integrity": "sha512-25kH01gwE5I0d8kWYZEpXZ9zHVHKSAVDiGwhteqagHtO6x/dEfqbMnjGuaNruCWppJR6wIylLKi/tuiOSpIy2A==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.7", "@mui/utils": "^5.16.6 || ^6.0.0", "@mui/x-charts-vendor": "7.20.0", - "@mui/x-internals": "7.23.0", + "@mui/x-internals": "7.23.6", "@react-spring/rafz": "^9.7.5", "@react-spring/web": "^9.7.5", "clsx": "^2.1.1", @@ -1448,14 +1448,14 @@ } }, "node_modules/@mui/x-data-grid": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.23.5.tgz", - "integrity": "sha512-JmwdfaegpwO9Ei3PYCKy1FFip9AcdMGzZ0VTqzWE93pvDBVGxs/MZKT0g/8PYHJ6yzA5sBHHBxFN8sKfs7kVsg==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.23.6.tgz", + "integrity": "sha512-NcCZH99ZBLRlCcfLwwhCkVowNZHgjy0XZ/c6EuTRMSZl1UqF8ouwitP1ZfAa1idDIWCHFhxo446U/93aGMqOyQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.7", "@mui/utils": "^5.16.6 || ^6.0.0", - "@mui/x-internals": "7.23.5", + "@mui/x-internals": "7.23.6", "clsx": "^2.1.1", "prop-types": "^15.8.1", "reselect": "^5.1.1" @@ -1484,35 +1484,15 @@ } } }, - "node_modules/@mui/x-data-grid/node_modules/@mui/x-internals": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.23.5.tgz", - "integrity": "sha512-PS6p9qL7otbQ2edSF83GgTicssE0Q84Ta+X/5tSwoCnToEKClka1Wc/cXlsjhRVLmoqz8uTqaiNcZAgnyQWNYQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.25.7", - "@mui/utils": "^5.16.6 || ^6.0.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/@mui/x-date-pickers": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.23.3.tgz", - "integrity": "sha512-bjTYX/QzD5ZhVZNNnastMUS3j2Hy4p4IXmJgPJ0vKvQBvUdfEO+ZF42r3PJNNde0FVT1MmTzkmdTlz0JZ6ukdw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.23.6.tgz", + "integrity": "sha512-jt6rEAYLju3NZe3y2S+I5KcTiSHV79FW0jeNUEUTceg1qsPzseHbND66k3zVF0hO3N2oZtLtPywof6vN5Doe+Q==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.7", "@mui/utils": "^5.16.6 || ^6.0.0", - "@mui/x-internals": "7.23.0", + "@mui/x-internals": "7.23.6", "@types/react-transition-group": "^4.4.11", "clsx": "^2.1.1", "prop-types": "^15.8.1", @@ -1531,7 +1511,7 @@ "@mui/material": "^5.15.14 || ^6.0.0", "@mui/system": "^5.15.14 || ^6.0.0", "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0", - "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0", + "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0", "dayjs": "^1.10.7", "luxon": "^3.0.2", "moment": "^2.29.4", @@ -1571,9 +1551,9 @@ } }, "node_modules/@mui/x-internals": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.23.0.tgz", - "integrity": "sha512-bPclKpqUiJYIHqmTxSzMVZi6MH51cQsn5U+8jskaTlo3J4QiMeCYJn/gn7YbeR9GOZFp8hetyHjoQoVHKRXCig==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.23.6.tgz", + "integrity": "sha512-hT1Pa4PNCnxwiauPbYMC3p4DiEF1x05Iu4C1MtC/jMJ1LtthymLmTuQ6ZQ53/R9FeqK6sYd6A6noR+vNMjp5DA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.7", diff --git a/Client/package.json b/Client/package.json index e4853715a..d3ed1741d 100644 --- a/Client/package.json +++ b/Client/package.json @@ -18,8 +18,8 @@ "@mui/lab": "6.0.0-beta.22", "@mui/material": "6.3.1", "@mui/x-charts": "^7.5.1", - "@mui/x-data-grid": "7.23.5", - "@mui/x-date-pickers": "7.23.3", + "@mui/x-data-grid": "7.23.6", + "@mui/x-date-pickers": "7.23.6", "@reduxjs/toolkit": "2.5.0", "axios": "^1.7.4", "dayjs": "1.11.13", From d76f02ba51d41a586b0a87c89bedbc9f5500c264 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 9 Jan 2025 10:41:33 -0800 Subject: [PATCH 03/11] repalce limit param of -1 with null --- Client/src/Pages/Incidents/index.jsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Client/src/Pages/Incidents/index.jsx b/Client/src/Pages/Incidents/index.jsx index 31f853e34..064ed5b85 100644 --- a/Client/src/Pages/Incidents/index.jsx +++ b/Client/src/Pages/Incidents/index.jsx @@ -29,7 +29,7 @@ const Incidents = () => { const res = await networkService.getMonitorsByTeamId({ authToken: authState.authToken, teamId: authState.user.teamId, - limit: -1, + limit: null, types: null, status: null, checkOrder: null, @@ -40,7 +40,6 @@ const Incidents = () => { field: null, order: null, }); - // Reduce to a lookup object for 0(1) lookup if (res?.data?.data?.monitors?.length > 0) { const monitorLookup = res.data.data.monitors.reduce((acc, monitor) => { acc[monitor._id] = monitor; @@ -56,7 +55,7 @@ const Incidents = () => { } }; fetchMonitors(); - }, [authState]); + }, [authState, monitorId]); useEffect(() => {}, [monitors]); From e8aecb4041dd269f0bae5f97db2b7858fd7b8cf6 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 9 Jan 2025 10:42:01 -0800 Subject: [PATCH 04/11] fix method name --- Server/controllers/monitorController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 686661a00..839f2bb13 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -508,7 +508,7 @@ class MonitorController { data: monitors, }); } catch (error) { - next(handleError(error, SERVICE_NAME, "getMonitorsForDisplay")); + next(handleError(error, SERVICE_NAME, "getMonitorsByTeamId")); } }; } From ef23648941e530fb48301afd98680e18cad1a3af Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 9 Jan 2025 10:42:25 -0800 Subject: [PATCH 05/11] ommit checks from query if limit is null --- Server/db/mongo/modules/monitorModule.js | 95 ++++++++++++++---------- 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 7b554ff32..745aecf88 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -508,7 +508,6 @@ const getMonitorById = async (monitorId) => { const getMonitorsByTeamId = async (req) => { let { limit, type, page, rowsPerPage, filter, field, order } = req.query; - // Parse ints limit = parseInt(limit); page = parseInt(page); rowsPerPage = parseInt(rowsPerPage); @@ -571,54 +570,67 @@ const getMonitorsByTeamId = async (req) => { { $sort: sort }, { $skip: skip }, ...(rowsPerPage ? [{ $limit: rowsPerPage }] : []), - { - $lookup: { - from: "checks", - let: { monitorId: "$_id" }, - pipeline: [ + ...(limit + ? [ { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, + $lookup: { + from: "checks", + let: { monitorId: "$_id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + }, + }, + { $sort: { createdAt: -1 } }, + ...(limit ? [{ $limit: limit }] : []), + ], + as: "standardchecks", }, }, - { $sort: { createdAt: -1 } }, - ...(limit ? [{ $limit: limit }] : []), - ], - as: "standardchecks", - }, - }, - { - $lookup: { - from: "pagespeedchecks", - let: { monitorId: "$_id" }, - pipeline: [ + ] + : []), + ...(limit + ? [ { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, + $lookup: { + from: "pagespeedchecks", + let: { monitorId: "$_id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + }, + }, + { $sort: { createdAt: -1 } }, + ...(limit ? [{ $limit: limit }] : []), + ], + as: "pagespeedchecks", }, }, - { $sort: { createdAt: -1 } }, - ...(limit ? [{ $limit: limit }] : []), - ], - as: "pagespeedchecks", - }, - }, - { - $lookup: { - from: "hardwarechecks", - let: { monitorId: "$_id" }, - pipeline: [ + ] + : []), + ...(limit + ? [ { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, + $lookup: { + from: "hardwarechecks", + let: { monitorId: "$_id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + }, + }, + { $sort: { createdAt: -1 } }, + ...(limit ? [{ $limit: limit }] : []), + ], + as: "hardwarechecks", }, }, - { $sort: { createdAt: -1 } }, - ...(limit ? [{ $limit: limit }] : []), - ], - as: "hardwarechecks", - }, - }, + ] + : []), + { $addFields: { checks: { @@ -662,6 +674,9 @@ const getMonitorsByTeamId = async (req) => { let { monitors, summary } = results[0]; monitors = monitors.map((monitor) => { + if (!monitor.checks) { + return monitor; + } monitor.checks = NormalizeData(monitor.checks, 10, 100); return monitor; }); From c1d6433e6cf17a4e624d3bb15635e47298520203 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 9 Jan 2025 12:01:51 -0800 Subject: [PATCH 06/11] use jobScheduler instead of deprecatred repeatable --- Server/service/jobQueue.js | 45 ++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index 177ca34c5..91a74f6be 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -8,6 +8,7 @@ const QUEUE_LOOKUP = { docker: "uptime", pagespeed: "pagespeed", }; +const getSchedulerId = (monitor) => `scheduler:${monitor.type}:${monitor._id}`; import { successMessages, errorMessages } from "../utils/messages.js"; class NewJobQueue { @@ -440,27 +441,29 @@ class NewJobQueue { throw new Error(`Queue for ${monitor.type} not found`); } - // build job options - const jobOptions = { - attempts: 1, - backoff: { - type: "exponential", - delay: 1000, + const jobTemplate = { + name: jobName, + data: monitor, + opts: { + attempts: 1, + backoff: { + type: "exponential", + delay: 1000, + }, + removeOnComplete: true, + removeOnFail: false, + timeout: 1 * 60 * 1000, }, - removeOnComplete: true, - removeOnFail: false, - timeout: 1 * 60 * 1000, }; - // Execute job immediately - await queue.add(jobName, monitor, jobOptions); - await queue.add(jobName, monitor, { - ...jobOptions, - repeat: { - every: monitor?.interval ?? 60000, - immediately: false, - }, - }); + const schedulerId = getSchedulerId(monitor); + await queue.upsertJobScheduler( + schedulerId, + { every: monitor?.interval ?? 60000 }, + jobTemplate + ); + + await queue.upsert; const workerStats = await this.getWorkerStats(queue); await this.scaleWorkers(workerStats, queue); @@ -490,9 +493,9 @@ class NewJobQueue { async deleteJob(monitor) { try { const queue = this.queues[QUEUE_LOOKUP[monitor.type]]; - const wasDeleted = await queue.removeRepeatable(monitor._id, { - every: monitor.interval, - }); + const schedulerId = getSchedulerId(monitor); + const wasDeleted = await queue.removeJobScheduler(schedulerId); + if (wasDeleted === true) { this.logger.info({ message: successMessages.JOB_QUEUE_DELETE_JOB, From 74f23c17923f779d2920aea361a69ea2151d0616 Mon Sep 17 00:00:00 2001 From: Jasneet Singh Date: Thu, 9 Jan 2025 17:38:08 -0500 Subject: [PATCH 07/11] Address pr comments --- Client/src/Utils/NetworkService.js | 43 ++++++++++++------------------ 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index edef40f0b..3388237da 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -1,20 +1,16 @@ -import { toast } from 'react-toastify'; import axios from "axios"; const BASE_URL = import.meta.env.VITE_APP_API_BASE_URL; const FALLBACK_BASE_URL = "http://localhost:5000/api/v1"; import { clearAuthState } from "../Features/Auth/authSlice"; import { clearUptimeMonitorState } from "../Features/UptimeMonitors/uptimeMonitorsSlice"; - +import { createToast } from "./toastUtils"; + class NetworkService { constructor(store, dispatch, navigate) { this.store = store; this.dispatch = dispatch; this.navigate = navigate; let baseURL = BASE_URL; - this.lastToastTimestamp = 0; - this.errorOccurred = false; - this.user = this.store.getState().auth?.user; - this.isAdmin = (this.user?.role?.includes("admin") ?? false) || (this.user?.role?.includes("superadmin") ?? false); this.axiosInstance = axios.create(); this.setBaseUrl(baseURL); this.unsubscribe = store.subscribe(() => { @@ -27,15 +23,9 @@ class NetworkService { baseURL = FALLBACK_BASE_URL; } this.setBaseUrl(baseURL); - }); + }); this.axiosInstance.interceptors.response.use( - (response) => { - if (this.isAdmin && this.errorOccurred && response.status >= 200 && response.status < 300) { - this.debounceToast("Checkmate server is running properly.", "success"); - this.errorOccurred = false;// Reset error flag if a successful response is received after errors - } - return response; - }, + (response) => response, (error) => { this.handleError(error); return Promise.reject(error); @@ -54,22 +44,23 @@ class NetworkService { this.dispatch(clearAuthState()); this.dispatch(clearUptimeMonitorState()); this.navigate("/login"); - } else if (this.isAdmin && status >= 500) { - this.debounceToast("Checkmate server is not running or has issues. Please check.", "error"); - this.errorOccurred = true; + } else if (status >= 500) { + // Display toast for server errors to all users + createToast({ + variant: "error", + body: "Checkmate server is not running or has issues. Please check.", + }); } - } - } - - debounceToast(message, type, debounceDuration = 5000) { - const now = Date.now(); - if (now - this.lastToastTimestamp > debounceDuration) { - this.lastToastTimestamp = now; - type === "success" ? toast.success(message) : toast.error(message); + } else if (error.request) { + // Show a toast informing the user the server didn't respond + createToast({ + variant: "error", + body: "The server did not respond. Please check your network or try again later.", + }); } } - + cleanup() { if (this.unsubscribe) { this.unsubscribe(); From 5bb1e64f25d7eb84c61cb823a6eb9b0f86a7e474 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 9 Jan 2025 23:07:07 +0000 Subject: [PATCH 08/11] Update dependency mongoose to v8.9.4 --- Server/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Server/package-lock.json b/Server/package-lock.json index 30306bf29..b6d9c3ca3 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -5774,9 +5774,9 @@ } }, "node_modules/mongoose": { - "version": "8.9.3", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.3.tgz", - "integrity": "sha512-G50GNPdMqhoiRAJ/24GYAzg13yxXDD3FOOFeYiFwtHmHpAJem3hxbYIxAhLJGWbYEiUZL0qFMu2LXYkgGAmo+Q==", + "version": "8.9.4", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.4.tgz", + "integrity": "sha512-DndoI01aV/q40P9DiYDXsYjhj8vZjmmuFwcC3Tro5wFznoE1z6Fe2JgMnbLR6ghglym5ziYizSfAJykp+UPZWg==", "license": "MIT", "dependencies": { "bson": "^6.10.1", From cd3767a04065de99396be7b7461d0927b20c9429 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 9 Jan 2025 20:50:08 -0800 Subject: [PATCH 09/11] new duration algorithm --- Server/db/mongo/modules/monitorModule.js | 1 + .../db/mongo/modules/monitorModuleQueries.js | 53 +++++++++++++++---- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 745aecf88..d66cd310f 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -353,6 +353,7 @@ const getUptimeDetailsById = async (req) => { const monitorStats = { ...monitor.toObject(), stats: { + uptimeStreak: monitorData[0].uptimeStreak, avgResponseTime: monitorData[0].avgResponseTime, totalChecks: monitorData[0].totalChecks, timeSinceLastCheck: monitorData[0].timeSinceLastCheck, diff --git a/Server/db/mongo/modules/monitorModuleQueries.js b/Server/db/mongo/modules/monitorModuleQueries.js index ccafbcbdc..18914c0c4 100644 --- a/Server/db/mongo/modules/monitorModuleQueries.js +++ b/Server/db/mongo/modules/monitorModuleQueries.js @@ -31,22 +31,43 @@ 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" }, // Haven't found a false yet + { $eq: ["$$this.status", true] }, // Current check is true + ], + }, + { + checks: { $concatArrays: ["$$value.checks", ["$$this"]] }, + foundFalse: false, + }, + { + checks: "$$value.checks", + foundFalse: true, // Mark that we found a false + }, + ], + }, + }, }, }, }, @@ -188,6 +209,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], }, From 59c6f39ea9a8ca7032b96a80cc085d9059b6e77a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 10 Jan 2025 09:46:22 -0800 Subject: [PATCH 10/11] Remove incomplete line --- Server/service/jobQueue.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index 91a74f6be..5a9ab335c 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -463,8 +463,6 @@ class NewJobQueue { jobTemplate ); - await queue.upsert; - const workerStats = await this.getWorkerStats(queue); await this.scaleWorkers(workerStats, queue); } catch (error) { From 12cc20a852169b96182e7ad14ffaf5e2647b2c6e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 10 Jan 2025 12:12:33 -0800 Subject: [PATCH 11/11] refactor uptime details query --- .../Uptime/Details/Charts/DownBarChart.jsx | 17 ++-- .../Uptime/Details/Charts/UpBarChart.jsx | 15 ++-- Client/src/Pages/Uptime/Details/index.jsx | 24 +++--- Server/db/mongo/modules/monitorModule.js | 21 ++--- .../db/mongo/modules/monitorModuleQueries.js | 83 +++++++------------ 5 files changed, 61 insertions(+), 99 deletions(-) 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 d66cd310f..b191bd1bc 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -340,32 +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: { - uptimeStreak: monitorData[0].uptimeStreak, - 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 18914c0c4..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", }, @@ -53,14 +50,16 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { $cond: [ { $and: [ - { $not: "$$value.foundFalse" }, // Haven't found a false yet - { $eq: ["$$this.status", true] }, // Current check is true + { $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, + foundFalse: false, // Add the check to the streak }, + // false case { checks: "$$value.checks", foundFalse: true, // Mark that we found a false @@ -72,7 +71,9 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, ], - 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 }, @@ -100,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 }, @@ -115,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 }, }, }, { @@ -133,7 +137,8 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, ], - upChecks: [ + // Up checks grouped by: {day: hour}, {week: day}, {month: day} + groupedUpChecks: [ { $match: { status: true, @@ -160,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 }, }, }, { @@ -178,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, @@ -252,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", }, }, ];