Refactored ChartUtils to allow for variable normalization parameters, Added day, week, and month filters to Details chart

This commit is contained in:
Alex Holliday committed 2024-07-17 23:00:24 -07:00
1 parent 07719aad2b
commit 9cb2abd62a
4 files changed
+91 -26

No files matched your search

+8 -9
View File
@@ -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,
};
});
@@ -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 (
<div style={{ backgroundColor: "white" }}>
<p>
{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
})}
</p>
<p>Response Time (ms): {payload[0].payload.originalResponseTime}</p>{" "}
{/* Display original value */}
</div>
);
}
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" }}
/>
<Tooltip />
<Tooltip content={<CustomToolTip />} />
<Area
type="monotone"
dataKey="responseTime"
@@ -65,5 +104,18 @@ const MonitorDetailsAreaChart = ({ checks }) => {
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;
@@ -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 (
<div className="chart-container">
<ResponsiveContainer width="100%" height="100%">
+19 -5
View File
@@ -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
</Typography>
<ButtonGroup>
<Button level="secondary" label="Day" />
<Button level="secondary" label="Week" />
<Button level="secondary" label="Month" />
<Button
level="secondary"
label="Day"
onClick={() => setFilter("day")}
/>
<Button
level="secondary"
label="Week"
onClick={() => setFilter("week")}
/>
<Button
level="secondary"
label="Month"
onClick={() => setFilter("month")}
/>
</ButtonGroup>
</div>
<div style={{ height: "10rem" }}>
<MonitorDetailsAreaChart checks={monitor.checks} />
<div style={{ height: "33vh" }}>
<MonitorDetailsAreaChart checks={monitor.checks} filter={filter} />
</div>
<Typography component="h1" mb={theme.gap.small}>
History