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:
Alexander Holliday
2024-08-08 14:22:18 -07:00
committed by GitHub
8 changed files with 125 additions and 51 deletions
@@ -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}`,
+74 -3
View File
@@ -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.
+1 -1
View File
@@ -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;
+34 -4
View File
@@ -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,
};
+4
View File
@@ -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(