Refactored ChartUtils to allow for variable normalization parameters, Added day, week, and month filters to Details chart
This commit is contained in:
1 parent
07719aad2b
commit
9cb2abd62a
4 files changed
+91
-26
No files matched your search
@@ -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%">
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user