Merge pull request #265 from bluewave-labs/feat/recharts

Feat/recharts, resolves #264
This commit is contained in:
Alexander Holliday
2024-07-05 09:48:43 -07:00
committed by GitHub
8 changed files with 279 additions and 85 deletions
@@ -1,63 +0,0 @@
import "./barChart.css";
import PropTypes from "prop-types";
const RANGE_MAX = 100;
const RANGE_MIN = 1;
/**
* Normalizes the response times of a set of checks to a specified range.
* This function calculates the minimum and maximum response times from the input,
* then scales each check's response time linearly between `RANGE_MIN` and `RANGE_MAX`.
*
* @param {Array<Checks>} checks - An array of check objects. Each check should have a `responseTime` property.
* @returns {Array<Checks>} An array of check objects with normalized `responseTime` values.
*/
const normalizeData = (checks) => {
const min = checks.reduce((accum, cur) => {
return Math.min(accum, cur.responseTime);
}, Infinity);
const max = checks.reduce((accum, cur) => {
return Math.max(accum, cur.responseTime);
}, 0);
const normalizedChecks = checks.map((check) => {
let normailzedResponseTime =
RANGE_MIN +
((check.responseTime - min) * (RANGE_MAX - RANGE_MIN)) / (max - min);
const normalizedCheck = { ...check, responseTime: normailzedResponseTime };
return normalizedCheck;
});
return normalizedChecks;
};
/**
* BarChart renders a bar chart visualization for a set of checks. Each bar represents a check's response time,
* with the color indicating the check's status (green for successful checks, red for failed ones).
*
* @component
* @param {Array<Checks>} checks - An array of check objects to be visualized. Each check object should have an `_id`,
* a `status` indicating success (true) or failure (false), and a `responseTime` representing the height of the bar in percentage.
*/
const BarChart = ({ checks = [] }) => {
const normailzedChecks = normalizeData(checks);
return (
<div className="bar-chart">
{normailzedChecks.map((check) => {
return (
<div
key={check._id}
className={`bar ${check.status ? "green" : "red"}`}
style={{ height: `${check.responseTime}%` }}
/>
);
})}
</div>
);
};
BarChart.propTypes = {
checks: PropTypes.array,
};
export default BarChart;
@@ -1,20 +0,0 @@
.bar-chart {
display: flex;
justify-content: space-around;
align-items: flex-end;
height: 50px;
width: 300px;
}
.bar {
width: 7%;
transition: height 0.3s ease;
}
.green {
background-color: var(--env-var-color-23);
}
.red {
background-color: var(--env-var-color-24);
}
@@ -0,0 +1,7 @@
.chart-container {
display: flex;
justify-content: space-around;
align-items: flex-end;
height: 50px;
width: 300px;
}
@@ -0,0 +1,78 @@
import "./index.css";
import PropTypes from "prop-types";
import { BarChart, Bar, ResponsiveContainer, Cell } from "recharts";
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
);
};
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;
}
};
const ResponseTimeChart = ({ checks = [] }) => {
const normalizedChecks = normalizeData(checks);
return (
<div className="chart-container">
<ResponsiveContainer width="100%" height="100%">
<BarChart width={150} height={40} data={normalizedChecks}>
<Bar maxBarSize={10} dataKey="responseTime">
{normalizedChecks.map((check, index) => (
<Cell
key={`cell-${index}`}
fill={
check.status === true
? "var(--env-var-color-23)"
: "var(--env-var-color-24)"
}
/>
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
);
};
ResponseTimeChart.propTypes = {
checks: PropTypes.array,
};
export default ResponseTimeChart;
+2 -2
View File
@@ -1,7 +1,7 @@
import BarChart from "../Charts/BarChart/BarChart";
import PropTypes from "prop-types";
import Host from "../Host/";
import HostStatus from "../HostStatus";
import ResponseTimeChart from "../Charts/ResponseTimeChart";
/**
* HostsTable displays the current status of monitor
*
@@ -59,7 +59,7 @@ const HostsTable = ({ monitors }) => {
<td className="tbody-row-cell">{host}</td>
<td className="tbody-row-cell">{status}</td>
<td className="tbody-row-cell">
<BarChart checks={monitor.checks} />
<ResponseTimeChart checks={monitor.checks} />
</td>
<td className="tbody-row-cell actions-cell">
{monitor.actions}