Merge pull request #580 from bluewave-labs/fix/incident-pagination
Fix/incident pagination, resolves #576
This commit is contained in:
12 files changed
+395
-198
No files matched your search
@@ -79,7 +79,6 @@ const MonitorDetailsAreaChart = ({ checks }) => {
|
||||
|
||||
MonitorDetailsAreaChart.propTypes = {
|
||||
checks: PropTypes.array,
|
||||
filter: PropTypes.string,
|
||||
};
|
||||
|
||||
CustomToolTip.propTypes = {
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
import PropTypes from "prop-types";
|
||||
import {
|
||||
TableContainer,
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableBody,
|
||||
Pagination,
|
||||
PaginationItem,
|
||||
Paper,
|
||||
} from "@mui/material";
|
||||
|
||||
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
|
||||
import ArrowForwardRoundedIcon from "@mui/icons-material/ArrowForwardRounded";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import axiosInstance from "../../../Utils/axiosConfig";
|
||||
import { StatusLabel } from "../../../Components/Label";
|
||||
|
||||
const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
|
||||
const { authToken, user } = useSelector((state) => state.auth);
|
||||
const [checks, setChecks] = useState([]);
|
||||
const [checksCount, setChecksCount] = useState(0);
|
||||
const [paginationController, setPaginationController] = useState({
|
||||
page: 0,
|
||||
rowsPerPage: 12,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPage = async () => {
|
||||
try {
|
||||
let url = `/checks/${selectedMonitor}?sortOrder=desc&filter=${filter}&page=${paginationController.page}&rowsPerPage=${paginationController.rowsPerPage}`;
|
||||
|
||||
if (selectedMonitor === "0") {
|
||||
url = `/checks/user/${user._id}?sortOrder=desc&filter=${filter}&page=${paginationController.page}&rowsPerPage=${paginationController.rowsPerPage}`;
|
||||
}
|
||||
|
||||
const res = await axiosInstance.get(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
},
|
||||
});
|
||||
setChecks(res.data.data.checks);
|
||||
setChecksCount(res.data.data.checksCount);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
fetchPage();
|
||||
}, [
|
||||
authToken,
|
||||
user,
|
||||
selectedMonitor,
|
||||
filter,
|
||||
paginationController.page,
|
||||
paginationController.rowsPerPage,
|
||||
]);
|
||||
|
||||
const handlePageChange = (_, newPage) => {
|
||||
console.log(newPage);
|
||||
setPaginationController({
|
||||
...paginationController,
|
||||
page: newPage - 1, // 0-indexed
|
||||
});
|
||||
};
|
||||
|
||||
let paginationComponent = <></>;
|
||||
if (checksCount > paginationController.rowsPerPage) {
|
||||
paginationComponent = (
|
||||
<Pagination
|
||||
count={Math.ceil(checksCount / paginationController.rowsPerPage)}
|
||||
page={paginationController.page + 1} //0-indexed
|
||||
onChange={handlePageChange}
|
||||
shape="rounded"
|
||||
renderItem={(item) => (
|
||||
<PaginationItem
|
||||
slots={{
|
||||
previous: ArrowBackRoundedIcon,
|
||||
next: ArrowForwardRoundedIcon,
|
||||
}}
|
||||
{...item}
|
||||
sx={{
|
||||
"&:focus": {
|
||||
outline: "none",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Monitor Name</TableCell>
|
||||
<TableCell>Status</TableCell>
|
||||
<TableCell>Date & Time</TableCell>
|
||||
<TableCell>Message</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{checks.map((check) => {
|
||||
const status = check.status === true ? "up" : "down";
|
||||
|
||||
return (
|
||||
<TableRow key={check._id}>
|
||||
<TableCell>{monitors[check.monitorId]?.name}</TableCell>
|
||||
<TableCell>
|
||||
<StatusLabel
|
||||
status={status}
|
||||
text={status}
|
||||
customStyles={{ textTransform: "capitalize" }}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{new Date(check.createdAt).toLocaleString()}
|
||||
</TableCell>
|
||||
<TableCell>{check.statusCode}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{paginationComponent}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
IncidentTable.propTypes = {
|
||||
monitors: PropTypes.object.isRequired,
|
||||
selectedMonitor: PropTypes.string.isRequired,
|
||||
filter: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default IncidentTable;
|
||||
@@ -14,6 +14,9 @@
|
||||
height: 34px;
|
||||
border-color: var(--env-var-color-16);
|
||||
}
|
||||
.incidents .MuiPagination-root {
|
||||
background-color: var(--env-var-color-8);
|
||||
}
|
||||
|
||||
/* remove later */
|
||||
.incidents .MuiSelect-select {
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { Typography, ButtonGroup, Stack, Box, Skeleton } from "@mui/material";
|
||||
import WestRoundedIcon from "@mui/icons-material/WestRounded";
|
||||
import { ButtonGroup, Stack, Skeleton, Typography } from "@mui/material";
|
||||
import Button from "../../Components/Button";
|
||||
import axiosInstance from "../../Utils/axiosConfig";
|
||||
import BasicTable from "../../Components/BasicTable";
|
||||
import { StatusLabel } from "../../Components/Label";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useNavigate } from "react-router";
|
||||
import Select from "../../Components/Inputs/Select";
|
||||
|
||||
import IncidentTable from "./IncidentTable";
|
||||
import "./index.css";
|
||||
|
||||
/**
|
||||
@@ -40,8 +36,8 @@ const SkeletonLayout = () => {
|
||||
|
||||
const Incidents = () => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const authState = useSelector((state) => state.auth);
|
||||
|
||||
const [monitors, setMonitors] = useState({});
|
||||
const [selectedMonitor, setSelectedMonitor] = useState("0");
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -49,21 +45,11 @@ const Incidents = () => {
|
||||
// TODO do something with these filters
|
||||
const [filter, setFilter] = useState("all");
|
||||
|
||||
let data = {
|
||||
cols: [
|
||||
{ id: 1, name: "Status" },
|
||||
{ id: 2, name: "Timestamp" },
|
||||
{ id: 3, name: "Monitor" },
|
||||
{ id: 4, name: "Message" },
|
||||
],
|
||||
rows: [],
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchIncidents = async () => {
|
||||
const fetchMonitors = async () => {
|
||||
setLoading(true);
|
||||
const res = await axiosInstance.get(
|
||||
`/monitors/user/${authState.user._id}?status=false`,
|
||||
`/monitors/user/${authState.user._id}?status=false&limit=1`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${authState.authToken}`,
|
||||
@@ -82,141 +68,70 @@ const Incidents = () => {
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
fetchIncidents();
|
||||
fetchMonitors();
|
||||
}, [authState]);
|
||||
|
||||
useEffect(() => {}, []);
|
||||
|
||||
const handleSelect = (event) => {
|
||||
setSelectedMonitor(event.target.value);
|
||||
};
|
||||
let incidents = [];
|
||||
let hasAnyIncidents = false;
|
||||
let hasSpecificIncidents = false;
|
||||
if (selectedMonitor === "0") {
|
||||
Object.values(monitors).forEach((monitor) => {
|
||||
incidents = incidents.concat(monitor.checks);
|
||||
});
|
||||
if (incidents.length !== 0) hasAnyIncidents = true;
|
||||
hasSpecificIncidents = hasAnyIncidents;
|
||||
} else {
|
||||
const monitor = monitors[selectedMonitor];
|
||||
incidents = monitor.checks;
|
||||
if (incidents.length !== 0) hasSpecificIncidents = true;
|
||||
hasAnyIncidents = true;
|
||||
}
|
||||
incidents = incidents
|
||||
.sort((a, b) => {
|
||||
return new Date(b.createdAt) - new Date(a.createdAt);
|
||||
})
|
||||
.filter((incident) => {
|
||||
if (filter === "all") return true;
|
||||
else if (filter === "resolve") return incident.statusCode === 5000;
|
||||
else if (filter === "down") return incident.status === false;
|
||||
})
|
||||
.map((incident, idx) => {
|
||||
return {
|
||||
id: idx,
|
||||
data: [
|
||||
{
|
||||
id: idx + 0,
|
||||
data: <StatusLabel status="down" text="Down" />,
|
||||
},
|
||||
{ id: idx + 1, data: new Date(incident.createdAt).toLocaleString() },
|
||||
{ id: idx + 2, data: monitors[incident.monitorId].name },
|
||||
{ id: idx + 3, data: incident.statusCode },
|
||||
],
|
||||
};
|
||||
});
|
||||
data.rows = incidents;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack className="incidents" gap={theme.gap.large}>
|
||||
{loading ? (
|
||||
<SkeletonLayout />
|
||||
) : hasAnyIncidents ? (
|
||||
<>
|
||||
<Stack direction="row" alignItems="center" gap={theme.gap.medium}>
|
||||
<Typography component="h1">Incident history for: </Typography>
|
||||
<Select
|
||||
id="incidents-select-monitor"
|
||||
placeholder="All servers"
|
||||
value={selectedMonitor}
|
||||
onChange={handleSelect}
|
||||
items={Object.values(monitors)}
|
||||
/>
|
||||
{hasAnyIncidents && hasSpecificIncidents && (
|
||||
<ButtonGroup sx={{ ml: "auto" }}>
|
||||
<Button
|
||||
level="secondary"
|
||||
label="All"
|
||||
onClick={() => setFilter("all")}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
filter === "all" && theme.palette.otherColors.fillGray,
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
level="secondary"
|
||||
label="Down"
|
||||
onClick={() => setFilter("down")}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
filter === "down" && theme.palette.otherColors.fillGray,
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
level="secondary"
|
||||
label="Cannot Resolve"
|
||||
onClick={() => setFilter("resolve")}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
filter === "resolve" &&
|
||||
theme.palette.otherColors.fillGray,
|
||||
}}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
)}
|
||||
</Stack>
|
||||
{hasAnyIncidents && hasSpecificIncidents ? (
|
||||
<BasicTable data={data} paginated={true} rowsPerPage={12} />
|
||||
) : (
|
||||
<Typography alignSelf="center" mt={theme.gap.xxl}>
|
||||
The monitor you have selected, has no recorded incidents yet.
|
||||
</Typography>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Box>
|
||||
<Button
|
||||
level="tertiary"
|
||||
label="Back"
|
||||
animate="slideLeft"
|
||||
img={<WestRoundedIcon />}
|
||||
onClick={() => navigate(-1)}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.otherColors.fillGray,
|
||||
px: theme.gap.ml,
|
||||
mb: theme.gap.ml,
|
||||
"& svg.MuiSvgIcon-root": {
|
||||
mr: theme.gap.small,
|
||||
fill: theme.palette.otherColors.slateGray,
|
||||
},
|
||||
}}
|
||||
<Stack className="incidents" gap={theme.gap.large}>
|
||||
{loading ? (
|
||||
<SkeletonLayout />
|
||||
) : (
|
||||
<>
|
||||
<Stack direction="row" alignItems="center" gap={theme.gap.medium}>
|
||||
<Typography display="inline-block" component="h1">
|
||||
Incidents for
|
||||
</Typography>
|
||||
<Select
|
||||
id="incidents-select-monitor"
|
||||
placeholder="All servers"
|
||||
value={selectedMonitor}
|
||||
onChange={handleSelect}
|
||||
items={Object.values(monitors)}
|
||||
/>
|
||||
<Box
|
||||
padding={theme.gap.xxl}
|
||||
gap={theme.gap.small}
|
||||
border={1}
|
||||
borderRadius={`${theme.shape.borderRadius}px`}
|
||||
borderColor={theme.palette.otherColors.graishWhite}
|
||||
backgroundColor={theme.palette.otherColors.white}
|
||||
>
|
||||
<Typography textAlign="center">No incidents, yet.</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</>
|
||||
<ButtonGroup sx={{ ml: "auto" }}>
|
||||
<Button
|
||||
level="secondary"
|
||||
label="All"
|
||||
onClick={() => setFilter("all")}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
filter === "all" && theme.palette.otherColors.fillGray,
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
level="secondary"
|
||||
label="Down"
|
||||
onClick={() => setFilter("down")}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
filter === "down" && theme.palette.otherColors.fillGray,
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
level="secondary"
|
||||
label="Cannot Resolve"
|
||||
onClick={() => setFilter("resolve")}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
filter === "resolve" && theme.palette.otherColors.fillGray,
|
||||
}}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
<IncidentTable
|
||||
monitors={monitors}
|
||||
selectedMonitor={selectedMonitor}
|
||||
filter={filter}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@ import {
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableBody,
|
||||
TableFooter,
|
||||
TablePagination,
|
||||
PaginationItem,
|
||||
Pagination,
|
||||
Paper,
|
||||
@@ -20,7 +18,7 @@ import { StatusLabel } from "../../../../Components/Label";
|
||||
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
|
||||
import ArrowForwardRoundedIcon from "@mui/icons-material/ArrowForwardRounded";
|
||||
|
||||
const PaginationTable = ({ monitorId, filter }) => {
|
||||
const PaginationTable = ({ monitorId, dateRange }) => {
|
||||
const { authToken } = useSelector((state) => state.auth);
|
||||
const [checks, setChecks] = useState([]);
|
||||
const [checksCount, setChecksCount] = useState(0);
|
||||
@@ -33,7 +31,7 @@ const PaginationTable = ({ monitorId, filter }) => {
|
||||
const fetchPage = async () => {
|
||||
try {
|
||||
const res = await axiosInstance.get(
|
||||
`/checks/${monitorId}?sortOrder=desc&filter=${filter}&page=${paginationController.page}&rowsPerPage=${paginationController.rowsPerPage}`,
|
||||
`/checks/${monitorId}?sortOrder=desc&dateRange=${dateRange}&page=${paginationController.page}&rowsPerPage=${paginationController.rowsPerPage}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
@@ -50,7 +48,7 @@ const PaginationTable = ({ monitorId, filter }) => {
|
||||
}, [
|
||||
authToken,
|
||||
monitorId,
|
||||
filter,
|
||||
dateRange,
|
||||
paginationController.page,
|
||||
paginationController.rowsPerPage,
|
||||
]);
|
||||
@@ -58,16 +56,16 @@ const PaginationTable = ({ monitorId, filter }) => {
|
||||
const handlePageChange = (_, newPage) => {
|
||||
setPaginationController({
|
||||
...paginationController,
|
||||
page: newPage - 1,
|
||||
page: newPage - 1, // 0-indexed
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
let paginationComponent = <></>;
|
||||
if (checksCount > paginationController.rowsPerPage) {
|
||||
paginationComponent = (
|
||||
<Pagination
|
||||
count={Math.ceil(checksCount / paginationController.rowsPerPage)}
|
||||
page={paginationController.page + 1}
|
||||
page={paginationController.page + 1} //0-indexed
|
||||
onChange={handlePageChange}
|
||||
shape="rounded"
|
||||
renderItem={(item) => (
|
||||
@@ -133,7 +131,7 @@ const PaginationTable = ({ monitorId, filter }) => {
|
||||
|
||||
PaginationTable.propTypes = {
|
||||
monitorId: PropTypes.string.isRequired,
|
||||
filter: PropTypes.string.isRequired,
|
||||
dateRange: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default PaginationTable;
|
||||
@@ -113,13 +113,13 @@ const DetailsPage = () => {
|
||||
const [monitor, setMonitor] = useState({});
|
||||
const { monitorId } = useParams();
|
||||
const { authToken } = useSelector((state) => state.auth);
|
||||
const [filter, setFilter] = useState("day");
|
||||
const [dateRange, setDateRange] = useState("day");
|
||||
const navigate = useNavigate();
|
||||
|
||||
const fetchMonitor = useCallback(async () => {
|
||||
try {
|
||||
const res = await axiosInstance.get(
|
||||
`/monitors/${monitorId}?sortOrder=asc&filter=${filter}&numToDisplay=50&normalize=true`,
|
||||
`/monitors/${monitorId}?sortOrder=asc&filter=${dateRange}&numToDisplay=50&normalize=true`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
@@ -131,7 +131,7 @@ const DetailsPage = () => {
|
||||
console.error("Error fetching monitor of id: " + monitorId);
|
||||
navigate("/not-found");
|
||||
}
|
||||
}, [authToken, monitorId, navigate, filter]);
|
||||
}, [authToken, monitorId, navigate, dateRange]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchMonitor();
|
||||
@@ -301,44 +301,43 @@ const DetailsPage = () => {
|
||||
<Button
|
||||
level="secondary"
|
||||
label="Day"
|
||||
onClick={() => setFilter("day")}
|
||||
onClick={() => setDateRange("day")}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
filter === "day" && theme.palette.otherColors.fillGray,
|
||||
dateRange === "day" &&
|
||||
theme.palette.otherColors.fillGray,
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
level="secondary"
|
||||
label="Week"
|
||||
onClick={() => setFilter("week")}
|
||||
onClick={() => setDateRange("week")}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
filter === "week" && theme.palette.otherColors.fillGray,
|
||||
dateRange === "week" &&
|
||||
theme.palette.otherColors.fillGray,
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
level="secondary"
|
||||
label="Month"
|
||||
onClick={() => setFilter("month")}
|
||||
onClick={() => setDateRange("month")}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
filter === "month" &&
|
||||
dateRange === "month" &&
|
||||
theme.palette.otherColors.fillGray,
|
||||
}}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
<Box sx={{ height: "200px" }}>
|
||||
<MonitorDetailsAreaChart
|
||||
checks={monitor.checks}
|
||||
filter={filter}
|
||||
/>
|
||||
<MonitorDetailsAreaChart checks={monitor.checks} />
|
||||
</Box>
|
||||
</Box>
|
||||
<Stack gap={theme.gap.ml}>
|
||||
<Typography component="h2">History</Typography>
|
||||
{/* TODO New Table */}
|
||||
<PaginationTable monitorId={monitorId} filter={filter} />
|
||||
<PaginationTable monitorId={monitorId} dateRange={dateRange} />
|
||||
{/* <BasicTable data={data} paginated={true} /> */}
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -3,6 +3,8 @@ const {
|
||||
createCheckBodyValidation,
|
||||
getChecksParamValidation,
|
||||
getChecksQueryValidation,
|
||||
getUserChecksParamValidation,
|
||||
getUserChecksQueryValidation,
|
||||
deleteChecksParamValidation,
|
||||
} = require("../validation/joi");
|
||||
const { successMessages } = require("../utils/messages");
|
||||
@@ -60,6 +62,31 @@ const getChecks = async (req, res, next) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getUserChecks = async (req, res, next) => {
|
||||
try {
|
||||
await getUserChecksParamValidation.validateAsync(req.params);
|
||||
await getUserChecksQueryValidation.validateAsync(req.query);
|
||||
} catch (error) {
|
||||
error.status = 422;
|
||||
error.service = SERVICE_NAME;
|
||||
error.message =
|
||||
error.details?.[0]?.message || error.message || "Validation Error";
|
||||
next(error);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const checkData = await req.db.getUserChecks(req);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.CHECK_GET,
|
||||
data: checkData,
|
||||
});
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteChecks = async (req, res, next) => {
|
||||
try {
|
||||
await deleteChecksParamValidation.validateAsync(req.params);
|
||||
@@ -88,5 +115,6 @@ const deleteChecks = async (req, res, next) => {
|
||||
module.exports = {
|
||||
createCheck,
|
||||
getChecks,
|
||||
getUserChecks,
|
||||
deleteChecks,
|
||||
};
|
||||
@@ -90,6 +90,7 @@ const {
|
||||
createCheck,
|
||||
getChecksCount,
|
||||
getChecks,
|
||||
getUserChecks,
|
||||
deleteChecks,
|
||||
} = require("./modules/checkModule");
|
||||
|
||||
@@ -148,6 +149,7 @@ module.exports = {
|
||||
createCheck,
|
||||
getChecksCount,
|
||||
getChecks,
|
||||
getUserChecks,
|
||||
deleteChecks,
|
||||
createAlert,
|
||||
getAlertsByUserId,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
const Check = require("../../../models/Check");
|
||||
const filterLookup = {
|
||||
const Monitor = require("../../../models/Monitor");
|
||||
const mongoose = require("mongoose");
|
||||
const dateRangeLookup = {
|
||||
day: new Date(new Date().setDate(new Date().getDate() - 1)),
|
||||
week: new Date(new Date().setDate(new Date().getDate() - 7)),
|
||||
month: new Date(new Date().setMonth(new Date().getMonth() - 1)),
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a check for a monitor
|
||||
* @async
|
||||
@@ -28,13 +31,32 @@ const createCheck = async (checkData) => {
|
||||
|
||||
const getChecksCount = async (req) => {
|
||||
const monitorId = req.params.monitorId;
|
||||
const dateRange = req.query.dateRange;
|
||||
const filter = req.query.filter;
|
||||
console.log(filter);
|
||||
// Build query
|
||||
const checksQuery = { monitorId: monitorId };
|
||||
// Filter checks by "day", "week", or "month"
|
||||
if (filter !== undefined) {
|
||||
checksQuery.createdAt = { $gte: filterLookup[filter] };
|
||||
if (dateRange !== undefined) {
|
||||
checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] };
|
||||
}
|
||||
|
||||
if (filter !== undefined) {
|
||||
checksQuery.status = false;
|
||||
switch (filter) {
|
||||
case "all":
|
||||
break;
|
||||
case "down":
|
||||
break;
|
||||
case "resolve":
|
||||
checksQuery.statusCode = 5000;
|
||||
break;
|
||||
default:
|
||||
console.log("default");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const count = await Check.countDocuments(checksQuery);
|
||||
return count;
|
||||
};
|
||||
@@ -50,23 +72,34 @@ const getChecksCount = async (req) => {
|
||||
const getChecks = async (req) => {
|
||||
try {
|
||||
const { monitorId } = req.params;
|
||||
let { sortOrder, limit, filter, page, rowsPerPage } = req.query;
|
||||
|
||||
let { sortOrder, limit, dateRange, filter, page, rowsPerPage } = req.query;
|
||||
// Default limit to 0 if not provided
|
||||
if (limit === undefined) limit = 0;
|
||||
limit = limit === "undefined" ? 0 : limit;
|
||||
|
||||
// Default sort order is newest -> oldest
|
||||
if (sortOrder === "asc") {
|
||||
sortOrder = 1;
|
||||
} else if (sortOrder === "desc") {
|
||||
sortOrder = -1;
|
||||
} else sortOrder = -1;
|
||||
sortOrder = sortOrder === "asc" ? 1 : -1;
|
||||
|
||||
// Build query
|
||||
const checksQuery = { monitorId: monitorId };
|
||||
// Filter checks by "day", "week", or "month"
|
||||
if (dateRange !== undefined) {
|
||||
checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] };
|
||||
}
|
||||
// Fitler checks by status
|
||||
if (filter !== undefined) {
|
||||
checksQuery.createdAt = { $gte: filterLookup[filter] };
|
||||
checksQuery.status = false;
|
||||
switch (filter) {
|
||||
case "all":
|
||||
break;
|
||||
case "down":
|
||||
break;
|
||||
case "resolve":
|
||||
checksQuery.statusCode = 5000;
|
||||
break;
|
||||
default:
|
||||
console.log("default");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to skip and limit here
|
||||
@@ -84,8 +117,61 @@ const getChecks = async (req) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getUserChecks = async (req) => {
|
||||
const { userId } = req.params;
|
||||
let { sortOrder, limit, dateRange, filter, page, rowsPerPage } = req.query;
|
||||
|
||||
// Get monitorIDs
|
||||
const userMonitors = await Monitor.find({ userId: userId });
|
||||
const monitorIds = userMonitors.map((monitor) => monitor._id);
|
||||
|
||||
//Build check query
|
||||
// Default limit to 0 if not provided
|
||||
limit = limit === "undefined" ? 0 : limit;
|
||||
|
||||
// Default sort order is newest -> oldest
|
||||
sortOrder = sortOrder === "asc" ? 1 : -1;
|
||||
|
||||
checksQuery = { monitorId: { $in: monitorIds } };
|
||||
|
||||
if (filter !== undefined) {
|
||||
checksQuery.status = false;
|
||||
switch (filter) {
|
||||
case "all":
|
||||
break;
|
||||
case "down":
|
||||
break;
|
||||
case "resolve":
|
||||
checksQuery.statusCode = 5000;
|
||||
break;
|
||||
default:
|
||||
console.log("default");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dateRange !== undefined) {
|
||||
checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] };
|
||||
}
|
||||
|
||||
// Skip and limit for pagination
|
||||
let skip = 0;
|
||||
if (page && rowsPerPage) {
|
||||
skip = page * rowsPerPage;
|
||||
}
|
||||
|
||||
const checksCount = await Check.countDocuments(checksQuery);
|
||||
|
||||
const checks = await Check.find(checksQuery)
|
||||
.skip(skip)
|
||||
.limit(rowsPerPage)
|
||||
.sort({ createdAt: sortOrder });
|
||||
|
||||
return { checksCount, checks };
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete all checks for a monitor
|
||||
* Delete all checks for a user
|
||||
* @async
|
||||
* @param {string} monitorId
|
||||
* @returns {number}
|
||||
@@ -100,4 +186,10 @@ const deleteChecks = async (monitorId) => {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
module.exports = { createCheck, getChecksCount, getChecks, deleteChecks };
|
||||
module.exports = {
|
||||
createCheck,
|
||||
getChecksCount,
|
||||
getChecks,
|
||||
getUserChecks,
|
||||
deleteChecks,
|
||||
};
|
||||
+13
-12
@@ -4,7 +4,7 @@ const Notification = require("../models/Notification");
|
||||
|
||||
/**
|
||||
* Check Schema for MongoDB collection.
|
||||
*
|
||||
*
|
||||
* Represents a check associated with a monitor, storing information
|
||||
* about the status and response of a particular check event.
|
||||
*/
|
||||
@@ -12,7 +12,7 @@ const CheckSchema = mongoose.Schema(
|
||||
{
|
||||
/**
|
||||
* Reference to the associated Monitor document.
|
||||
*
|
||||
*
|
||||
* @type {mongoose.Schema.Types.ObjectId}
|
||||
*/
|
||||
monitorId: {
|
||||
@@ -22,7 +22,7 @@ const CheckSchema = mongoose.Schema(
|
||||
},
|
||||
/**
|
||||
* Status of the check (true for up, false for down).
|
||||
*
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
status: {
|
||||
@@ -30,23 +30,23 @@ const CheckSchema = mongoose.Schema(
|
||||
},
|
||||
/**
|
||||
* Response time of the check in milliseconds.
|
||||
*
|
||||
*
|
||||
* @type {Number}
|
||||
*/
|
||||
responseTime: {
|
||||
type: Number,
|
||||
type: Number,
|
||||
},
|
||||
/**
|
||||
* HTTP status code received during the check.
|
||||
*
|
||||
*
|
||||
* @type {Number}
|
||||
*/
|
||||
statusCode: {
|
||||
type: Number,
|
||||
type: Number,
|
||||
},
|
||||
/**
|
||||
* Message or description of the check result.
|
||||
*
|
||||
*
|
||||
* @type {String}
|
||||
*/
|
||||
message: {
|
||||
@@ -54,7 +54,7 @@ const CheckSchema = mongoose.Schema(
|
||||
},
|
||||
/**
|
||||
* Expiry date of the check, auto-calculated to expire after 30 days.
|
||||
*
|
||||
*
|
||||
* @type {Date}
|
||||
*/
|
||||
expiry: {
|
||||
@@ -70,7 +70,7 @@ const CheckSchema = mongoose.Schema(
|
||||
|
||||
/**
|
||||
* Pre-save middleware to handle status change notifications.
|
||||
*
|
||||
*
|
||||
* This middleware checks if the status of the monitor associated
|
||||
* with the check has changed and sends notifications if necessary.
|
||||
*/
|
||||
@@ -79,7 +79,9 @@ CheckSchema.pre("save", async function (next) {
|
||||
const monitor = await mongoose.model("Monitor").findById(this.monitorId);
|
||||
|
||||
if (monitor) {
|
||||
const notifications = await Notification.find({ monitorId: this.monitorId });
|
||||
const notifications = await Notification.find({
|
||||
monitorId: this.monitorId,
|
||||
});
|
||||
|
||||
// Check if there are any notifications
|
||||
if (notifications && notifications.length > 0) {
|
||||
@@ -128,5 +130,4 @@ CheckSchema.pre("save", async function (next) {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = mongoose.model("Check", CheckSchema);
|
||||
@@ -21,4 +21,6 @@ router.delete(
|
||||
checkController.deleteChecks
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
router.get("/user/:userId", checkController.getUserChecks);
|
||||
|
||||
module.exports = router;
|
||||
@@ -235,7 +235,21 @@ const getChecksParamValidation = joi.object({
|
||||
const getChecksQueryValidation = joi.object({
|
||||
sortOrder: joi.string().valid("asc", "desc"),
|
||||
limit: joi.number(),
|
||||
filter: joi.string().valid("day", "week", "month"),
|
||||
dateRange: joi.string().valid("day", "week", "month"),
|
||||
filter: joi.string().valid("all", "down", "resolve"),
|
||||
page: joi.number(),
|
||||
rowsPerPage: joi.number(),
|
||||
});
|
||||
|
||||
const getUserChecksParamValidation = joi.object({
|
||||
userId: joi.string().required(),
|
||||
});
|
||||
|
||||
const getUserChecksQueryValidation = joi.object({
|
||||
sortOrder: joi.string().valid("asc", "desc"),
|
||||
limit: joi.number(),
|
||||
dateRange: joi.string().valid("day", "week", "month"),
|
||||
filter: joi.string().valid("all", "down", "resolve"),
|
||||
page: joi.number(),
|
||||
rowsPerPage: joi.number(),
|
||||
});
|
||||
@@ -320,6 +334,8 @@ module.exports = {
|
||||
createCheckBodyValidation,
|
||||
getChecksParamValidation,
|
||||
getChecksQueryValidation,
|
||||
getUserChecksParamValidation,
|
||||
getUserChecksQueryValidation,
|
||||
deleteChecksParamValidation,
|
||||
deleteUserParamValidation,
|
||||
getPageSpeedCheckParamValidation,
|
||||
|
||||
Reference in new issue
Block a user