Merge branch 'develop' into feat/fe/statuspage-3
This commit is contained in:
commit
6f4c606356
12 files changed
+232
-217
No files matched your search
Generated
+18
-38
@@ -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
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -508,7 +508,7 @@ class MonitorController {
|
||||
data: monitors,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getMonitorsForDisplay"));
|
||||
next(handleError(error, SERVICE_NAME, "getMonitorsByTeamId"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
Generated
+3
-3
@@ -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
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user