diff --git a/Client/src/Components/Charts/ChartUtils.js b/Client/src/Components/Charts/ChartUtils.js new file mode 100644 index 000000000..a2c0faa9d --- /dev/null +++ b/Client/src/Components/Charts/ChartUtils.js @@ -0,0 +1,45 @@ +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); + const lower = Math.floor(index); + const upper = lower + 1; + const weight = index % 1; + if (upper >= sorted.length) return sorted[lower].responseTime; + return ( + sorted[lower].responseTime * (1 - weight) + + sorted[upper].responseTime * weight + ); +}; + +export const NormalizeData = (checks) => { + if (checks.length > 1) { + // Get the 5th and 95th percentile + const min = calculatePercentile(checks, 5); + const max = calculatePercentile(checks, 95); + + const normalizedChecks = checks.map((check) => { + // Normalize the response time between 1 and 100 + let normalizedResponseTime = + RANGE_MIN + + ((check.responseTime - min) * (RANGE_MAX - RANGE_MIN)) / (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) + ); + return { + ...check, + responseTime: normalizedResponseTime, + }; + }); + + return normalizedChecks; + } else { + return checks; + } +}; diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.css b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.css new file mode 100644 index 000000000..e69de29bb diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx new file mode 100644 index 000000000..2b5074ccb --- /dev/null +++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx @@ -0,0 +1,69 @@ +import PropTypes from "prop-types"; +import { + AreaChart, + Area, + XAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, +} from "recharts"; +import { NormalizeData } from "../ChartUtils"; + +const MonitorDetailsAreaChart = ({ checks }) => { + // SQUASH ERROR, NOT PERMANENT SOLUTION + const error = console.error; + console.error = (...args) => { + if (/defaultProps/.test(args[0])) return; + error(...args); + }; + + // END SQUASH ERROR, NOT PERMANENT SOLUTION + + const formatDate = (timestamp) => { + const date = new Date(timestamp); + return date.toLocaleTimeString("en-US", { + hour: "numeric", + minute: "2-digit", + hour12: true, + }); + }; + + let normalizedChecks = []; + if (checks && checks.length > 0) { + normalizedChecks = NormalizeData(checks); + } + + return ( + + + + + + + + ); +}; + +MonitorDetailsAreaChart.propTypes = { + checks: PropTypes.array, +}; +export default MonitorDetailsAreaChart; diff --git a/Client/src/Pages/Details/index.jsx b/Client/src/Pages/Details/index.jsx index c53054ccd..e251cbfd0 100644 --- a/Client/src/Pages/Details/index.jsx +++ b/Client/src/Pages/Details/index.jsx @@ -4,7 +4,7 @@ import { useSelector } from "react-redux"; import { useParams } from "react-router-dom"; import axiosInstance from "../../Utils/axiosConfig"; import BasicTable from "../../Components/BasicTable"; - +import MonitorDetailsAreaChart from "../../Components/Charts/MonitorDetailsAreaChart"; /** * Details page component displaying monitor details and related information. * @component @@ -107,6 +107,9 @@ const DetailsPage = () => { return (
+
+ +
);