Merge pull request #3016 from bluewave-labs/fix/v2/normalization

Fix/v2/normalization
This commit is contained in:
Alexander Holliday authored and GitHub committed 2025-10-09 13:56:10 -07:00
commit c7799ca7dc
8 files changed
+104 -61

No files matched your search

@@ -5,7 +5,13 @@ import type { MonitorStatus } from "@/Types/Monitor";
import { getStatusPalette } from "@/Utils/MonitorUtils";
import { useTheme } from "@mui/material/styles";
export const StatusLabel = ({ status }: { status: MonitorStatus }) => {
export const StatusLabel = ({
status,
isActive,
}: {
status: MonitorStatus;
isActive?: boolean;
}) => {
const theme = useTheme();
const palette = getStatusPalette(status);
const transformedText = status.charAt(0).toUpperCase() + status.slice(1).toLowerCase();
@@ -29,7 +35,7 @@ export const StatusLabel = ({ status }: { status: MonitorStatus }) => {
borderRadius="50%"
marginRight="5px"
/>
{transformedText}
{isActive === false ? "Paused" : transformedText}
</BaseBox>
);
};
@@ -1,12 +1,12 @@
import Notifications from "@/assets/icons/notifications.svg?react";
// import Notifications from "@/assets/icons/notifications.svg?react";
import Monitors from "@/assets/icons/monitors.svg?react";
import PageSpeed from "@/assets/icons/page-speed.svg?react";
import Integrations from "@/assets/icons/integrations.svg?react";
import Incidents from "@/assets/icons/incidents.svg?react";
import StatusPages from "@/assets/icons/status-pages.svg?react";
import Maintenance from "@/assets/icons/maintenance.svg?react";
import Logs from "@/assets/icons/logs.svg?react";
import Settings from "@/assets/icons/settings.svg?react";
// import PageSpeed from "@/assets/icons/page-speed.svg?react";
// import Integrations from "@/assets/icons/integrations.svg?react";
// import Incidents from "@/assets/icons/incidents.svg?react";
// import StatusPages from "@/assets/icons/status-pages.svg?react";
// import Maintenance from "@/assets/icons/maintenance.svg?react";
// import Logs from "@/assets/icons/logs.svg?react";
// import Settings from "@/assets/icons/settings.svg?react";
import Support from "@/assets/icons/support.svg?react";
import Discussions from "@/assets/icons/discussions.svg?react";
import Docs from "@/assets/icons/docs.svg?react";
@@ -14,25 +14,25 @@ import ChangeLog from "@/assets/icons/changeLog.svg?react";
export const getMenu = (t: Function) => [
{ name: t("menu.uptime"), path: "v2/uptime", icon: <Monitors /> },
{ name: t("menu.pagespeed"), path: "pagespeed", icon: <PageSpeed /> },
// { name: t("menu.pagespeed"), path: "pagespeed", icon: <PageSpeed /> },
{ name: t("menu.infrastructure"), path: "infrastructure", icon: <Integrations /> },
{
name: t("menu.notifications"),
path: "notifications",
icon: <Notifications />,
},
{ name: t("menu.incidents"), path: "incidents", icon: <Incidents /> },
// { name: t("menu.infrastructure"), path: "infrastructure", icon: <Integrations /> },
// {
// name: t("menu.notifications"),
// path: "notifications",
// icon: <Notifications />,
// },
// { name: t("menu.incidents"), path: "incidents", icon: <Incidents /> },
{ name: t("menu.statusPages"), path: "status", icon: <StatusPages /> },
{ name: t("menu.maintenance"), path: "maintenance", icon: <Maintenance /> },
{ name: t("menu.logs"), path: "logs", icon: <Logs /> },
// { name: t("menu.statusPages"), path: "status", icon: <StatusPages /> },
// { name: t("menu.maintenance"), path: "maintenance", icon: <Maintenance /> },
// { name: t("menu.logs"), path: "logs", icon: <Logs /> },
{
name: t("menu.settings"),
icon: <Settings />,
path: "settings",
},
// {
// name: t("menu.settings"),
// icon: <Settings />,
// path: "settings",
// },
];
export const getBottomMenu = (t: Function) => [
@@ -1,6 +1,7 @@
import { BaseChart } from "./HistogramStatus";
import { BaseBox } from "../DesignElements";
import ResponseTimeIcon from "@/assets/icons/response-time-icon.svg?react";
import { normalizeResponseTimes } from "@/Utils/DataUtils";
import {
AreaChart,
Area,
@@ -68,7 +69,7 @@ const ResponseTimeToolTip: React.FC<ResponseTimeToolTipProps> = ({
if (!active) return null;
const format = tooltipDateFormatLookup(range);
const responseTime = Math.floor(payload?.[0]?.value || 0);
const responseTime = Math.floor(payload?.[0]?.payload?.avgResponseTime || 0);
return (
<BaseBox sx={{ py: theme.spacing(2), px: theme.spacing(4) }}>
<Typography>{formatDateWithTz(label, format, uiTimezone)}</Typography>
@@ -86,7 +87,10 @@ export const ChartResponseTime = ({
}) => {
const theme = useTheme();
const uiTimezone = useSelector((state: any) => state.ui.timezone);
const normalized = normalizeResponseTimes<GroupedCheck, "avgResponseTime">(
checks,
"avgResponseTime"
);
return (
<BaseChart
icon={<ResponseTimeIcon />}
@@ -96,7 +100,7 @@ export const ChartResponseTime = ({
width="100%"
height={300}
>
<AreaChart data={checks?.slice().reverse()}>
<AreaChart data={normalized?.slice().reverse()}>
<CartesianGrid
stroke={theme.palette.primary.lowContrast}
strokeWidth={1}
@@ -148,7 +152,7 @@ export const ChartResponseTime = ({
/>
<Area
type="monotone"
dataKey="avgResponseTime"
dataKey="normalResponseTime"
stroke={theme.palette.accent.main}
fill="url(#colorUv)"
/>
@@ -3,24 +3,20 @@ import Box from "@mui/material/Box";
import { useTheme } from "@mui/material/styles";
import type { Check } from "@/Types/Check";
import { HistogramResponseTimeTooltip } from "@/Components/v2/Monitors/HistogramResponseTimeTooltip";
import { normalizeResponseTimes } from "@/Utils/DataUtils";
export const HistogramResponseTime = ({ checks }: { checks: Check[] }) => {
const normalChecks = normalizeResponseTimes(checks, "responseTime");
let data = Array<any>();
data = checks.map((c) => Math.max(c.responseTime, 1));
const logResponses = data.map((r) => Math.log10(r));
const logMin = Math.min(...logResponses);
const logMax = Math.max(...logResponses);
if (!checks) {
if (!normalChecks || normalChecks.length === 0) {
return null;
}
if (checks.length !== 25) {
const placeholders = Array(25 - checks.length).fill("placeholder");
data = [...checks, ...placeholders];
if (normalChecks.length !== 25) {
const placeholders = Array(25 - normalChecks.length).fill("placeholder");
data = [...normalChecks, ...placeholders];
} else {
data = checks;
data = normalChecks;
}
const theme = useTheme();
@@ -38,14 +34,6 @@ export const HistogramResponseTime = ({ checks }: { checks: Check[] }) => {
}}
>
{data.map((check, index) => {
const safeResponse = Math.max(check.responseTime, 1);
const logValue = Math.log10(safeResponse);
const minHeight = 10;
const barHeight =
logMax === logMin
? 100
: Math.max(minHeight, ((logValue - logMin) / (logMax - logMin)) * 100);
if (check === "placeholder") {
return (
<Box
@@ -78,7 +66,7 @@ export const HistogramResponseTime = ({ checks }: { checks: Check[] }) => {
position="absolute"
bottom={0}
width="100%"
height={`${barHeight}%`}
height={`${check.normalResponseTime}%`}
bgcolor={
check.status
? theme.palette.success.lowContrast
@@ -9,6 +9,7 @@ import IncidentsIcon from "@/assets/icons/incidents.svg?react";
import type { GroupedCheck } from "@/Types/Check";
import type { MonitorStatus } from "@/Types/Monitor";
import { normalizeResponseTimes } from "@/Utils/DataUtils";
import { useState } from "react";
import { formatDateWithTz } from "@/Utils/TimeUtils";
import { useSelector } from "react-redux";
@@ -122,8 +123,9 @@ export const HistogramStatus = ({
const theme = useTheme();
const [idx, setIdx] = useState<number | null>(null);
const dateFormat = range === "1d" || range === "2h" ? "MMM D, h A" : "MMM D";
const normalChecks = normalizeResponseTimes(checks, "avgResponseTime");
if (checks.length === 0) {
if (normalChecks.length === 0) {
return (
<BaseChart
icon={icon}
@@ -142,7 +144,7 @@ export const HistogramStatus = ({
);
}
const totalChecks = checks.reduce((count, check) => {
const totalChecks = normalChecks.reduce((count, check) => {
return count + check.count;
}, 0);
@@ -161,12 +163,12 @@ export const HistogramStatus = ({
<Typography>Total checks</Typography>
{idx ? (
<Stack>
<Typography variant="h2">{checks[idx].count}</Typography>
<Typography variant="h2">{normalChecks[idx].count}</Typography>
<Typography
position={"absolute"}
top={"100%"}
>
{formatDateWithTz(checks[idx]._id, dateFormat, uiTimezone)}
{formatDateWithTz(normalChecks[idx]._id, dateFormat, uiTimezone)}
</Typography>
</Stack>
) : (
@@ -178,26 +180,26 @@ export const HistogramStatus = ({
width="100%"
height={155}
>
<BarChart data={checks}>
<BarChart data={normalChecks}>
<XAxis
stroke={theme.palette.primary.lowContrast}
height={15}
tick={false}
label={
<XLabel
p1={checks[0]}
p2={checks[checks.length - 1]}
p1={normalChecks[0]}
p2={normalChecks[normalChecks.length - 1]}
range={range}
/>
}
/>
<Bar
dataKey="avgResponseTime"
dataKey="normalResponseTime"
maxBarSize={7}
background={{ fill: "transparent" }}
>
{checks?.map((groupedCheck, idx) => {
const fillColor = getResponseTimeColor(groupedCheck.avgResponseTime);
{normalChecks?.map((groupedCheck, idx) => {
const fillColor = getResponseTimeColor(groupedCheck.normalResponseTime);
return (
<Cell
onMouseEnter={() => setIdx(idx)}
+6 -1
View File
@@ -104,7 +104,12 @@ export const MonitorTable = ({
id: "status",
content: t("status"),
render: (row) => {
return <StatusLabel status={row.status} />;
return (
<StatusLabel
status={row.status}
isActive={row.isActive}
/>
);
},
},
{
+1
View File
@@ -27,6 +27,7 @@ export interface Check {
status: string;
message: string;
responseTime: number;
normalResponseTime?: number;
httpStatusCode: number;
ack: boolean;
expiry: string;
+37
View File
@@ -0,0 +1,37 @@
const MIN_OUT = 10;
const MAX_OUT = 100;
export const normalizeResponseTimes = <T extends Record<K, number>, K extends keyof T>(
checks: T[],
key: K
): (T & { normalResponseTime: number })[] => {
if (!Array.isArray(checks) || checks.length === 0)
return checks as (T & {
normalResponseTime: number;
})[];
if (checks.length === 1) {
return [
{
...checks[0],
normalResponseTime: 50,
},
];
}
const { min, max } = checks.reduce(
(acc, check) => {
if (check[key] > acc.max) acc.max = check[key];
if (check[key] < acc.min) acc.min = check[key];
return acc;
},
{ max: -Infinity, min: Infinity }
);
const range = max - min || 1;
return checks.map((check) => ({
...check,
normalResponseTime: MIN_OUT + ((check[key] - min) * (MAX_OUT - MIN_OUT)) / range,
}));
};