mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-04-29 21:20:17 -05:00
Merge pull request #571 from bluewave-labs/feat/be/get-monitor-by-id-refactor
Feat/be/get monitor by id refactor
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { AreaChart, Area, XAxis, Tooltip, ResponsiveContainer } from "recharts";
|
||||
import { NormalizeData } from "../ChartUtils";
|
||||
import "./index.css";
|
||||
|
||||
const CustomToolTip = ({ active, payload, label }) => {
|
||||
@@ -28,7 +27,7 @@ const CustomToolTip = ({ active, payload, label }) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const MonitorDetailsAreaChart = ({ checks, filter }) => {
|
||||
const MonitorDetailsAreaChart = ({ checks }) => {
|
||||
// SQUASH ERROR, NOT PERMANENT SOLUTION
|
||||
const error = console.error;
|
||||
console.error = (...args) => {
|
||||
@@ -47,45 +46,12 @@ const MonitorDetailsAreaChart = ({ checks, filter }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const filterChecks = (checks, filter, numToDisplay) => {
|
||||
const limits = {
|
||||
day: 24 * 60 * 60 * 1000,
|
||||
week: 24 * 60 * 60 * 1000 * 7,
|
||||
month: 24 * 60 * 60 * 1000 * 30, //TODO better monthly calculations
|
||||
};
|
||||
|
||||
const now = new Date().getTime();
|
||||
let result = [];
|
||||
for (let i = 0; i < checks.length; i++) {
|
||||
const checkTime = new Date(checks[i].createdAt).getTime();
|
||||
if (now - checkTime < limits[filter]) {
|
||||
result.push(checks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// If more than numToDisplay checks, pick every nth check
|
||||
if (result.length > numToDisplay) {
|
||||
const n = Math.ceil(result.length / numToDisplay);
|
||||
result = result.filter((_, idx) => {
|
||||
return idx % n === 0;
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
let normalizedChecks = [];
|
||||
if (checks && checks.length > 0) {
|
||||
const filteredChecks = filterChecks(checks, filter, 75);
|
||||
normalizedChecks = NormalizeData(filteredChecks, 10, 100);
|
||||
}
|
||||
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<AreaChart
|
||||
width={500}
|
||||
height={400}
|
||||
data={normalizedChecks}
|
||||
data={checks}
|
||||
margin={{
|
||||
top: 10,
|
||||
right: 0,
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import "./index.css";
|
||||
import PropTypes from "prop-types";
|
||||
import { BarChart, Bar, ResponsiveContainer, Cell } from "recharts";
|
||||
import { NormalizeData } from "../ChartUtils";
|
||||
|
||||
const ResponseTimeChart = ({ checks = [] }) => {
|
||||
const normalizedChecks = NormalizeData(checks, 1, 100);
|
||||
return (
|
||||
<div className="chart-container">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart
|
||||
width={150}
|
||||
height={40}
|
||||
data={normalizedChecks}
|
||||
data={checks}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<Bar maxBarSize={10} dataKey="responseTime">
|
||||
{normalizedChecks.map((check, index) => (
|
||||
{checks.map((check, index) => (
|
||||
<Cell
|
||||
key={`cell-${index}`}
|
||||
fill={
|
||||
|
||||
@@ -51,7 +51,7 @@ export const getUptimeMonitorsByUserId = createAsyncThunk(
|
||||
const user = jwtDecode(token);
|
||||
try {
|
||||
const res = await axiosInstance.get(
|
||||
`/monitors/user/${user._id}?limit=25&type=http&type=ping&sortOrder=desc`,
|
||||
`/monitors/user/${user._id}?limit=25&type=http&type=ping&sortOrder=desc&normalize=true`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, useCallback } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Box, Skeleton, Stack, Typography, useTheme } from "@mui/material";
|
||||
import { useSelector } from "react-redux";
|
||||
@@ -115,6 +115,74 @@ const DetailsPage = () => {
|
||||
const { monitorId } = useParams();
|
||||
const { authToken } = useSelector((state) => state.auth);
|
||||
const [filter, setFilter] = useState("day");
|
||||
const navigate = useNavigate();
|
||||
|
||||
const fetchDataForTable = useCallback(async () => {
|
||||
try {
|
||||
const limit = 0;
|
||||
|
||||
const res = await axiosInstance.get(
|
||||
`/monitors/${monitorId}?sortOrder=asc&limit=${limit}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
const data = {
|
||||
cols: [
|
||||
{ id: 1, name: "Status" },
|
||||
{ id: 2, name: "Date & Time" },
|
||||
{ id: 3, name: "Message" },
|
||||
],
|
||||
rows: res.data.data.checks.map((check, idx) => {
|
||||
const status = check.status === true ? "up" : "down";
|
||||
|
||||
return {
|
||||
id: check._id,
|
||||
data: [
|
||||
{
|
||||
id: idx,
|
||||
data: (
|
||||
<StatusLabel
|
||||
status={status}
|
||||
text={status}
|
||||
customStyles={{ textTransform: "capitalize" }}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: idx + 1,
|
||||
data: new Date(check.createdAt).toLocaleString(),
|
||||
},
|
||||
{ id: idx + 2, data: check.statusCode },
|
||||
],
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
setData(data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, [authToken, monitorId]);
|
||||
|
||||
const fetchMonitor = useCallback(async () => {
|
||||
try {
|
||||
const res = await axiosInstance.get(
|
||||
`/monitors/${monitorId}?sortOrder=asc&filter=${filter}&numToDisplay=50&normalize=true`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
setMonitor(res.data.data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching monitor of id: " + monitorId);
|
||||
navigate("/not-found");
|
||||
}
|
||||
}, [authToken, monitorId, navigate, filter]);
|
||||
|
||||
const fetchDataForChart = async () => {
|
||||
try {
|
||||
@@ -185,14 +253,17 @@ const DetailsPage = () => {
|
||||
|
||||
useEffect(() => {
|
||||
fetchMonitor();
|
||||
}, [monitorId, authToken]);
|
||||
}, [fetchMonitor]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchDataForTable();
|
||||
}, [fetchDataForTable]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchDataForChart();
|
||||
}, [filter]);
|
||||
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
|
||||
/**
|
||||
* Function to calculate uptime duration based on the most recent check.
|
||||
|
||||
@@ -145,7 +145,7 @@ const Monitors = () => {
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getUptimeMonitorsByUserId(authState.authToken));
|
||||
}, []);
|
||||
}, [authState.authToken, dispatch]);
|
||||
|
||||
const up = monitorState.monitors.reduce((acc, cur) => {
|
||||
return cur.status === true ? acc + 1 : acc;
|
||||
|
||||
@@ -3,6 +3,7 @@ const Check = require("../../../models/Check");
|
||||
const PageSpeedCheck = require("../../../models/PageSpeedCheck");
|
||||
const { errorMessages } = require("../../../utils/messages");
|
||||
const Notification = require("../../../models/Notification");
|
||||
const { NormalizeData } = require("../../../utils/dataUtils");
|
||||
|
||||
/**
|
||||
* Get all monitors
|
||||
@@ -32,7 +33,14 @@ const getAllMonitors = async (req, res) => {
|
||||
const getMonitorById = async (req, res) => {
|
||||
try {
|
||||
const { monitorId } = req.params;
|
||||
let { status, limit, sortOrder } = req.query;
|
||||
let { status, limit, sortOrder, filter, numToDisplay, normalize } =
|
||||
req.query;
|
||||
|
||||
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)),
|
||||
};
|
||||
|
||||
// This effectively removes limit, returning all checks
|
||||
if (limit === undefined) limit = 0;
|
||||
@@ -52,18 +60,35 @@ const getMonitorById = async (req, res) => {
|
||||
checksQuery.status = status;
|
||||
}
|
||||
|
||||
// Filter checks by "day", "week", or "month"
|
||||
if (filter !== undefined) {
|
||||
checksQuery.createdAt = { $gte: filterLookup[filter] };
|
||||
}
|
||||
|
||||
// Determine model type
|
||||
let model =
|
||||
monitor.type === "http" || monitor.type === "ping"
|
||||
? Check
|
||||
: PageSpeedCheck;
|
||||
|
||||
const checks = await model
|
||||
let checks = await model
|
||||
.find(checksQuery)
|
||||
.sort({
|
||||
createdAt: sortOrder,
|
||||
})
|
||||
.limit(limit);
|
||||
|
||||
// If more than numToDisplay checks, pick every nth check
|
||||
if (numToDisplay !== undefined && checks && checks.length > numToDisplay) {
|
||||
const n = Math.ceil(checks.length / numToDisplay);
|
||||
checks = checks.filter((_, index) => index % n === 0);
|
||||
}
|
||||
|
||||
// Normalize checks if requested
|
||||
if (normalize) {
|
||||
checks = NormalizeData(checks, 10, 100);
|
||||
}
|
||||
|
||||
const notifications = await Notification.find({ monitorId: monitor._id });
|
||||
const monitorWithChecks = { ...monitor.toObject(), checks, notifications };
|
||||
return monitorWithChecks;
|
||||
@@ -82,7 +107,7 @@ const getMonitorById = async (req, res) => {
|
||||
*/
|
||||
const getMonitorsByUserId = async (req, res) => {
|
||||
try {
|
||||
let { limit, type, status, sortOrder } = req.query;
|
||||
let { limit, type, status, sortOrder, normalize } = req.query;
|
||||
const monitorQuery = { userId: req.params.userId };
|
||||
|
||||
if (type !== undefined) {
|
||||
@@ -115,13 +140,18 @@ const getMonitorsByUserId = async (req, res) => {
|
||||
: PageSpeedCheck;
|
||||
|
||||
// Checks are order newest -> oldest
|
||||
const checks = await model
|
||||
let checks = await model
|
||||
.find(checksQuery)
|
||||
.sort({
|
||||
createdAt: sortOrder,
|
||||
})
|
||||
.limit(limit);
|
||||
|
||||
//Normalize checks if requested
|
||||
if (normalize === true) {
|
||||
checks = NormalizeData(checks, 10, 100);
|
||||
}
|
||||
|
||||
// Get notifications
|
||||
const notifications = await Notification.find({
|
||||
monitorId: monitor._id,
|
||||
|
||||
@@ -11,7 +11,7 @@ const calculatePercentile = (arr, percentile) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const NormalizeData = (checks, rangeMin, rangeMax) => {
|
||||
const NormalizeData = (checks, rangeMin, rangeMax) => {
|
||||
if (checks.length > 1) {
|
||||
// Get the 5th and 95th percentile
|
||||
const min = calculatePercentile(checks, 0);
|
||||
@@ -19,6 +19,7 @@ export 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 +
|
||||
@@ -31,7 +32,7 @@ export const NormalizeData = (checks, rangeMin, rangeMax) => {
|
||||
Math.min(rangeMax, normalizedResponseTime)
|
||||
);
|
||||
return {
|
||||
...check,
|
||||
...check._doc,
|
||||
responseTime: normalizedResponseTime,
|
||||
originalResponseTime: originalResponseTime,
|
||||
};
|
||||
@@ -44,3 +45,7 @@ export const NormalizeData = (checks, rangeMin, rangeMax) => {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
NormalizeData,
|
||||
};
|
||||
@@ -126,6 +126,9 @@ const getMonitorByIdQueryValidation = joi.object({
|
||||
status: joi.boolean(),
|
||||
sortOrder: joi.string().valid("asc", "desc"),
|
||||
limit: joi.number(),
|
||||
filter: joi.string().valid("day", "week", "month"),
|
||||
numToDisplay: joi.number(),
|
||||
normalize: joi.boolean(),
|
||||
});
|
||||
|
||||
const getMonitorsByUserIdValidation = joi.object({
|
||||
@@ -136,6 +139,7 @@ const getMonitorsByUserIdQueryValidation = joi.object({
|
||||
status: joi.boolean(),
|
||||
sortOrder: joi.string().valid("asc", "desc"),
|
||||
limit: joi.number(),
|
||||
normalize: joi.boolean(),
|
||||
type: joi
|
||||
.alternatives()
|
||||
.try(
|
||||
|
||||
Reference in New Issue
Block a user