From 9cb2abd62adcb03ff741edcc039ba506b561bcca Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 17 Jul 2024 23:00:24 -0700 Subject: [PATCH] Refactored ChartUtils to allow for variable normalization parameters, Added day, week, and month filters to Details chart --- Client/src/Components/Charts/ChartUtils.js | 17 ++--- .../Charts/MonitorDetailsAreaChart/index.jsx | 74 ++++++++++++++++--- .../Charts/ResponseTimeChart/index.jsx | 2 +- Client/src/Pages/Monitors/Details/index.jsx | 24 ++++-- 4 files changed, 91 insertions(+), 26 deletions(-) diff --git a/Client/src/Components/Charts/ChartUtils.js b/Client/src/Components/Charts/ChartUtils.js index a2c0faa9d..a484622ec 100644 --- a/Client/src/Components/Charts/ChartUtils.js +++ b/Client/src/Components/Charts/ChartUtils.js @@ -1,6 +1,3 @@ -const RANGE_MAX = 100; -const RANGE_MIN = 1; - const calculatePercentile = (arr, percentile) => { const sorted = arr.slice().sort((a, b) => a.responseTime - b.responseTime); const index = (percentile / 100) * (sorted.length - 1); @@ -14,27 +11,29 @@ const calculatePercentile = (arr, percentile) => { ); }; -export const NormalizeData = (checks) => { +export const NormalizeData = (checks, rangeMin, rangeMax) => { if (checks.length > 1) { // Get the 5th and 95th percentile - const min = calculatePercentile(checks, 5); + const min = calculatePercentile(checks, 0); const max = calculatePercentile(checks, 95); const normalizedChecks = checks.map((check) => { + const originalResponseTime = check.responseTime; // Normalize the response time between 1 and 100 let normalizedResponseTime = - RANGE_MIN + - ((check.responseTime - min) * (RANGE_MAX - RANGE_MIN)) / (max - min); + rangeMin + + ((check.responseTime - min) * (rangeMax - rangeMin)) / (max - min); // Put a floor on the response times so we don't have extreme outliers // Better visuals normalizedResponseTime = Math.max( - RANGE_MIN, - Math.min(RANGE_MAX, normalizedResponseTime) + rangeMin, + Math.min(rangeMax, normalizedResponseTime) ); return { ...check, responseTime: normalizedResponseTime, + originalResponseTime: originalResponseTime, }; }); diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx index 2b5074ccb..1d1ea3758 100644 --- a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx +++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx @@ -1,15 +1,33 @@ import PropTypes from "prop-types"; -import { - AreaChart, - Area, - XAxis, - CartesianGrid, - Tooltip, - ResponsiveContainer, -} from "recharts"; +import { AreaChart, Area, XAxis, Tooltip, ResponsiveContainer } from "recharts"; import { NormalizeData } from "../ChartUtils"; -const MonitorDetailsAreaChart = ({ checks }) => { +const CustomToolTip = ({ active, payload, label }) => { + if (active && payload && payload.length) { + return ( +
+

+ {new Date(label).toLocaleDateString("en-US", { + weekday: "short", // Mon + month: "long", // July + day: "numeric", // 17 + }) + + ", " + + new Date(label).toLocaleTimeString("en-US", { + hour: "numeric", // 12 + minute: "2-digit", // 15 + hour12: true, // AM/PM format + })} +

+

Response Time (ms): {payload[0].payload.originalResponseTime}

{" "} + {/* Display original value */} +
+ ); + } + return null; +}; + +const MonitorDetailsAreaChart = ({ checks, filter }) => { // SQUASH ERROR, NOT PERMANENT SOLUTION const error = console.error; console.error = (...args) => { @@ -28,9 +46,30 @@ const MonitorDetailsAreaChart = ({ checks }) => { }); }; + const filterChecks = (checks, filter) => { + 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(); + const 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]); + } else { + break; + } + } + return result; + }; + let normalizedChecks = []; if (checks && checks.length > 0) { - normalizedChecks = NormalizeData(checks); + const filteredChecks = filterChecks(checks, filter); + normalizedChecks = NormalizeData(filteredChecks, 33, 100); } return ( @@ -51,7 +90,7 @@ const MonitorDetailsAreaChart = ({ checks }) => { tickFormatter={formatDate} tick={{ fontSize: "13px" }} /> - + } /> { MonitorDetailsAreaChart.propTypes = { checks: PropTypes.array, + filter: PropTypes.string, +}; + +CustomToolTip.propTypes = { + active: PropTypes.bool, + payload: PropTypes.arrayOf( + PropTypes.shape({ + payload: PropTypes.shape({ + originalResponseTime: PropTypes.number.isRequired, + }).isRequired, + }) + ), + label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), }; export default MonitorDetailsAreaChart; diff --git a/Client/src/Components/Charts/ResponseTimeChart/index.jsx b/Client/src/Components/Charts/ResponseTimeChart/index.jsx index 7a9bf71b2..d76abc196 100644 --- a/Client/src/Components/Charts/ResponseTimeChart/index.jsx +++ b/Client/src/Components/Charts/ResponseTimeChart/index.jsx @@ -4,7 +4,7 @@ import { BarChart, Bar, ResponsiveContainer, Cell } from "recharts"; import { NormalizeData } from "../ChartUtils"; const ResponseTimeChart = ({ checks = [] }) => { - const normalizedChecks = NormalizeData(checks); + const normalizedChecks = NormalizeData(checks, 1, 100); return (
diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index ebdd34611..74177b583 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -46,6 +46,8 @@ const DetailsPage = () => { const [data, setData] = useState({}); const { monitorId } = useParams(); const { authToken } = useSelector((state) => state.auth); + const [filter, setFilter] = useState("day"); + useEffect(() => { const fetchMonitor = async () => { const res = await axiosInstance.get(`/monitors/${monitorId}`, { @@ -180,13 +182,25 @@ const DetailsPage = () => { Response Times -
-
- +
+
History