Merge pull request #1548 from bluewave-labs/fix/be/uptime-duration

fix: be/uptime duration
This commit is contained in:
Alexander Holliday
2025-01-10 12:14:33 -08:00
committed by GitHub
5 changed files with 102 additions and 104 deletions

View File

@@ -4,7 +4,7 @@ import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts";
import PropTypes from "prop-types";
import CustomLabels from "./CustomLabels";
const DownBarChart = memo(({ stats, type, onBarHover }) => {
const DownBarChart = memo(({ monitor, type, onBarHover }) => {
const theme = useTheme();
const [chartHovered, setChartHovered] = useState(false);
@@ -19,7 +19,7 @@ const DownBarChart = memo(({ stats, type, onBarHover }) => {
<BarChart
width="100%"
height="100%"
data={stats.downChecks}
data={monitor.groupedDownChecks}
onMouseEnter={() => {
setChartHovered(true);
onBarHover({ time: null, totalChecks: 0 });
@@ -40,8 +40,10 @@ const DownBarChart = memo(({ stats, type, onBarHover }) => {
y={0}
width="100%"
height="100%"
firstDataPoint={stats.downChecks?.[0] ?? {}}
lastDataPoint={stats.downChecks?.[stats.downChecks.length - 1] ?? {}}
firstDataPoint={monitor.groupedDownChecks?.[0] ?? {}}
lastDataPoint={
monitor.groupedDownChecks?.[monitor.groupedDownChecks.length - 1] ?? {}
}
type={type}
/>
}
@@ -51,7 +53,7 @@ const DownBarChart = memo(({ stats, type, onBarHover }) => {
maxBarSize={7}
background={{ fill: "transparent" }}
>
{stats.downChecks.map((entry, index) => (
{monitor.groupedDownChecks.map((entry, index) => (
<Cell
key={`cell-${entry.time}`}
fill={
@@ -79,9 +81,8 @@ const DownBarChart = memo(({ stats, type, onBarHover }) => {
DownBarChart.displayName = "DownBarChart";
DownBarChart.propTypes = {
stats: PropTypes.shape({
downChecks: PropTypes.arrayOf(PropTypes.object),
downChecksAggregate: PropTypes.object,
monitor: PropTypes.shape({
groupedDownChecks: PropTypes.arrayOf(PropTypes.object),
}),
type: PropTypes.string,
onBarHover: PropTypes.func,

View File

@@ -4,7 +4,7 @@ import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts";
import PropTypes from "prop-types";
import CustomLabels from "./CustomLabels";
const UpBarChart = memo(({ stats, type, onBarHover }) => {
const UpBarChart = memo(({ monitor, type, onBarHover }) => {
const theme = useTheme();
const [chartHovered, setChartHovered] = useState(false);
const [hoveredBarIndex, setHoveredBarIndex] = useState(null);
@@ -26,7 +26,7 @@ const UpBarChart = memo(({ stats, type, onBarHover }) => {
<BarChart
width="100%"
height="100%"
data={stats.upChecks}
data={monitor.groupedUpChecks}
onMouseEnter={() => {
setChartHovered(true);
onBarHover({ time: null, totalChecks: 0, avgResponseTime: 0 });
@@ -47,8 +47,8 @@ const UpBarChart = memo(({ stats, type, onBarHover }) => {
y={0}
width="100%"
height="100%"
firstDataPoint={stats.upChecks[0]}
lastDataPoint={stats.upChecks[stats.upChecks.length - 1]}
firstDataPoint={monitor.groupedUpChecks[0]}
lastDataPoint={monitor.groupedUpChecks[monitor.groupedUpChecks.length - 1]}
type={type}
/>
}
@@ -58,7 +58,7 @@ const UpBarChart = memo(({ stats, type, onBarHover }) => {
maxBarSize={7}
background={{ fill: "transparent" }}
>
{stats.upChecks.map((entry, index) => {
{monitor.groupedUpChecks.map((entry, index) => {
let { main, light } = getColorRange(entry.avgResponseTime);
return (
<Cell
@@ -90,9 +90,8 @@ UpBarChart.displayName = "UpBarChart";
// Validate props using PropTypes
UpBarChart.propTypes = {
stats: PropTypes.shape({
upChecks: PropTypes.array,
upChecksAggregate: PropTypes.object,
monitor: PropTypes.shape({
groupedUpChecks: PropTypes.array,
}),
type: PropTypes.string,
onBarHover: PropTypes.func,

View File

@@ -221,17 +221,17 @@ const DetailsPage = () => {
<StatBox
sx={statusStyles[determineState(monitor)]}
heading={"active for"}
subHeading={splitDuration(monitor?.stats?.timeSinceLastFalseCheck)}
subHeading={splitDuration(monitor?.uptimeStreak)}
/>
<StatBox
heading="last check"
subHeading={splitDuration(monitor?.stats?.timeSinceLastCheck)}
subHeading={splitDuration(monitor?.timeSinceLastCheck)}
/>
<StatBox
heading="last response time"
subHeading={
<>
{monitor?.stats?.latestResponseTime}
{monitor?.latestResponseTime}
<Typography component="span">{"ms"}</Typography>
</>
}
@@ -308,7 +308,7 @@ const DetailsPage = () => {
<Typography component="span">
{hoveredUptimeData !== null
? hoveredUptimeData.totalChecks
: (monitor.stats?.upChecks?.reduce((count, checkGroup) => {
: (monitor?.groupedUpChecks?.reduce((count, checkGroup) => {
return count + checkGroup.totalChecks;
}, 0) ?? 0)}
</Typography>
@@ -338,8 +338,8 @@ const DetailsPage = () => {
{hoveredUptimeData !== null
? Math.floor(hoveredUptimeData?.avgResponseTime ?? 0)
: Math.floor(
((monitor?.stats?.upChecksAggregate?.totalChecks ?? 0) /
(monitor?.stats?.totalChecks ?? 1)) *
((monitor?.upChecks?.totalChecks ?? 0) /
(monitor?.totalChecks ?? 1)) *
100
)}
<Typography component="span">
@@ -349,7 +349,7 @@ const DetailsPage = () => {
</Box>
</Stack>
<UpBarChart
stats={monitor?.stats}
monitor={monitor}
type={dateRange}
onBarHover={setHoveredUptimeData}
/>
@@ -366,7 +366,7 @@ const DetailsPage = () => {
<Typography component="span">
{hoveredIncidentsData !== null
? hoveredIncidentsData.totalChecks
: (monitor.stats?.downChecks?.reduce((count, checkGroup) => {
: (monitor?.groupedDownChecks?.reduce((count, checkGroup) => {
return count + checkGroup.totalChecks;
}, 0) ?? 0)}
</Typography>
@@ -388,7 +388,7 @@ const DetailsPage = () => {
)}
</Box>
<DownBarChart
stats={monitor?.stats}
monitor={monitor}
type={dateRange}
onBarHover={setHoveredIncidentsData}
/>
@@ -400,9 +400,7 @@ const DetailsPage = () => {
</IconBox>
<Typography component="h2">Average Response Time</Typography>
</Stack>
<ResponseGaugeChart
avgResponseTime={monitor?.stats?.groupAggregate?.avgResponseTime ?? 0}
/>
<ResponseGaugeChart avgResponseTime={monitor.avgResponseTime ?? 0} />
</ChartBox>
<ChartBox sx={{ padding: 0 }}>
<Stack
@@ -415,7 +413,7 @@ const DetailsPage = () => {
<Typography component="h2">Response Times</Typography>
</Stack>
<MonitorDetailsAreaChart
checks={monitor?.stats?.groupChecks ?? []}
checks={monitor.groupedChecks ?? []}
dateRange={dateRange}
/>
</ChartBox>

View File

@@ -340,31 +340,21 @@ const getUptimeDetailsById = async (req) => {
};
const dateString = formatLookup[dateRange];
const monitorData = await Check.aggregate(
const results = await Check.aggregate(
buildUptimeDetailsPipeline(monitor, dates, dateString)
);
const monitorData = results[0];
const normalizedGroupChecks = NormalizeDataUptimeDetails(
monitorData[0].groupChecks,
monitorData.groupedChecks,
10,
100
);
const monitorStats = {
...monitor.toObject(),
stats: {
avgResponseTime: monitorData[0].avgResponseTime,
totalChecks: monitorData[0].totalChecks,
timeSinceLastCheck: monitorData[0].timeSinceLastCheck,
timeSinceLastFalseCheck: monitorData[0].timeSinceLastFalseCheck,
latestResponseTime: monitorData[0].latestResponseTime,
groupChecks: normalizedGroupChecks,
groupAggregate: monitorData[0].groupAggregate,
upChecksAggregate: monitorData[0].upChecksAggregate,
upChecks: monitorData[0].upChecks,
downChecksAggregate: monitorData[0].downChecksAggregate,
downChecks: monitorData[0].downChecks,
},
...monitorData,
groupedChecks: normalizedGroupChecks,
};
return monitorStats;

View File

@@ -19,9 +19,6 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => {
avgResponseTime: {
$avg: "$responseTime",
},
firstCheck: {
$first: "$$ROOT",
},
lastCheck: {
$last: "$$ROOT",
},
@@ -31,27 +28,52 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => {
},
},
],
uptimeDuration: [
{
$match: {
status: false,
},
},
uptimeStreak: [
{
$sort: {
createdAt: 1,
createdAt: -1,
},
},
{
$group: {
_id: null,
lastFalseCheck: {
$last: "$$ROOT",
checks: { $push: "$$ROOT" },
},
},
{
$project: {
streak: {
$reduce: {
input: "$checks",
initialValue: { checks: [], foundFalse: false },
in: {
$cond: [
{
$and: [
{ $not: "$$value.foundFalse" }, // stop reducing if a false check has been found
{ $eq: ["$$this.status", true] }, // continue reducing if current check true
],
},
// true case
{
checks: { $concatArrays: ["$$value.checks", ["$$this"]] },
foundFalse: false, // Add the check to the streak
},
// false case
{
checks: "$$value.checks",
foundFalse: true, // Mark that we found a false
},
],
},
},
},
},
},
],
groupChecks: [
// For the response time chart, should return checks for date window
// Grouped by: {day: hour}, {week: day}, {month: day}
groupedChecks: [
{
$match: {
createdAt: { $gte: dates.start, $lte: dates.end },
@@ -79,7 +101,8 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => {
},
},
],
groupAggregate: [
// Average response time for the date window
groupAvgResponseTime: [
{
$match: {
createdAt: { $gte: dates.start, $lte: dates.end },
@@ -94,10 +117,12 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => {
},
},
],
upChecksAggregate: [
// All UpChecks for the date window
upChecks: [
{
$match: {
status: true,
createdAt: { $gte: dates.start, $lte: dates.end },
},
},
{
@@ -112,7 +137,8 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => {
},
},
],
upChecks: [
// Up checks grouped by: {day: hour}, {week: day}, {month: day}
groupedUpChecks: [
{
$match: {
status: true,
@@ -139,10 +165,12 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => {
$sort: { _id: 1 },
},
],
downChecksAggregate: [
// All down checks for the date window
downChecks: [
{
$match: {
status: false,
createdAt: { $gte: dates.start, $lte: dates.end },
},
},
{
@@ -157,7 +185,8 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => {
},
},
],
downChecks: [
// Down checks grouped by: {day: hour}, {week: day}, {month: day} for the date window
groupedDownChecks: [
{
$match: {
status: false,
@@ -188,6 +217,20 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => {
},
{
$project: {
uptimeStreak: {
$cond: [
{ $eq: [{ $size: { $first: "$uptimeStreak.streak.checks" } }, 0] },
0,
{
$subtract: [
new Date(),
{
$last: { $first: "$uptimeStreak.streak.checks.createdAt" },
},
],
},
],
},
avgResponseTime: {
$arrayElemAt: ["$aggregateData.avgResponseTime", 0],
},
@@ -217,51 +260,18 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => {
},
},
},
timeSinceLastFalseCheck: {
$let: {
vars: {
lastFalseCheck: {
$arrayElemAt: ["$uptimeDuration.lastFalseCheck", 0],
},
firstCheck: {
$arrayElemAt: ["$aggregateData.firstCheck", 0],
},
},
in: {
$cond: [
{
$ifNull: ["$$lastFalseCheck", false],
},
{
$subtract: [new Date(), "$$lastFalseCheck.createdAt"],
},
{
$cond: [
{
$ifNull: ["$$firstCheck", false],
},
{
$subtract: [new Date(), "$$firstCheck.createdAt"],
},
0,
],
},
],
},
},
groupedChecks: "$groupedChecks",
groupedAvgResponseTime: {
$arrayElemAt: ["$groupAvgResponseTime", 0],
},
groupChecks: "$groupChecks",
groupAggregate: {
$arrayElemAt: ["$groupAggregate", 0],
upChecks: {
$arrayElemAt: ["$upChecks", 0],
},
upChecksAggregate: {
$arrayElemAt: ["$upChecksAggregate", 0],
groupedUpChecks: "$groupedUpChecks",
downChecks: {
$arrayElemAt: ["$downChecks", 0],
},
upChecks: "$upChecks",
downChecksAggregate: {
$arrayElemAt: ["$downChecksAggregate", 0],
},
downChecks: "$downChecks",
groupedDownChecks: "$groupedDownChecks",
},
},
];