From 22770cc343d67599ed11d37c4e9ac0b3aa4c458b Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 30 Aug 2024 09:41:27 -0700 Subject: [PATCH 1/7] Add db method for deleting checks by team --- Server/db/mongo/modules/checkModule.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/Server/db/mongo/modules/checkModule.js b/Server/db/mongo/modules/checkModule.js index 56a40d2ad..89d0e707f 100644 --- a/Server/db/mongo/modules/checkModule.js +++ b/Server/db/mongo/modules/checkModule.js @@ -170,7 +170,7 @@ const getTeamChecks = async (req) => { }; /** - * Delete all checks for a user + * Delete all checks for a monitor * @async * @param {string} monitorId * @returns {number} @@ -185,10 +185,33 @@ const deleteChecks = async (monitorId) => { throw error; } }; + +/** + * Delete all checks for a team + * @async + * @param {string} monitorId + * @returns {number} + * @throws {Error} + */ + +const deleteChecksByTeam = async (teamId) => { + try { + const teamMonitors = await Monitor.find({ teamId: teamId }); + teamMonitors.forEach(async (monitor) => { + await Check.deleteMany({ monitorId: monitor._id }); + }); + + return true; + } catch (error) { + throw error; + } +}; + module.exports = { createCheck, getChecksCount, getChecks, getTeamChecks, deleteChecks, + deleteChecksByTeam, }; From fd3985190b3b483c5e074f295db24f1259d27c81 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 30 Aug 2024 09:41:42 -0700 Subject: [PATCH 2/7] Add controller method and validation for deleting check by team --- Server/controllers/checkController.js | 19 +++++++++++++++++++ Server/validation/joi.js | 5 +++++ 2 files changed, 24 insertions(+) diff --git a/Server/controllers/checkController.js b/Server/controllers/checkController.js index f553dabcb..3e3a0203c 100644 --- a/Server/controllers/checkController.js +++ b/Server/controllers/checkController.js @@ -6,6 +6,7 @@ const { getTeamChecksParamValidation, getTeamChecksQueryValidation, deleteChecksParamValidation, + deleteChecksByTeamParamValidation, } = require("../validation/joi"); const { successMessages } = require("../utils/messages"); const SERVICE_NAME = "check"; @@ -112,9 +113,27 @@ const deleteChecks = async (req, res, next) => { } }; +const deleteChecksByTeam = async (req, res, next) => { + try { + const res = await deleteChecksByTeamParamValidation.validateAsync( + req.params + ); + return res.status(200).json({ + success: true, + msg: successMessages.CHECK_DELETE, + data: { deletedCount: res }, + }); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteChecksByTeam"; + error.status = 422; + } +}; + module.exports = { createCheck, getChecks, getTeamChecks, deleteChecks, + deleteChecksByTeam, }; diff --git a/Server/validation/joi.js b/Server/validation/joi.js index 2a7ff173d..9a2dba06c 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -292,6 +292,10 @@ const deleteChecksParamValidation = joi.object({ monitorId: joi.string().required(), }); +const deleteChecksByTeamParamValidation = joi.object({ + teamId: joi.string().required(), +}); + //**************************************** // PageSpeedCheckValidation //**************************************** @@ -372,6 +376,7 @@ module.exports = { getTeamChecksParamValidation, getTeamChecksQueryValidation, deleteChecksParamValidation, + deleteChecksByTeamParamValidation, deleteUserParamValidation, getPageSpeedCheckParamValidation, createPageSpeedCheckParamValidation, From 6708027d66d56c6e64e5551bd39dbc9f1f09075f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 30 Aug 2024 09:43:16 -0700 Subject: [PATCH 3/7] Add route --- Server/routes/checkRoute.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Server/routes/checkRoute.js b/Server/routes/checkRoute.js index 562b91263..e5c3b9d63 100644 --- a/Server/routes/checkRoute.js +++ b/Server/routes/checkRoute.js @@ -1,6 +1,7 @@ const router = require("express").Router(); const checkController = require("../controllers/checkController"); const { verifyOwnership } = require("../middleware/verifyOwnership"); +const { isAllowed } = require("../middleware/isAllowed"); const Monitor = require("../models/Monitor"); router.post( @@ -19,4 +20,10 @@ router.delete( checkController.deleteChecks ); +router.delete( + "/team/:teamId", + isAllowed(["admin", "superadmin"]), + checkController.deleteChecks +); + module.exports = router; From 13039ed2ff17ebdc17b2e430acab79b4f6c930e4 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 30 Aug 2024 09:47:11 -0700 Subject: [PATCH 4/7] Add thunk and network methods --- .../UptimeMonitors/uptimeMonitorsSlice.js | 38 +++++++++++++++++++ Client/src/Utils/NetworkService.js | 19 ++++++++++ 2 files changed, 57 insertions(+) diff --git a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js index 79af537e0..61da63ba0 100644 --- a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js +++ b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js @@ -149,6 +149,26 @@ export const pauseUptimeMonitor = createAsyncThunk( } ); +export const deleteMonitorChecksByTeamId = createAsyncThunk( + "monitors/deleteChecksByTeamId", + async (data, thunkApi) => { + try { + const { authToken, teamId } = data; + const res = await networkService.deleteChecksByTeamId(authToken, teamId); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } +); + const uptimeMonitorsSlice = createSlice({ name: "uptimeMonitors", initialState, @@ -256,6 +276,24 @@ const uptimeMonitorsSlice = createSlice({ : "Failed to delete uptime monitor"; }) // ***************************************************** + // Delete Monitor checks by Team ID + // ***************************************************** + .addCase(deleteMonitorChecksByTeamId.pending, (state) => { + state.isLoading = true; + }) + .addCase(deleteMonitorChecksByTeamId.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(deleteMonitorChecksByTeamId.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to delete monitor checks"; + }) + // ***************************************************** // Pause Monitor // ***************************************************** .addCase(pauseUptimeMonitor.pending, (state) => { diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 74bf8460b..31a23b5b1 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -184,6 +184,25 @@ class NetworkService { }, }); } + + /** + * ************************************ + * Deletes all checks for all monitor by teamID + * ************************************ + * + * @async + * @param {string} authToken - The authorization token to be used in the request header. + * @param {string} monitorId - The ID of the monitor to be deleted. + * @returns {Promise} The response from the axios DELETE request. + */ + async deleteChecksByTeamId(authToken, teamId) { + return this.axiosInstance.delete(`/checks/team/${teamId}`, { + headers: { + Authorization: `Bearer ${authToken}`, + "Content-Type": "application/json", + }, + }); + } /** * ************************************ * Pauses a single monitor by its ID From eedf2879ee52796aa4510cc40a8448d1769a60a4 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 30 Aug 2024 10:04:47 -0700 Subject: [PATCH 5/7] finish implementation --- Client/src/Pages/Settings/index.jsx | 29 ++++++++++++++++++++++++-- Server/controllers/checkController.js | 29 ++++++++++++++++---------- Server/db/mongo/MongoDB.js | 2 ++ Server/db/mongo/modules/checkModule.js | 4 ++-- Server/routes/checkRoute.js | 2 +- Server/validation/joi.js | 4 ++-- 6 files changed, 52 insertions(+), 18 deletions(-) diff --git a/Client/src/Pages/Settings/index.jsx b/Client/src/Pages/Settings/index.jsx index d908e33c1..06bc00e1e 100644 --- a/Client/src/Pages/Settings/index.jsx +++ b/Client/src/Pages/Settings/index.jsx @@ -1,14 +1,37 @@ import { useTheme } from "@emotion/react"; import { Box, Stack, styled, Typography } from "@mui/material"; import Button from "../../Components/Button"; +import ButtonSpinner from "../../Components/ButtonSpinner"; import Field from "../../Components/Inputs/Field"; import Link from "../../Components/Link"; import Select from "../../Components/Inputs/Select"; import { logger } from "../../Utils/Logger"; import "./index.css"; - +import { useDispatch, useSelector } from "react-redux"; +import { createToast } from "../../Utils/toastUtils"; +import { deleteMonitorChecksByTeamId } from "../../Features/UptimeMonitors/uptimeMonitorsSlice"; const Settings = () => { const theme = useTheme(); + const { user, authToken } = useSelector((state) => state.auth); + const { isLoading } = useSelector((state) => state.uptimeMonitors); + + const dispatch = useDispatch(); + const handleClearStats = async () => { + try { + const action = await dispatch( + deleteMonitorChecksByTeamId({ teamId: user.teamId, authToken }) + ); + + if (deleteMonitorChecksByTeamId.fulfilled.match(action)) { + createToast({ body: "Stats cleared successfully" }); + } else { + createToast({ body: "Failed to clear stats" }); + } + } catch (error) { + logger.error(error); + createToast({ body: "Failed to clear stats" }); + } + }; const ConfigBox = styled("div")({ display: "flex", @@ -100,9 +123,11 @@ const Settings = () => { /> Clear all stats. This is irreversible. -