From b3fab31a612646cb5b2c629ac2b6cd75933f7092 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 8 Aug 2024 12:37:02 -0700 Subject: [PATCH 1/2] node --- Client/src/Pages/Monitors/Details/index.jsx | 8 ++---- Server/controllers/checkController.js | 4 ++- Server/db/mongo/modules/checkModule.js | 32 +++++++++++++++++++-- Server/utils/dataUtils.js | 1 - Server/validation/joi.js | 7 +++++ 5 files changed, 43 insertions(+), 9 deletions(-) diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 6f7d36d3d..aebde997b 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -119,10 +119,8 @@ const DetailsPage = () => { const fetchDataForTable = useCallback(async () => { try { - const limit = 0; - const res = await axiosInstance.get( - `/monitors/${monitorId}?sortOrder=asc&limit=${limit}`, + `/checks/${monitorId}?sortOrder=desc&filter=${filter}`, { headers: { Authorization: `Bearer ${authToken}`, @@ -135,7 +133,7 @@ const DetailsPage = () => { { id: 2, name: "Date & Time" }, { id: 3, name: "Message" }, ], - rows: res.data.data.checks.map((check, idx) => { + rows: res.data.data.map((check, idx) => { const status = check.status === true ? "up" : "down"; return { @@ -165,7 +163,7 @@ const DetailsPage = () => { } catch (error) { console.log(error); } - }, [authToken, monitorId]); + }, [authToken, monitorId, filter]); const fetchMonitor = useCallback(async () => { try { diff --git a/Server/controllers/checkController.js b/Server/controllers/checkController.js index f3c85352f..5d939cbad 100644 --- a/Server/controllers/checkController.js +++ b/Server/controllers/checkController.js @@ -2,6 +2,7 @@ const { createCheckParamValidation, createCheckBodyValidation, getChecksParamValidation, + getChecksQueryValidation, deleteChecksParamValidation, } = require("../validation/joi"); const { successMessages } = require("../utils/messages"); @@ -35,6 +36,7 @@ const createCheck = async (req, res, next) => { const getChecks = async (req, res, next) => { try { await getChecksParamValidation.validateAsync(req.params); + await getChecksQueryValidation.validateAsync(req.query); } catch (error) { error.status = 422; error.service = SERVICE_NAME; @@ -45,7 +47,7 @@ const getChecks = async (req, res, next) => { } try { - const checks = await req.db.getChecks(req.params.monitorId); + const checks = await req.db.getChecks(req); return res .status(200) .json({ success: true, msg: successMessages.CHECK_GET, data: checks }); diff --git a/Server/db/mongo/modules/checkModule.js b/Server/db/mongo/modules/checkModule.js index 35c313fd9..3cf5de3e5 100644 --- a/Server/db/mongo/modules/checkModule.js +++ b/Server/db/mongo/modules/checkModule.js @@ -30,9 +30,37 @@ const createCheck = async (checkData) => { * @throws {Error} */ -const getChecks = async (monitorId) => { +const getChecks = async (req) => { + const filterLookup = { + 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)), + }; + try { - const checks = await Check.find({ monitorId }); + const { monitorId } = req.params; + let { sortOrder, limit, filter } = req.query; + + // Default limit to 0 if not provided + if (limit === undefined) limit = 0; + + // Default sort order is newest -> oldest + if (sortOrder === "asc") { + sortOrder = 1; + } else if (sortOrder === "desc") { + sortOrder = -1; + } else sortOrder = -1; + + // Build query + const checksQuery = { monitorId: monitorId }; + // Filter checks by "day", "week", or "month" + if (filter !== undefined) { + checksQuery.createdAt = { $gte: filterLookup[filter] }; + } + + const checks = await Check.find(checksQuery) + .limit(limit) + .sort({ createdAt: sortOrder }); return checks; } catch (error) { throw error; diff --git a/Server/utils/dataUtils.js b/Server/utils/dataUtils.js index 3495cc3d6..f1c4b39b2 100644 --- a/Server/utils/dataUtils.js +++ b/Server/utils/dataUtils.js @@ -19,7 +19,6 @@ const NormalizeData = (checks, rangeMin, rangeMax) => { const normalizedChecks = checks.map((check) => { const originalResponseTime = check.responseTime; - console.log(originalResponseTime); // Normalize the response time between 1 and 100 let normalizedResponseTime = rangeMin + diff --git a/Server/validation/joi.js b/Server/validation/joi.js index 8e3dbcfec..d6c198e4a 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -232,6 +232,12 @@ const getChecksParamValidation = joi.object({ monitorId: joi.string().required(), }); +const getChecksQueryValidation = joi.object({ + sortOrder: joi.string().valid("asc", "desc"), + limit: joi.number(), + filter: joi.string().valid("day", "week", "month"), +}); + const deleteChecksParamValidation = joi.object({ monitorId: joi.string().required(), }); @@ -311,6 +317,7 @@ module.exports = { createCheckParamValidation, createCheckBodyValidation, getChecksParamValidation, + getChecksQueryValidation, deleteChecksParamValidation, deleteUserParamValidation, getPageSpeedCheckParamValidation, From d43b4806e67a54023f31fd31594a1a8a9173c168 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 8 Aug 2024 13:19:37 -0700 Subject: [PATCH 2/2] Implement pagination on FE and BE --- .../Details/PaginationTable/index.jsx | 121 ++++++++++++++++++ Client/src/Pages/Monitors/Details/index.jsx | 59 +-------- Server/controllers/checkController.js | 9 +- Server/db/mongo/MongoDB.js | 2 + Server/db/mongo/modules/checkModule.js | 37 ++++-- Server/validation/joi.js | 2 + 6 files changed, 162 insertions(+), 68 deletions(-) create mode 100644 Client/src/Pages/Monitors/Details/PaginationTable/index.jsx diff --git a/Client/src/Pages/Monitors/Details/PaginationTable/index.jsx b/Client/src/Pages/Monitors/Details/PaginationTable/index.jsx new file mode 100644 index 000000000..5f0f8a690 --- /dev/null +++ b/Client/src/Pages/Monitors/Details/PaginationTable/index.jsx @@ -0,0 +1,121 @@ +import PropTypes from "prop-types"; +import { + TableContainer, + Table, + TableHead, + TableRow, + TableCell, + TableBody, + TableFooter, + TablePagination, +} from "@mui/material"; + +import { useState, useEffect } from "react"; +import { useSelector } from "react-redux"; +import axiosInstance from "../../../../Utils/axiosConfig"; +import { StatusLabel } from "../../../../Components/Label"; + +const PaginationTable = ({ monitorId, filter }) => { + const { authToken } = useSelector((state) => state.auth); + const [checks, setChecks] = useState([]); + const [checksCount, setChecksCount] = useState(0); + const [paginationController, setPaginationController] = useState({ + page: 0, + rowsPerPage: 5, + }); + + useEffect(() => { + const fetchPage = async () => { + try { + const res = await axiosInstance.get( + `/checks/${monitorId}?sortOrder=desc&filter=${filter}&page=${paginationController.page}&rowsPerPage=${paginationController.rowsPerPage}`, + { + headers: { + Authorization: `Bearer ${authToken}`, + }, + } + ); + setChecks(res.data.data.checks); + setChecksCount(res.data.data.checksCount); + } catch (error) { + console.log(error); + } + }; + fetchPage(); + }, [ + authToken, + monitorId, + filter, + paginationController.page, + paginationController.rowsPerPage, + ]); + + const handlePageChange = (_, newPage) => { + setPaginationController({ + ...paginationController, + page: newPage, + }); + }; + + const handleChangeRowsPerPage = (event) => { + setPaginationController({ + ...paginationController, + rowsPerPage: parseInt(event.target.value, 10), + page: 0, + }); + }; + + return ( + + + + + Status + Date & Time + Message + + + + {checks.map((check) => { + const status = check.status === true ? "up" : "down"; + + return ( + + + + + + {new Date(check.createdAt).toLocaleString()} + + {check.statusCode} + + ); + })} + + + + + + +
+
+ ); +}; + +PaginationTable.propTypes = { + monitorId: PropTypes.string.isRequired, + filter: PropTypes.string.isRequired, +}; + +export default PaginationTable; diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index aebde997b..55dfd9335 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -4,7 +4,6 @@ import { Box, Skeleton, Stack, Typography, useTheme } from "@mui/material"; import { useSelector } from "react-redux"; import { useNavigate, useParams } from "react-router-dom"; import axiosInstance from "../../../Utils/axiosConfig"; -import BasicTable from "../../../Components/BasicTable"; import MonitorDetailsAreaChart from "../../../Components/Charts/MonitorDetailsAreaChart"; import { StatusLabel } from "../../../Components/Label"; import ButtonGroup from "@mui/material/ButtonGroup"; @@ -13,6 +12,7 @@ import WestRoundedIcon from "@mui/icons-material/WestRounded"; import GreenCheck from "../../../assets/icons/checkbox-green.svg?react"; import RedCheck from "../../../assets/icons/checkbox-red.svg?react"; import SettingsIcon from "../../../assets/icons/settings-bold.svg?react"; +import PaginationTable from "./PaginationTable"; import { formatDuration, formatDurationRounded, @@ -111,60 +111,11 @@ const SkeletonLayout = () => { */ const DetailsPage = () => { const [monitor, setMonitor] = useState({}); - const [data, setData] = useState({}); const { monitorId } = useParams(); const { authToken } = useSelector((state) => state.auth); const [filter, setFilter] = useState("day"); const navigate = useNavigate(); - const fetchDataForTable = useCallback(async () => { - try { - const res = await axiosInstance.get( - `/checks/${monitorId}?sortOrder=desc&filter=${filter}`, - { - headers: { - Authorization: `Bearer ${authToken}`, - }, - } - ); - const data = { - cols: [ - { id: 1, name: "Status" }, - { id: 2, name: "Date & Time" }, - { id: 3, name: "Message" }, - ], - rows: res.data.data.map((check, idx) => { - const status = check.status === true ? "up" : "down"; - - return { - id: check._id, - data: [ - { - id: idx, - data: ( - - ), - }, - { - id: idx + 1, - data: new Date(check.createdAt).toLocaleString(), - }, - { id: idx + 2, data: check.statusCode }, - ], - }; - }), - }; - - setData(data); - } catch (error) { - console.log(error); - } - }, [authToken, monitorId, filter]); - const fetchMonitor = useCallback(async () => { try { const res = await axiosInstance.get( @@ -186,10 +137,6 @@ const DetailsPage = () => { fetchMonitor(); }, [fetchMonitor]); - useEffect(() => { - fetchDataForTable(); - }, [fetchDataForTable]); - const theme = useTheme(); /** @@ -390,7 +337,9 @@ const DetailsPage = () => { History - + {/* TODO New Table */} + + {/* */} diff --git a/Server/controllers/checkController.js b/Server/controllers/checkController.js index 5d939cbad..20c2e0b8c 100644 --- a/Server/controllers/checkController.js +++ b/Server/controllers/checkController.js @@ -48,9 +48,12 @@ const getChecks = async (req, res, next) => { try { const checks = await req.db.getChecks(req); - return res - .status(200) - .json({ success: true, msg: successMessages.CHECK_GET, data: checks }); + const checksCount = await req.db.getChecksCount(req); + return res.status(200).json({ + success: true, + msg: successMessages.CHECK_GET, + data: { checksCount, checks }, + }); } catch (error) { error.service = SERVICE_NAME; next(error); diff --git a/Server/db/mongo/MongoDB.js b/Server/db/mongo/MongoDB.js index 68597a794..1dbc4e3b8 100644 --- a/Server/db/mongo/MongoDB.js +++ b/Server/db/mongo/MongoDB.js @@ -88,6 +88,7 @@ const { const { createCheck, + getChecksCount, getChecks, deleteChecks, } = require("./modules/checkModule"); @@ -145,6 +146,7 @@ module.exports = { deleteAllMonitors, editMonitor, createCheck, + getChecksCount, getChecks, deleteChecks, createAlert, diff --git a/Server/db/mongo/modules/checkModule.js b/Server/db/mongo/modules/checkModule.js index 3cf5de3e5..4d315dc9a 100644 --- a/Server/db/mongo/modules/checkModule.js +++ b/Server/db/mongo/modules/checkModule.js @@ -1,5 +1,9 @@ const Check = require("../../../models/Check"); - +const filterLookup = { + 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 @@ -22,6 +26,19 @@ const createCheck = async (checkData) => { } }; +const getChecksCount = async (req) => { + const monitorId = req.params.monitorId; + const filter = req.query.filter; + // Build query + const checksQuery = { monitorId: monitorId }; + // Filter checks by "day", "week", or "month" + if (filter !== undefined) { + checksQuery.createdAt = { $gte: filterLookup[filter] }; + } + const count = await Check.countDocuments(checksQuery); + return count; +}; + /** * Get all checks for a monitor * @async @@ -31,15 +48,9 @@ const createCheck = async (checkData) => { */ const getChecks = async (req) => { - const filterLookup = { - 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)), - }; - try { const { monitorId } = req.params; - let { sortOrder, limit, filter } = req.query; + let { sortOrder, limit, filter, page, rowsPerPage } = req.query; // Default limit to 0 if not provided if (limit === undefined) limit = 0; @@ -58,8 +69,14 @@ const getChecks = async (req) => { checksQuery.createdAt = { $gte: filterLookup[filter] }; } + // Need to skip and limit here + let skip = 0; + if (page && rowsPerPage) { + skip = page * rowsPerPage; + } const checks = await Check.find(checksQuery) - .limit(limit) + .skip(skip) + .limit(rowsPerPage) .sort({ createdAt: sortOrder }); return checks; } catch (error) { @@ -83,4 +100,4 @@ const deleteChecks = async (monitorId) => { throw error; } }; -module.exports = { createCheck, getChecks, deleteChecks }; +module.exports = { createCheck, getChecksCount, getChecks, deleteChecks }; diff --git a/Server/validation/joi.js b/Server/validation/joi.js index d6c198e4a..f938fabd9 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -236,6 +236,8 @@ const getChecksQueryValidation = joi.object({ sortOrder: joi.string().valid("asc", "desc"), limit: joi.number(), filter: joi.string().valid("day", "week", "month"), + page: joi.number(), + rowsPerPage: joi.number(), }); const deleteChecksParamValidation = joi.object({