add loading states, skeletons, fix map tooltip

This commit is contained in:
Alex Holliday
2025-02-07 15:54:19 -08:00
parent ea1a717ad0
commit d852388202
10 changed files with 205 additions and 129 deletions
@@ -73,9 +73,12 @@ const CustomToolTip = ({ active, payload, label, dateRange }) => {
component="span"
sx={{ opacity: 0.8 }}
>
Response Time
</Typography>{" "}
<Typography component="span">
Response time:
</Typography>
<Typography
ml={theme.spacing(4)}
component="span"
>
{Math.floor(responseTime)}
<Typography
component="span"
@@ -24,73 +24,70 @@ const CustomToolTip = ({ active, payload, label }) => {
? payload[0]?.payload?.originalAvgResponseTime
: (payload[0]?.payload?.avgResponseTime ?? 0);
return (
<ChartBox
icon={<ResponseTimeIcon />}
header="Response Times"
sx={{ padding: 0 }}
<Box
className="area-tooltip"
sx={{
backgroundColor: theme.palette.primary.main,
border: 1,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
py: theme.spacing(2),
px: theme.spacing(4),
}}
>
<Box
className="area-tooltip"
<Typography
sx={{
backgroundColor: theme.palette.background.main,
border: 1,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
py: theme.spacing(2),
px: theme.spacing(4),
color: theme.palette.text.tertiary,
fontSize: 12,
fontWeight: 500,
}}
>
<Typography
{formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)}
</Typography>
<Box mt={theme.spacing(1)}>
<Box
display="inline-block"
width={theme.spacing(4)}
height={theme.spacing(4)}
backgroundColor={theme.palette.primary.main}
sx={{ borderRadius: "50%" }}
/>
<Stack
display="inline-flex"
direction="row"
justifyContent="space-between"
ml={theme.spacing(3)}
sx={{
color: theme.palette.text.tertiary,
fontSize: 12,
fontWeight: 500,
"& span": {
color: theme.palette.text.tertiary,
fontSize: 11,
fontWeight: 500,
},
}}
>
{formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)}
</Typography>
<Box mt={theme.spacing(1)}>
<Box
display="inline-block"
width={theme.spacing(4)}
height={theme.spacing(4)}
backgroundColor={theme.palette.primary.main}
sx={{ borderRadius: "50%" }}
/>
<Stack
display="inline-flex"
direction="row"
justifyContent="space-between"
ml={theme.spacing(3)}
sx={{
"& span": {
color: theme.palette.text.tertiary,
fontSize: 11,
fontWeight: 500,
},
}}
<Typography
component="span"
sx={{ opacity: 0.8 }}
>
Response time
</Typography>
<Typography
ml={theme.spacing(4)}
component="span"
>
{Math.floor(responseTime)}
<Typography
component="span"
sx={{ opacity: 0.8 }}
>
Response Time
</Typography>{" "}
<Typography component="span">
{Math.floor(responseTime)}
<Typography
component="span"
sx={{ opacity: 0.8 }}
>
{" "}
ms
</Typography>
{" "}
ms
</Typography>
</Stack>
</Box>
{/* Display original value */}
</Typography>
</Stack>
</Box>
</ChartBox>
{/* Display original value */}
</Box>
);
}
return null;
@@ -143,75 +140,81 @@ const DistributedUptimeResponseChart = ({ checks }) => {
if (checks.length === 0) return null;
return (
<ResponsiveContainer
width="100%"
minWidth={25}
height={220}
<ChartBox
icon={<ResponseTimeIcon />}
header="Response Times"
sx={{ padding: 0 }}
>
<AreaChart
<ResponsiveContainer
width="100%"
height="100%"
data={checks}
margin={{
top: 10,
right: 0,
left: 0,
bottom: 0,
}}
onMouseMove={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
minWidth={25}
height={220}
>
<CartesianGrid
stroke={theme.palette.primary.lowContrast}
strokeWidth={1}
strokeOpacity={1}
fill="transparent"
vertical={false}
/>
<defs>
<linearGradient
id="colorUv"
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="0%"
stopColor={theme.palette.accent.darker}
stopOpacity={0.8}
/>
<stop
offset="100%"
stopColor={theme.palette.accent.main}
stopOpacity={0}
/>
</linearGradient>
</defs>
<XAxis
stroke={theme.palette.primary.lowContrast}
dataKey="_id"
tick={<CustomTick />}
minTickGap={0}
axisLine={false}
tickLine={false}
height={20}
/>
<Tooltip
cursor={{ stroke: theme.palette.primary.lowContrast }}
content={<CustomToolTip />}
wrapperStyle={{ pointerEvents: "none" }}
/>
<Area
type="monotone"
dataKey="avgResponseTime"
stroke={theme.palette.primary.accent}
fill="url(#colorUv)"
strokeWidth={isHovered ? 2.5 : 1.5}
activeDot={{ stroke: theme.palette.background.main, r: 5 }}
/>
</AreaChart>
</ResponsiveContainer>
<AreaChart
width="100%"
height="100%"
data={checks}
margin={{
top: 10,
right: 0,
left: 0,
bottom: 0,
}}
onMouseMove={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<CartesianGrid
stroke={theme.palette.primary.lowContrast}
strokeWidth={1}
strokeOpacity={1}
fill="transparent"
vertical={false}
/>
<defs>
<linearGradient
id="colorUv"
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="0%"
stopColor={theme.palette.accent.darker}
stopOpacity={0.8}
/>
<stop
offset="100%"
stopColor={theme.palette.accent.main}
stopOpacity={0}
/>
</linearGradient>
</defs>
<XAxis
stroke={theme.palette.primary.lowContrast}
dataKey="_id"
tick={<CustomTick />}
minTickGap={0}
axisLine={false}
tickLine={false}
height={20}
/>
<Tooltip
cursor={{ stroke: theme.palette.primary.lowContrast }}
content={<CustomToolTip />}
wrapperStyle={{ pointerEvents: "none" }}
/>
<Area
type="monotone"
dataKey="avgResponseTime"
stroke={theme.palette.primary.accent}
fill="url(#colorUv)"
strokeWidth={isHovered ? 2.5 : 1.5}
activeDot={{ stroke: theme.palette.background.main, r: 5 }}
/>
</AreaChart>
</ResponsiveContainer>
</ChartBox>
);
};
@@ -0,0 +1,14 @@
import { Stack, Skeleton } from "@mui/material";
export const SkeletonLayout = () => {
return (
<Stack>
<Skeleton
variant="rectangular"
height={"90vh"}
/>
</Stack>
);
};
export default SkeletonLayout;
@@ -68,6 +68,9 @@ const useSubscribeToDetails = ({ monitorId, dateRange }) => {
dateRange: dateRange,
normalize: true,
onUpdate: (data) => {
if (isLoading === true) {
setIsLoading(false);
}
if (networkError === true) {
setNetworkError(false);
}
@@ -87,6 +90,7 @@ const useSubscribeToDetails = ({ monitorId, dateRange }) => {
setRetryCount(0); // Reset retry count on successful connection
},
onError: () => {
setIsLoading(false);
setNetworkError(true);
setConnectionStatus("down");
},
@@ -94,8 +98,6 @@ const useSubscribeToDetails = ({ monitorId, dateRange }) => {
return cleanup;
} catch (error) {
setNetworkError(true);
} finally {
setIsLoading(false);
}
}, [
authToken,
@@ -105,6 +107,7 @@ const useSubscribeToDetails = ({ monitorId, dateRange }) => {
setConnectionStatus,
networkError,
devices,
isLoading,
]);
useEffect(() => {
@@ -11,7 +11,7 @@ import MonitorHeader from "./Components/MonitorHeader";
import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader";
import GenericFallback from "../../../Components/GenericFallback";
import MonitorCreateHeader from "../../../Components/MonitorCreateHeader";
import SkeletonLayout from "./Components/Skeleton";
//Utils
import { useTheme } from "@mui/material/styles";
import { useState } from "react";
@@ -35,6 +35,10 @@ const DistributedUptimeDetails = () => {
{ name: "Details", path: `/distributed-uptime/${monitorId}` },
];
if (isLoading) {
return <SkeletonLayout />;
}
if (networkError) {
return (
<GenericFallback>
@@ -0,0 +1,14 @@
import { Stack, Skeleton } from "@mui/material";
export const SkeletonLayout = () => {
return (
<Stack>
<Skeleton
variant="rectangular"
height={"90vh"}
/>
</Stack>
);
};
export default SkeletonLayout;
@@ -32,6 +32,10 @@ const useSubscribeToMonitors = () => {
field: null,
order: null,
onUpdate: (data) => {
if (isLoading === true) {
setIsLoading(false);
}
const res = data.monitors;
const { monitors, filteredMonitors, summary } = res;
const mappedMonitors = filteredMonitors.map((monitor) =>
@@ -41,6 +45,9 @@ const useSubscribeToMonitors = () => {
setMonitorsSummary(summary);
setFilteredMonitors(mappedMonitors);
},
onError: () => {
setIsLoading(false);
},
});
return cleanup;
@@ -49,8 +56,6 @@ const useSubscribeToMonitors = () => {
body: error.message,
});
setNetworkError(true);
} finally {
setIsLoading(false);
}
}, [authToken, user, getMonitorWithPercentage, theme]);
return [isLoading, networkError, monitors, monitorsSummary, filteredMonitors];
@@ -10,6 +10,7 @@ import GenericFallback from "../../../Components/GenericFallback";
import { useTheme } from "@mui/material/styles";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
import { useSubscribeToMonitors } from "./Hooks/useSubscribeToMonitors";
import SkeletonLayout from "./Components/Skeleton";
// Constants
const BREADCRUMBS = [{ name: `Distributed Uptime`, path: "/distributed-uptime" }];
@@ -21,6 +22,10 @@ const DistributedUptimeMonitors = () => {
const [isLoading, networkError, monitors, monitorsSummary, filteredMonitors] =
useSubscribeToMonitors();
if (isLoading) {
return <SkeletonLayout />;
}
if (networkError) {
return (
<GenericFallback>
@@ -0,0 +1,14 @@
import { Stack, Skeleton } from "@mui/material";
export const SkeletonLayout = () => {
return (
<Stack>
<Skeleton
variant="rectangular"
height={"90vh"}
/>
</Stack>
);
};
export default SkeletonLayout;
@@ -11,6 +11,7 @@ import ControlsHeader from "../../StatusPage/Status/Components/ControlsHeader";
import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader";
import GenericFallback from "../../../Components/GenericFallback";
import Dialog from "../../../Components/Dialog";
import SkeletonLayout from "./Components/Skeleton";
//Utils
import { useTheme } from "@mui/material/styles";
import { useState } from "react";
@@ -46,6 +47,7 @@ const DistributedUptimeStatus = () => {
// Constants
const BREADCRUMBS = [
{ name: "Distributed Uptime", path: "/distributed-uptime" },
{ name: "details", path: `/distributed-uptime/${monitorId}` },
{ name: "status", path: `` },
];
@@ -59,7 +61,11 @@ const DistributedUptimeStatus = () => {
};
}
if (networkError) {
if (isLoading || statusPageIsLoading) {
return <SkeletonLayout />;
}
if (networkError || statusPageNetworkError) {
return (
<GenericFallback>
<Typography
@@ -74,7 +80,11 @@ const DistributedUptimeStatus = () => {
);
}
if (typeof monitor === "undefined" || monitor.totalChecks === 0) {
if (
typeof statusPage === "undefined" ||
typeof monitor === "undefined" ||
monitor.totalChecks === 0
) {
return (
<Stack gap={theme.spacing(10)}>
<Breadcrumbs list={BREADCRUMBS} />
@@ -93,6 +103,7 @@ const DistributedUptimeStatus = () => {
>
{!isPublic && <Breadcrumbs list={BREADCRUMBS} />}
<ControlsHeader
shouldShow={!isPublic}
statusPage={statusPage}
isDeleting={isDeleting}
isDeleteOpen={isDeleteOpen}