Merge branch 'develop' into feat/fe/statuspage-3

This commit is contained in:
Shemy Gan committed 2025-01-10 15:28:01 -05:00
commit 6f4c606356
12 files changed
+232 -217

No files matched your search

+18 -38
View File
@@ -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",
+2 -2
View File
@@ -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",
+2 -3
View File
@@ -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]);
@@ -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 }) => {
<BarChart
width="100%"
height="100%"
data={stats.downChecks}
data={monitor.groupedDownChecks}
onMouseEnter={() => {
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) => (
<Cell
key={`cell-${entry.time}`}
fill={
@@ -79,9 +81,8 @@ const DownBarChart = memo(({ stats, type, onBarHover }) => {
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,
@@ -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 }) => {
<BarChart
width="100%"
height="100%"
data={stats.upChecks}
data={monitor.groupedUpChecks}
onMouseEnter={() => {
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 (
<Cell
@@ -90,9 +90,8 @@ UpBarChart.displayName = "UpBarChart";
// Validate props using PropTypes
UpBarChart.propTypes = {
stats: PropTypes.shape({
upChecks: PropTypes.array,
upChecksAggregate: PropTypes.object,
monitor: PropTypes.shape({
groupedUpChecks: PropTypes.array,
}),
type: PropTypes.string,
onBarHover: PropTypes.func,
+11 -13
View File
@@ -221,17 +221,17 @@ const DetailsPage = () => {
<StatBox
sx={statusStyles[determineState(monitor)]}
heading={"active for"}
subHeading={splitDuration(monitor?.stats?.timeSinceLastFalseCheck)}
subHeading={splitDuration(monitor?.uptimeStreak)}
/>
<StatBox
heading="last check"
subHeading={splitDuration(monitor?.stats?.timeSinceLastCheck)}
subHeading={splitDuration(monitor?.timeSinceLastCheck)}
/>
<StatBox
heading="last response time"
subHeading={
<>
{monitor?.stats?.latestResponseTime}
{monitor?.latestResponseTime}
<Typography component="span">{"ms"}</Typography>
</>
}
@@ -308,7 +308,7 @@ const DetailsPage = () => {
<Typography component="span">
{hoveredUptimeData !== null
? hoveredUptimeData.totalChecks
: (monitor.stats?.upChecks?.reduce((count, checkGroup) => {
: (monitor?.groupedUpChecks?.reduce((count, checkGroup) => {
return count + checkGroup.totalChecks;
}, 0) ?? 0)}
</Typography>
@@ -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
)}
<Typography component="span">
@@ -349,7 +349,7 @@ const DetailsPage = () => {
</Box>
</Stack>
<UpBarChart
stats={monitor?.stats}
monitor={monitor}
type={dateRange}
onBarHover={setHoveredUptimeData}
/>
@@ -366,7 +366,7 @@ const DetailsPage = () => {
<Typography component="span">
{hoveredIncidentsData !== null
? hoveredIncidentsData.totalChecks
: (monitor.stats?.downChecks?.reduce((count, checkGroup) => {
: (monitor?.groupedDownChecks?.reduce((count, checkGroup) => {
return count + checkGroup.totalChecks;
}, 0) ?? 0)}
</Typography>
@@ -388,7 +388,7 @@ const DetailsPage = () => {
)}
</Box>
<DownBarChart
stats={monitor?.stats}
monitor={monitor}
type={dateRange}
onBarHover={setHoveredIncidentsData}
/>
@@ -400,9 +400,7 @@ const DetailsPage = () => {
</IconBox>
<Typography component="h2">Average Response Time</Typography>
</Stack>
<ResponseGaugeChart
avgResponseTime={monitor?.stats?.groupAggregate?.avgResponseTime ?? 0}
/>
<ResponseGaugeChart avgResponseTime={monitor.avgResponseTime ?? 0} />
</ChartBox>
<ChartBox sx={{ padding: 0 }}>
<Stack
@@ -415,7 +413,7 @@ const DetailsPage = () => {
<Typography component="h2">Response Times</Typography>
</Stack>
<MonitorDetailsAreaChart
checks={monitor?.stats?.groupChecks ?? []}
checks={monitor.groupedChecks ?? []}
dateRange={dateRange}
/>
</ChartBox>
+27 -5
View File
@@ -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();
+1 -1
View File
@@ -508,7 +508,7 @@ class MonitorController {
data: monitors,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getMonitorsForDisplay"));
next(handleError(error, SERVICE_NAME, "getMonitorsByTeamId"));
}
};
}
+60 -55
View File
@@ -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;
});
+70 -60
View File
@@ -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",
},
},
];
+3 -3
View File
@@ -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",
+22 -21
View File
@@ -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,