diff --git a/Client/package-lock.json b/Client/package-lock.json index 53d7490c3..86e7f43d5 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", @@ -1402,15 +1402,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", @@ -1460,14 +1460,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" @@ -1496,35 +1496,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", @@ -1543,7 +1523,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", @@ -1583,9 +1563,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 ff654d726..8417ad1fa 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", 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]); 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/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 99c093d25..168b46822 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -3,6 +3,8 @@ 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; @@ -25,11 +27,7 @@ class NetworkService { this.axiosInstance.interceptors.response.use( (response) => response, (error) => { - if (error.response && error.response.status === 401) { - dispatch(clearAuthState()); - dispatch(clearUptimeMonitorState()); - navigate("/login"); - } + this.handleError(error); return Promise.reject(error); } ); @@ -39,6 +37,30 @@ 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 (status >= 500) { + // Display toast for server errors to all users + createToast({ + variant: "error", + body: "Checkmate server is not running or has issues. Please check.", + }); + } + } 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(); 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")); } }; } diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 7b554ff32..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; @@ -508,7 +498,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 +560,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 +664,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; }); 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", }, }, ]; 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", diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index 177ca34c5..5a9ab335c 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,27 @@ 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 + ); const workerStats = await this.getWorkerStats(queue); await this.scaleWorkers(workerStats, queue); @@ -490,9 +491,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,