Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fe/uptime details refactor #1648

Merged
merged 10 commits into from
Jan 28, 2025
Merged

Conversation

ajhollid
Copy link
Collaborator

@ajhollid ajhollid commented Jan 27, 2025

This PR is part of a series of PRs to firm up the FE and try for more consistent UI, loading states, and error handling

This PR significantly simplifies and cleans up the Uptime Details components.

  • Break UI up into components
  • Extract network operations to hooks
  • Implement consistent loading states and skeletons
  • Simplify code structure
  • Use reusable Table component instead of one off table
<Stack gap={theme.spacing(10)}>
	<Breadcrumbs list={BREADCRUMBS} />
	<MonitorHeader
		isAdmin={isAdmin}
		shouldRender={!monitorIsLoading}
		monitor={monitor}
	/>
	<StatusBoxes
		shouldRender={!monitorIsLoading}
		monitor={monitor}
		certificateExpiry={certificateExpiry}
	/>
	<TimeFramePicker
		shouldRender={!monitorIsLoading}
		dateRange={dateRange}
		setDateRange={setDateRange}
	/>
	<ChartBoxes
		shouldRender={!monitorIsLoading}
		monitor={monitor}
		uiTimezone={uiTimezone}
		dateRange={dateRange}
		dateFormat={dateFormat}
		hoveredUptimeData={hoveredUptimeData}
		setHoveredUptimeData={setHoveredUptimeData}
		hoveredIncidentsData={hoveredIncidentsData}
		setHoveredIncidentsData={setHoveredIncidentsData}
	/>
	<ResponseTimeChart
		shouldRender={!monitorIsLoading}
		monitor={monitor}
		dateRange={dateRange}
	/>
	<ResponseTable
		shouldRender={!checksAreLoading}
		checks={checks}
		uiTimezone={uiTimezone}
		page={page}
		setPage={handlePageChange}
		rowsPerPage={rowsPerPage}
		setRowsPerPage={handleChangeRowsPerPage}
		checksCount={checksCount}
	/>
</Stack>

vs

<Box className="monitor-details">
	{loading ? (
		<SkeletonLayout />
	) : (
		<>
			<Breadcrumbs list={BREADCRUMBS} />
			<Stack
				gap={theme.spacing(10)}
				mt={theme.spacing(10)}
			>
				<Stack
					direction="row"
					gap={theme.spacing(2)}
				>
					<Box>
						<Typography
							component="h1"
							variant="h1"
						>
							{monitor.name}
						</Typography>
						<Stack
							direction="row"
							alignItems="center"
							height="fit-content"
							gap={theme.spacing(2)}
						>
							{/* TODO there is  a tooltip at BarChart component. Wrap the Tooltip on our own component */}
							<Tooltip
								title={statusMsg[determineState(monitor)]}
								disableInteractive
								slotProps={{
									popper: {
										modifiers: [
											{
												name: "offset",
												options: {
													offset: [0, -8],
												},
											},
										],
										sx: {
											"& .MuiTooltip-tooltip": {
												backgroundColor: theme.palette.secondary.main,
												color: theme.palette.secondary.contrastText,
												px: theme.spacing(4),
												py: theme.spacing(3),
												border: 1,
												borderColor: theme.palette.primary.lowContrast,
												borderRadius: theme.shape.borderRadius,
												boxShadow: theme.shape.boxShadow,
												/* TODO Font size should point to theme */
												fontSize: 12,
												fontWeight: 500,
											},
										},
									},
								}}
							>
								<Box>
									<PulseDot color={statusColor[determineState(monitor)]} />
								</Box>
							</Tooltip>
							<Typography
								component="h2"
								variant="h2"
							>
								{monitor.url?.replace(/^https?:\/\//, "") || "..."}
							</Typography>
							<Typography
								position="relative"
								variant="body2"
								mt={theme.spacing(1)}
								ml={theme.spacing(6)}
								sx={{
									"&:before": {
										position: "absolute",
										content: `""`,
										width: 4,
										height: 4,
										borderRadius: "50%",
										backgroundColor: theme.palette.primary.contrastTextTertiary,
										opacity: 0.8,
										left: -9,
										top: "50%",
										transform: "translateY(-50%)",
									},
								}}
							>
								{/* Checking every {formatDurationRounded(monitor?.interval)}. */}
							</Typography>
						</Stack>
					</Box>
					<Stack
						direction="row"
						height={34}
						sx={{
							ml: "auto",
							alignSelf: "flex-end",
						}}
					>
						{isAdmin && (
							<Button
								variant="contained"
								color="secondary"
								onClick={() => navigate(`/uptime/configure/${monitorId}`)}
								sx={{
									px: theme.spacing(5),
									"& svg": {
										mr: theme.spacing(3),
										"& path": {
											/* Should always be contrastText for the button color */
											stroke: theme.palette.secondary.contrastText,
										},
									},
								}}
							>
								<SettingsIcon /> Configure
							</Button>
						)}
					</Stack>
				</Stack>
				<Stack
					direction="row"
					gap={theme.spacing(8)}
				>
					<StatBox
						/* sx={getStatusStyles(determineState(monitor))} */
						/* statusStyles[determineState(monitor)] */
						gradient={true}
						status={determineState(monitor)}
						heading={"active for"}
						subHeading={splitDuration(monitor?.uptimeStreak)}
					/>
					<StatBox
						heading="last check"
						subHeading={splitDuration(monitor?.timeSinceLastCheck)}
					/>
					<StatBox
						heading="last response time"
						subHeading={
							<>
								{monitor?.latestResponseTime}
								<Typography component="span">{"ms"}</Typography>
							</>
						}
					/>
					<StatBox
						heading="certificate expiry"
						subHeading={
							<Typography
								component="span"
								fontSize={13}
								color={theme.palette.primary.contrastText}
							>
								{certificateExpiry}
							</Typography>
						}
					/>
				</Stack>
				<Box>
					<Stack
						direction="row"
						justifyContent="space-between"
						alignItems="flex-end"
						gap={theme.spacing(4)}
						mb={theme.spacing(8)}
					>
						<Typography variant="body2">
							Showing statistics for past{" "}
							{dateRange === "day"
								? "24 hours"
								: dateRange === "week"
									? "7 days"
									: "30 days"}
							.
						</Typography>
						<ButtonGroup sx={{ height: 32 }}>
							<Button
								variant="group"
								filled={(dateRange === "day").toString()}
								onClick={() => setDateRange("day")}
							>
								Day
							</Button>
							<Button
								variant="group"
								filled={(dateRange === "week").toString()}
								onClick={() => setDateRange("week")}
							>
								Week
							</Button>
							<Button
								variant="group"
								filled={(dateRange === "month").toString()}
								onClick={() => setDateRange("month")}
							>
								Month
							</Button>
						</ButtonGroup>
					</Stack>
					<Stack
						direction="row"
						flexWrap="wrap"
						gap={theme.spacing(8)}
					>
						<ChartBox>
							<Stack>
								<IconBox>
									<UptimeIcon />
								</IconBox>
								<Typography component="h2">Uptime</Typography>
							</Stack>
							<Stack justifyContent="space-between">
								<Box position="relative">
									<Typography>Total Checks</Typography>
									<Typography component="span">
										{hoveredUptimeData !== null
											? hoveredUptimeData.totalChecks
											: (monitor?.groupedUpChecks?.reduce((count, checkGroup) => {
													return count + checkGroup.totalChecks;
												}, 0) ?? 0)}
									</Typography>
									{hoveredUptimeData !== null && hoveredUptimeData.time !== null && (
										<Typography
											component="h5"
											position="absolute"
											top="100%"
											fontSize={11}
											color={theme.palette.primary.contrastTextTertiary}
										>
											{formatDateWithTz(
												hoveredUptimeData._id,
												dateFormat,
												uiTimezone
											)}
										</Typography>
									)}
								</Box>
								<Box>
									<Typography>
										{hoveredUptimeData !== null
											? "Avg Response Time"
											: "Uptime Percentage"}
									</Typography>
									<Typography component="span">
										{hoveredUptimeData !== null
											? Math.floor(hoveredUptimeData?.avgResponseTime ?? 0)
											: Math.floor(
													((monitor?.upChecks?.totalChecks ?? 0) /
														(monitor?.totalChecks ?? 1)) *
														100
												)}
										<Typography component="span">
											{hoveredUptimeData !== null ? " ms" : " %"}
										</Typography>
									</Typography>
								</Box>
							</Stack>
							<UpBarChart
								monitor={monitor}
								type={dateRange}
								onBarHover={setHoveredUptimeData}
							/>
						</ChartBox>
						<ChartBox>
							<Stack>
								<IconBox>
									<IncidentsIcon />
								</IconBox>
								<Typography component="h2">Incidents</Typography>
							</Stack>
							<Box position="relative">
								<Typography>Total Incidents</Typography>
								<Typography component="span">
									{hoveredIncidentsData !== null
										? hoveredIncidentsData.totalChecks
										: (monitor?.groupedDownChecks?.reduce((count, checkGroup) => {
												return count + checkGroup.totalChecks;
											}, 0) ?? 0)}
								</Typography>
								{hoveredIncidentsData !== null &&
									hoveredIncidentsData.time !== null && (
										<Typography
											component="h5"
											position="absolute"
											top="100%"
											fontSize={11}
											color={theme.palette.primary.contrastTextTertiary}
										>
											{formatDateWithTz(
												hoveredIncidentsData._id,
												dateFormat,
												uiTimezone
											)}
										</Typography>
									)}
							</Box>
							<DownBarChart
								monitor={monitor}
								type={dateRange}
								onBarHover={setHoveredIncidentsData}
							/>
						</ChartBox>
						<ChartBox justifyContent="space-between">
							<Stack>
								<IconBox>
									<AverageResponseIcon />
								</IconBox>
								<Typography component="h2">Average Response Time</Typography>
							</Stack>
							<ResponseGaugeChart avgResponseTime={monitor.avgResponseTime ?? 0} />
						</ChartBox>
						<ChartBox sx={{ padding: 0 }}>
							<Stack
								pt={theme.spacing(8)}
								pl={theme.spacing(8)}
							>
								<IconBox>
									<ResponseTimeIcon />
								</IconBox>
								<Typography component="h2">Response Times</Typography>
							</Stack>
							<MonitorDetailsAreaChart
								checks={monitor.groupedChecks ?? []}
								dateRange={dateRange}
							/>
						</ChartBox>
						<ChartBox
							gap={theme.spacing(8)}
							sx={{
								flex: "100%",
								height: "fit-content",
								"& nav": { mt: theme.spacing(12) },
							}}
						>
							<Stack mb={theme.spacing(8)}>
								<IconBox>
									<HistoryIcon />
								</IconBox>
								<Typography
									component="h2"
									color={theme.palette.primary.contrastTextSecondary}
								>
									History
								</Typography>
							</Stack>
							<PaginationTable
								monitorId={monitorId}
								dateRange={dateRange}
							/>
						</ChartBox>
					</Stack>
				</Box>
			</Stack>
		</>
	)}
</Box>

Copy link

@llamapreview llamapreview bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto Pull Request Review from LlamaPReview

Large PR Notification

Dear contributor,

Thank you for your substantial contribution to this project. LlamaPReview has detected that this Pull Request contains a large volume of changes, which exceeds our current processing capacity.

Details:

  • PR and related contents total size: Approximately 117,202 characters
  • Current limit: 50,000 characters

Next steps:

  1. Consider breaking this PR into smaller, more focused changes if possible.
  2. For manual review, please reach out to your team members or maintainers.

We appreciate your understanding and commitment to improving this project. Your contributions are valuable, and we want to ensure they receive the attention they deserve.

LlamaPReview is continuously evolving to better serve the community. Share your thoughts on handling large PRs in our GitHub Discussions - your feedback helps us improve and expand our capabilities.

If you have any questions or need assistance, our community and support team are here to help.

Best regards,
LlamaPReview Team

Copy link

coderabbitai bot commented Jan 27, 2025

Walkthrough

This pull request introduces a comprehensive refactoring of the Uptime Details page in the client application. The changes focus on modularizing the UI components, introducing new reusable components like Dot, ChartBox, and various utility hooks for data fetching. The modifications enhance the code's maintainability by breaking down complex components into smaller, more focused parts and implementing robust error handling through optional chaining and custom hooks.

Changes

File Path Change Summary
Client/src/Components/Dot/index.jsx New Dot component for rendering circular dots with configurable color and size
Client/src/Pages/Uptime/Details/* Extensive refactoring of Uptime Details page components, introducing modular components like MonitorHeader, StatusBoxes, ChartBoxes, ResponseTable
Client/src/Routes/index.jsx Updated routing configuration with new Uptime and UptimeDetails components
Client/src/Utils/timeUtils.js Added getHumanReadableDuration utility function

Possibly related PRs

Suggested Labels

design, frontend

Suggested Reviewers

  • mertssmnoglu
✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🔭 Outside diff range comments (2)
Client/src/Pages/Uptime/Home/Components/Host/index.jsx (1)

Line range hint 46-49: Hold up, something's not adding up with these PropTypes!

The percentage prop is being used with a % suffix, suggesting it's a number, but it's typed as a string.

 Host.propTypes = {
   title: PropTypes.string,
   percentageColor: PropTypes.string,
-  percentage: PropTypes.string,
+  percentage: PropTypes.number,
   url: PropTypes.string,
 };
Client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx (1)

Line range hint 14-18: Yo, we need some safety nets here!

Accessing firstDataPoint._id without checking could make your app go boom if the data point is missing.

-				{formatDateWithTz(firstDataPoint._id, dateFormat, uiTimezone)}
+				{firstDataPoint?._id ? formatDateWithTz(firstDataPoint._id, dateFormat, uiTimezone) : ''}
🧹 Nitpick comments (24)
Client/src/Components/Dot/index.jsx (2)

6-14: Yo dawg, let's make these styles more robust, eh?

The styles need a bit of tweaking to be more reliable across different contexts:

  • The content property isn't doing anything here since we're using a <span>
  • Missing display: inline-block could cause layout inconsistencies

Here's a better way to style it:

 <span
   style={{
-    content: '""',
+    display: "inline-block",
     width: size,
     height: size,
     borderRadius: "50%",
     backgroundColor: color,
     opacity: 0.8,
   }}
 />

18-21: These PropTypes are looking a bit loose, buddy!

Consider adding isRequired for essential props and using more specific PropTypes like oneOf for color validation.

Here's a more robust PropTypes definition:

 Dot.propTypes = {
-  color: PropTypes.string,
-  size: PropTypes.string,
+  color: PropTypes.oneOf([
+    "gray",
+    "primary",
+    "secondary",
+    "error",
+    "warning",
+    "info",
+    "success"
+  ]),
+  size: PropTypes.string
 };
Client/src/Pages/Uptime/Home/Components/Host/index.jsx (2)

4-4: That import path's deeper than mom's spaghetti, eh?

Consider setting up path aliases to simplify these deep import paths. Instead of using relative paths, you could use something like:

- import Dot from "../../../../../Components/Dot";
+ import Dot from "@components/Dot";

Would you like me to help set up path aliases in your webpack/babel config?


30-30: The Dot's looking a bit plain, don't you think?

Since this Dot is being used to indicate status alongside a percentage, we could make it more semantic by passing a color prop that matches percentageColor.

- <Dot />
+ <Dot color={percentageColor} />
Client/src/Pages/Uptime/Details/index.jsx (1)

31-78: Knees weak, arms heavy—ensure we catch errors in custom hooks.
While your custom hooks (useMonitorFetch, useCertificateFetch, useChecksFetch) neatly encapsulate data fetching, it might be beneficial to handle potential errors or warnings inside them. Real-time error handling can protect you from dropping that precious pasta.

Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx (1)

25-29: Props validation needs more sauce, fam!

The monitor prop type could be more specific to catch potential issues early.

 ResponseTImeChart.propTypes = {
 	shouldRender: PropTypes.bool,
-	monitor: PropTypes.object,
+	monitor: PropTypes.shape({
+		groupedChecks: PropTypes.arrayOf(PropTypes.object)
+	}),
 	dateRange: PropTypes.string,
 };
Client/src/Pages/Uptime/Details/Components/ConfigButton/index.jsx (1)

17-17: Let's make this callback fresh like mom's spaghetti!

The navigation callback should be memoized to prevent unnecessary re-renders.

+	const handleNavigate = useCallback(() => 
+		navigate(`/uptime/configure/${monitorId}`), 
+		[navigate, monitorId]
+	);
+
 	return (
 		<Box alignSelf="flex-end">
 			<Button
 				variant="contained"
 				color="secondary"
-				onClick={() => navigate(`/uptime/configure/${monitorId}`)}
+				onClick={handleNavigate}
Client/src/Pages/Uptime/Details/Hooks/useMonitorFetch.jsx (1)

7-7: Yo, these state names are making me lose myself!

The state setter name setMonitorsIsLoading (plural) doesn't match the state variable monitorIsLoading (singular).

-	const [monitorIsLoading, setMonitorsIsLoading] = useState(false);
+	const [monitorIsLoading, setMonitorIsLoading] = useState(false);
Client/src/Pages/Uptime/Details/Hooks/useChecksFetch.jsx (2)

6-12: Consider adopting TypeScript for better type safety.

The hook accepts multiple parameters that would benefit from TypeScript interfaces, especially for complex objects like dateRange.


40-40: Add debouncing for frequent parameter changes.

Consider using useDebounce hook from a utility library like use-debounce to prevent excessive API calls when parameters change frequently.

Client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx (1)

16-42: Optimize certificate fetching with useMemo.

The date formatting operation could be memoized to prevent unnecessary recalculations.

+ import { useMemo } from 'react';
// ... inside the hook
+ const formattedDate = useMemo(() => {
+   if (!res?.data?.data?.certificateDate) return "N/A";
+   return formatDateWithTz(res.data.data.certificateDate, certificateDateFormat, uiTimezone) ?? "N/A";
+ }, [res?.data?.data?.certificateDate, certificateDateFormat, uiTimezone]);
Client/src/Pages/Uptime/Details/Components/TimeFramePicker/index.jsx (1)

23-23: Extract time periods to constants.

Consider moving the time period mappings to constants to improve maintainability.

const TIME_PERIOD_LABELS = {
  day: "24 hours",
  week: "7 days",
  month: "30 days"
};
Client/src/Pages/Uptime/Details/Components/MonitorHeader/index.jsx (1)

33-33: Use URL API for robust URL handling.

Consider using the URL API instead of regex for more robust URL parsing.

- {monitor?.url?.replace(/^https?:\/\//, "") || "..."}
+ {monitor?.url ? new URL(monitor.url).host : "..."}
Client/src/Pages/Uptime/Details/Components/StatusBoxes/index.jsx (1)

77-81: Yo dawg, let's make these prop types more specific!

The monitor prop type is too loose. We should define the exact shape of the monitor object to catch potential issues early.

 StatusBoxes.propTypes = {
   shouldRender: PropTypes.bool,
-  monitor: PropTypes.object,
+  monitor: PropTypes.shape({
+    uptimeStreak: PropTypes.number,
+    timeSinceLastCheck: PropTypes.number,
+    latestResponseTime: PropTypes.number
+  }),
   certificateExpiry: PropTypes.string,
 };
Client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx (1)

Line range hint 56-73: Cleanup needed: Remove that CAIO_REVIEW comment!

Also, that nested ternary operation is making my knees weak. Let's make it more readable.

- fill={
-   hoveredBarIndex === index
-     ? theme.palette.error.main
-     : chartHovered
-       ? theme.palette.error.light
-       : theme.palette.error.main
- }
+ fill={
+   getBarFillColor({
+     isHovered: hoveredBarIndex === index,
+     isChartHovered: chartHovered,
+     theme
+   })
+ }

Add this helper function:

const getBarFillColor = ({ isHovered, isChartHovered, theme }) => {
  if (isHovered) return theme.palette.error.main;
  return isChartHovered ? theme.palette.error.light : theme.palette.error.main;
};
Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx (1)

23-55: Performance optimization: Move headers array outside component!

The headers array is being recreated on every render. Let's move it outside to prevent unnecessary recreations.

+ const TABLE_HEADERS = [
+   {
+     id: "status",
+     content: "Status",
+     render: (row) => {
+       const status = row.status === true ? "up" : "down";
+       return (
+         <StatusLabel
+           status={status}
+           text={status}
+           customStyles={{ textTransform: "capitalize" }}
+         />
+       );
+     },
+   },
+   // ... rest of the headers
+ ];

 const ResponseTable = ({
   shouldRender = true,
   checks,
   // ... other props
 }) => {
-  const headers = [ ... ];  // Remove this
   // ... rest of the component
   return (
     <Table
-      headers={headers}
+      headers={TABLE_HEADERS}
       // ... other props
     />
   );
 };
Client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx (1)

Line range hint 7-14: Optimize performance: Memoize getThemeColor function!

This function is being recreated on every render. Let's memoize it using useMemo.

+ import { memo, useState, useMemo } from "react";

- const getThemeColor = (responseTime) => {
+ const useThemeColor = () => useMemo(() => (responseTime) => {
   if (responseTime < 200) {
     return "success";
   } else if (responseTime < 300) {
     return "warning";
   } else {
     return "error";
   }
- };
+ }, []);

 const UpBarChart = memo(({ monitor, type, onBarHover }) => {
+  const getThemeColor = useThemeColor();
   // ... rest of the component
Client/src/Utils/timeUtils.js (1)

80-95: Consider consolidating duration formatting functions.

The new getHumanReadableDuration function has similar functionality to the existing formatDurationSplit function. Both return an object with time and units/format.

Consider consolidating these functions to reduce code duplication:

-export const getHumanReadableDuration = (ms) => {
-	const durationObj = dayjs.duration(ms);
-	if (durationObj.asDays() >= 1) {
-		const days = Math.floor(durationObj.asDays());
-		return { time: days, units: days === 1 ? "day" : "days" };
-	} else if (durationObj.asHours() >= 1) {
-		const hours = Math.floor(durationObj.asHours());
-		return { time: hours, units: hours === 1 ? "hour" : "hours" };
-	} else if (durationObj.asMinutes() >= 1) {
-		const minutes = Math.floor(durationObj.asMinutes());
-		return { time: minutes, units: minutes === 1 ? "minute" : "minutes" };
-	} else {
-		const seconds = Math.floor(durationObj.asSeconds());
-		return { time: seconds, units: seconds === 1 ? "second" : "seconds" };
-	}
-};

+export const formatDurationSplit = (ms) => {
+	const durationObj = dayjs.duration(ms);
+	if (durationObj.asDays() >= 1) {
+		const days = Math.floor(durationObj.asDays());
+		return { time: days, format: days === 1 ? "day" : "days" };
+	} else if (durationObj.asHours() >= 1) {
+		const hours = Math.floor(durationObj.asHours());
+		return { time: hours, format: hours === 1 ? "hour" : "hours" };
+	} else if (durationObj.asMinutes() >= 1) {
+		const minutes = Math.floor(durationObj.asMinutes());
+		return { time: minutes, format: minutes === 1 ? "minute" : "minutes" };
+	} else {
+		const seconds = Math.floor(durationObj.asSeconds());
+		return { time: seconds, format: seconds === 1 ? "second" : "seconds" };
+	}
+};

+export const getHumanReadableDuration = formatDurationSplit;
Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx (3)

16-26: Consider adding prop defaults and destructuring improvements.

The component could benefit from default values for optional props and cleaner prop destructuring.

-const ChartBoxes = ({
-	shouldRender = true,
-	monitor,
-	dateRange,
-	uiTimezone,
-	dateFormat,
-	hoveredUptimeData,
-	setHoveredUptimeData,
-	hoveredIncidentsData,
-	setHoveredIncidentsData,
-}) => {
+const ChartBoxes = ({
+	shouldRender = true,
+	monitor,
+	dateRange,
+	uiTimezone,
+	dateFormat,
+	hoveredUptimeData = null,
+	setHoveredUptimeData = () => {},
+	hoveredIncidentsData = null,
+	setHoveredIncidentsData = () => {},
+}) => {

49-51: Simplify complex calculations.

The nested calculations with optional chaining could be simplified for better readability.

-monitor?.groupedUpChecks?.reduce((count, checkGroup) => {
-	return count + checkGroup.totalChecks;
-}, 0) ?? 0

+const totalUpChecks = monitor?.groupedUpChecks?.reduce(
+	(count, { totalChecks }) => count + totalChecks,
+	0
+) ?? 0;

-((monitor?.upChecks?.totalChecks ?? 0) /
-	(monitor?.totalChecks ?? 1)) *
-	100

+const uptimePercentage = (monitor?.upChecks?.totalChecks ?? 0) * 100 / (monitor?.totalChecks ?? 1);

Also applies to: 73-75


123-123: Add error boundary for chart components.

The ResponseGaugeChart and other chart components could throw errors if the monitor data is malformed.

Consider wrapping chart components in an error boundary:

import { ErrorBoundary } from 'react-error-boundary';

const ChartErrorFallback = () => (
  <Typography color="error">
    Failed to load chart data
  </Typography>
);

// Usage
<ErrorBoundary FallbackComponent={ChartErrorFallback}>
  <ResponseGaugeChart avgResponseTime={monitor.avgResponseTime ?? 0} />
</ErrorBoundary>
Client/src/Pages/Uptime/Details/Components/Charts/ChartBox.jsx (3)

1-5: Yo dawg, consider moving IconBox closer to its usage!

That deep import path ../../../../../Components/IconBox is making me nervous like mom's spaghetti. Consider moving the IconBox component to a more accessible location, like a shared components directory at a higher level.


9-52: These styles are longer than a rap battle, fam!

The styling object is getting pretty hefty. Consider these improvements to keep it clean:

  1. Extract the styles into a separate styled component or styles object
  2. Replace magic numbers with theme values:
-					fontSize: 15,
+					fontSize: theme.typography.fontSize.small,
-					fontSize: 13,
+					fontSize: theme.typography.fontSize.xs,
-					fontSize: 20,
+					fontSize: theme.typography.fontSize.large,

70-75: Prop validation's got the right flow, but let's make it tighter!

Consider adding more specific validation for the height prop to ensure it's a valid CSS value:

-	height: PropTypes.string,
+	height: PropTypes.oneOfType([
+		PropTypes.string,
+		PropTypes.number
+	]),

Also, consider adding default props for better documentation:

ChartBox.defaultProps = {
  height: "300px",
  children: null,
};
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66ba0ef and b209567.

📒 Files selected for processing (30)
  • Client/src/Components/Dot/index.jsx (1 hunks)
  • Client/src/Components/Table/TablePagination/index.jsx (1 hunks)
  • Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx (1 hunks)
  • Client/src/Pages/Uptime/Details/Components/ChartBoxes/skeleton.jsx (1 hunks)
  • Client/src/Pages/Uptime/Details/Components/Charts/ChartBox.jsx (1 hunks)
  • Client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx (2 hunks)
  • Client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx (2 hunks)
  • Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx (1 hunks)
  • Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx (1 hunks)
  • Client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx (3 hunks)
  • Client/src/Pages/Uptime/Details/Components/ConfigButton/index.jsx (1 hunks)
  • Client/src/Pages/Uptime/Details/Components/MonitorHeader/index.jsx (1 hunks)
  • Client/src/Pages/Uptime/Details/Components/MonitorHeader/skeleton.jsx (1 hunks)
  • Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx (1 hunks)
  • Client/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx (1 hunks)
  • Client/src/Pages/Uptime/Details/Components/StatusBoxes/index.jsx (1 hunks)
  • Client/src/Pages/Uptime/Details/Components/StatusBoxes/skeleton.jsx (1 hunks)
  • Client/src/Pages/Uptime/Details/Components/TimeFramePicker/index.jsx (1 hunks)
  • Client/src/Pages/Uptime/Details/Components/TimeFramePicker/skeleton.jsx (1 hunks)
  • Client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx (1 hunks)
  • Client/src/Pages/Uptime/Details/Hooks/useChecksFetch.jsx (1 hunks)
  • Client/src/Pages/Uptime/Details/Hooks/useMonitorFetch.jsx (1 hunks)
  • Client/src/Pages/Uptime/Details/PaginationTable/index.jsx (0 hunks)
  • Client/src/Pages/Uptime/Details/index.jsx (1 hunks)
  • Client/src/Pages/Uptime/Details/skeleton.jsx (0 hunks)
  • Client/src/Pages/Uptime/Details/styled.jsx (0 hunks)
  • Client/src/Pages/Uptime/Home/Components/Host/index.jsx (2 hunks)
  • Client/src/Pages/Uptime/Home/index.jsx (3 hunks)
  • Client/src/Routes/index.jsx (3 hunks)
  • Client/src/Utils/timeUtils.js (2 hunks)
💤 Files with no reviewable changes (3)
  • Client/src/Pages/Uptime/Details/skeleton.jsx
  • Client/src/Pages/Uptime/Details/styled.jsx
  • Client/src/Pages/Uptime/Details/PaginationTable/index.jsx
✅ Files skipped from review due to trivial changes (4)
  • Client/src/Pages/Uptime/Details/Components/TimeFramePicker/skeleton.jsx
  • Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx
  • Client/src/Pages/Uptime/Details/Components/MonitorHeader/skeleton.jsx
  • Client/src/Pages/Uptime/Details/Components/ChartBoxes/skeleton.jsx
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (13)
Client/src/Routes/index.jsx (2)

9-10: Yo, these import changes are straight fire! 🔥

The renaming from Monitors to Uptime makes the component's purpose crystal clear, and splitting out UptimeDetails shows solid component organization. This is exactly what we wanted with the frontend refactoring.


50-50: Mom's spaghetti moment: Let's verify these route changes! 🍝

The route updates look solid, but we should double-check that the new components handle URL parameters consistently.

Let's verify the route parameter handling:

Also applies to: 59-59

✅ Verification successful

Yo, these routes are as solid as mom's spaghetti! 🍝

The parameter handling is properly implemented across all components:

  • Home component handles navigation smoothly
  • Details component correctly uses URL parameters
  • Custom hooks properly manage the monitorId
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for monitorId parameter usage in the new components
echo "Checking Uptime component parameter handling:"
rg -A 3 "monitorId|useParams" "Client/src/Pages/Uptime/Home.jsx" || echo "File not found or no matches"

echo -e "\nChecking UptimeDetails component parameter handling:"
rg -A 3 "monitorId|useParams" "Client/src/Pages/Uptime/Details.jsx" || echo "File not found or no matches"

Length of output: 637


Script:

#!/bin/bash
# First locate the actual component files
echo "Looking for Uptime component files:"
fd -e jsx -e js "Uptime" Client/src

echo -e "\nChecking contents of found files for parameter handling:"
for file in $(fd -e jsx -e js "Uptime" Client/src); do
    echo -e "\n=== Checking $file ==="
    rg -A 3 "monitorId|useParams|useNavigate" "$file" || echo "No parameter handling found in $file"
done

Length of output: 282


Script:

#!/bin/bash
# Search for component declarations and route usage
echo "Searching for Uptime component declarations:"
rg -i "function\s+(Uptime|UptimeDetails)" Client/src

echo -e "\nSearching for Uptime route definitions:"
rg -i "element.*Uptime" Client/src

echo -e "\nSearching for any file containing Uptime:"
rg -l "Uptime" Client/src

Length of output: 1775


Script:

#!/bin/bash
echo "=== Checking Home component ==="
rg -A 3 "useParams|useNavigate|monitorId" "Client/src/Pages/Uptime/Home/index.jsx"

echo -e "\n=== Checking Details component ==="
rg -A 3 "useParams|useNavigate|monitorId" "Client/src/Pages/Uptime/Details/index.jsx"

echo -e "\n=== Checking useMonitorFetch hook ==="
rg -A 3 "useParams|monitorId" "Client/src/Pages/Uptime/Details/Hooks/useMonitorFetch.jsx"

Length of output: 1803

Client/src/Pages/Uptime/Details/index.jsx (4)

1-11: Sweet import structure—mom's spaghetti stays on the plate!
Your imports are well-organized, separating UI components from hooks and utilities. This fosters maintainability and clarity right out of the gate.


12-28: Bready to roll? 'cause these breadcrumbs look solid.
Your BREADCRUMBS array adds a neat navigational structure. The optional comment about clicking paths suggests a future enhancement if a clickable breadcrumb is needed. Overall, no concerns—just keep track of any usability improvements you might want.


81-124: Your stack is cooking with sauce—lots of tasty subcomponents!
The composition of subcomponents in your return statement is well-layered, like lasagna. Each chunk is clearly responsible for its own piece of functionality, making it easier to maintain. This clean approach sets the stage for delightful user experiences.


128-128: Exporting for the win—nothing saucey here.
Default export is neatly handled and consistent with the new naming convention. Smooth final touch.

Client/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx (1)

1-13: Lighten the load—no sweaty palms with this skeleton.
Your SkeletonLayout is straightforward and minimal, ensuring a clear loading state without spaghetti code. It's a good practice to keep skeleton components small and specialized this way.

Client/src/Pages/Uptime/Details/Components/StatusBoxes/skeleton.jsx (1)

1-30: Triple threat skeleton—no sauce lost between these boxes.
Your SkeletonLayout effectively displays three placeholder boxes, each taking one-third of the width, which aligns well with your final layout. This approach keeps users informed in a visually balanced way until real data is available.

Client/src/Utils/timeUtils.js (1)

2-2: LGTM! Clean plugin setup.

The duration plugin is properly imported and extended to dayjs.

Also applies to: 10-10

Client/src/Pages/Uptime/Home/index.jsx (2)

105-105: LGTM! Consistent spacing adjustment.

The increased gap spacing aligns with the UI refactoring objectives mentioned in the PR summary.


16-16: Verify the hook name change impact.

The hook import has been renamed from useMonitorFetch to useMonitorsFetch. Let's verify this change across the codebase.

✅ Verification successful

Yo, the hook name change is legit! 🍝

The renamed hook useMonitorsFetch in Home component coexists with useMonitorFetch in Details component. They're separate hooks serving different purposes - one for multiple monitors, one for a single monitor. Keep it as is!

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining references to the old hook name
rg "useMonitorFetch" --type js --type jsx

# Search for the new hook name usage
rg "useMonitorsFetch" --type js --type jsx

Length of output: 140


Script:

#!/bin/bash
# Find all JavaScript and JSX files, then search for both hook names
fd -e js -e jsx | xargs rg "useMonitorFetch|useMonitorsFetch" -l

# Search for the hook definition
fd -e js -e jsx | xargs rg "^(export )?((const|function) )?useMonitors?Fetch" -l

Length of output: 458


Script:

#!/bin/bash
# Check the implementation of both hooks
echo "=== useMonitorsFetch implementation ==="
rg -A 10 "^(export )?((const|function) )?useMonitorsFetch" Client/src/Pages/Uptime/Home/Hooks/useMonitorsFetch.jsx

echo -e "\n=== useMonitorFetch implementation ==="
rg -A 10 "^(export )?((const|function) )?useMonitorFetch" Client/src/Pages/Uptime/Details/Hooks/useMonitorFetch.jsx

Length of output: 821

Client/src/Components/Table/TablePagination/index.jsx (1)

8-8: LGTM! Enhanced pagination context.

The addition of the paginationLabel prop improves accessibility and user experience by providing context about the paginated items.

Client/src/Pages/Uptime/Details/Components/Charts/ChartBox.jsx (1)

54-64: This structure's cleaner than Eminem's verses!

The component layout is well-organized with proper spacing and alignment. Keep spitting that clean code! 🎤

import SkeletonLayout from "./ResponseTimeChartSkeleton";
import PropTypes from "prop-types";

const ResponseTImeChart = ({ shouldRender = true, monitor, dateRange }) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Yo dawg, there's a typo in your component name!

The component name has "TIme" instead of "Time" - this inconsistency could make it harder to import and use the component correctly.

-const ResponseTImeChart = ({ shouldRender = true, monitor, dateRange }) => {
+const ResponseTimeChart = ({ shouldRender = true, monitor, dateRange }) => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const ResponseTImeChart = ({ shouldRender = true, monitor, dateRange }) => {
const ResponseTimeChart = ({ shouldRender = true, monitor, dateRange }) => {

Comment on lines +6 to +32
export const useMonitorFetch = ({ authToken, monitorId, dateRange }) => {
const [monitorIsLoading, setMonitorsIsLoading] = useState(false);
const [monitor, setMonitor] = useState({});
const navigate = useNavigate();

useEffect(() => {
const fetchMonitors = async () => {
try {
setMonitorsIsLoading(true);
const res = await networkService.getUptimeDetailsById({
authToken: authToken,
monitorId: monitorId,
dateRange: dateRange,
normalize: true,
});
setMonitor(res?.data?.data ?? {});
} catch (error) {
logger.error(error);
navigate("/not-found", { replace: true });
} finally {
setMonitorsIsLoading(false);
}
};
fetchMonitors();
}, [authToken, dateRange, monitorId, navigate]);
return { monitor, monitorIsLoading };
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

This hook needs more error handling than a rap battle!

Consider adding an error state to handle failed requests more gracefully instead of just navigating away.

 export const useMonitorFetch = ({ authToken, monitorId, dateRange }) => {
 	const [monitorIsLoading, setMonitorIsLoading] = useState(false);
 	const [monitor, setMonitor] = useState({});
+	const [error, setError] = useState(null);
 	const navigate = useNavigate();

 	useEffect(() => {
 		const fetchMonitors = async () => {
 			try {
 				setMonitorIsLoading(true);
+				setError(null);
 				const res = await networkService.getUptimeDetailsById({
 					authToken: authToken,
 					monitorId: monitorId,
 					dateRange: dateRange,
 					normalize: true,
 				});
 				setMonitor(res?.data?.data ?? {});
 			} catch (error) {
 				logger.error(error);
+				setError(error);
 				navigate("/not-found", { replace: true });
 			} finally {
 				setMonitorIsLoading(false);
 			}
 		};
 		fetchMonitors();
 	}, [authToken, dateRange, monitorId, navigate]);
-	return { monitor, monitorIsLoading };
+	return { monitor, monitorIsLoading, error };
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const useMonitorFetch = ({ authToken, monitorId, dateRange }) => {
const [monitorIsLoading, setMonitorsIsLoading] = useState(false);
const [monitor, setMonitor] = useState({});
const navigate = useNavigate();
useEffect(() => {
const fetchMonitors = async () => {
try {
setMonitorsIsLoading(true);
const res = await networkService.getUptimeDetailsById({
authToken: authToken,
monitorId: monitorId,
dateRange: dateRange,
normalize: true,
});
setMonitor(res?.data?.data ?? {});
} catch (error) {
logger.error(error);
navigate("/not-found", { replace: true });
} finally {
setMonitorsIsLoading(false);
}
};
fetchMonitors();
}, [authToken, dateRange, monitorId, navigate]);
return { monitor, monitorIsLoading };
};
export const useMonitorFetch = ({ authToken, monitorId, dateRange }) => {
const [monitorIsLoading, setMonitorsIsLoading] = useState(false);
const [monitor, setMonitor] = useState({});
const [error, setError] = useState(null);
const navigate = useNavigate();
useEffect(() => {
const fetchMonitors = async () => {
try {
setMonitorsIsLoading(true);
setError(null);
const res = await networkService.getUptimeDetailsById({
authToken: authToken,
monitorId: monitorId,
dateRange: dateRange,
normalize: true,
});
setMonitor(res?.data?.data ?? {});
} catch (error) {
logger.error(error);
setError(error);
navigate("/not-found", { replace: true });
} finally {
setMonitorsIsLoading(false);
}
};
fetchMonitors();
}, [authToken, dateRange, monitorId, navigate]);
return { monitor, monitorIsLoading, error };
};

Comment on lines +21 to +30
const res = await networkService.getChecksByMonitor({
authToken: authToken,
monitorId: monitorId,
sortOrder: "desc",
limit: null,
dateRange: dateRange,
filter: null,
page: page,
rowsPerPage: rowsPerPage,
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve error handling and parameter validation.

A few concerns with the current implementation:

  1. Using null for the limit parameter seems unclear - consider using rowsPerPage instead
  2. No error state is exposed to consumers of this hook
-  limit: null,
+  limit: rowsPerPage,

Also, consider adding:

const [error, setError] = useState(null);
// ... in catch block
setError(error);
// ... in return
return { checks, checksCount, checksAreLoading, error };

<ButtonGroup sx={{ height: 32 }}>
<Button
variant="group"
filled={(dateRange === "day").toString()}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove unnecessary toString() conversion.

The filled prop conversion to string is unnecessary as boolean values work directly.

- filled={(dateRange === "day").toString()}
+ filled={dateRange === "day"}

Also applies to: 35-35, 42-42

const MonitorHeader = ({ shouldRender = true, isAdmin, monitor }) => {
const theme = useTheme();
const { statusColor, statusMsg, determineState } = useUtils();
console.log(shouldRender);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove debug console.log statement.

Production code should not contain debug logging statements.

- console.log(shouldRender);

Comment on lines +49 to +53
MonitorHeader.propTypes = {
shouldRender: PropTypes.bool,
isAdmin: PropTypes.bool,
monitor: PropTypes.object,
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Strengthen PropTypes for monitor object.

Define the expected shape of the monitor object for better type checking.

- monitor: PropTypes.object,
+ monitor: PropTypes.shape({
+   name: PropTypes.string.isRequired,
+   url: PropTypes.string.isRequired,
+   interval: PropTypes.number.isRequired,
+   _id: PropTypes.string.isRequired,
+ }).isRequired,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
MonitorHeader.propTypes = {
shouldRender: PropTypes.bool,
isAdmin: PropTypes.bool,
monitor: PropTypes.object,
};
MonitorHeader.propTypes = {
shouldRender: PropTypes.bool,
isAdmin: PropTypes.bool,
monitor: PropTypes.shape({
name: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
interval: PropTypes.number.isRequired,
_id: PropTypes.string.isRequired,
}).isRequired,
};

Comment on lines +18 to +24
const { time: streakTime, units: streakUnits } = getHumanReadableDuration(
monitor?.uptimeStreak
);

const { time: lastCheckTime, units: lastCheckUnits } = getHumanReadableDuration(
monitor?.timeSinceLastCheck
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Mom's spaghetti moment: Add fallback values for undefined cases!

The optional chaining is good for preventing crashes, but we should provide fallback values to avoid rendering undefined.

 const { time: streakTime, units: streakUnits } = getHumanReadableDuration(
-  monitor?.uptimeStreak
+  monitor?.uptimeStreak ?? 0
 );

 const { time: lastCheckTime, units: lastCheckUnits } = getHumanReadableDuration(
-  monitor?.timeSinceLastCheck
+  monitor?.timeSinceLastCheck ?? 0
 );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { time: streakTime, units: streakUnits } = getHumanReadableDuration(
monitor?.uptimeStreak
);
const { time: lastCheckTime, units: lastCheckUnits } = getHumanReadableDuration(
monitor?.timeSinceLastCheck
);
const { time: streakTime, units: streakUnits } = getHumanReadableDuration(
monitor?.uptimeStreak ?? 0
);
const { time: lastCheckTime, units: lastCheckUnits } = getHumanReadableDuration(
monitor?.timeSinceLastCheck ?? 0
);

@coderabbitai coderabbitai bot mentioned this pull request Mar 11, 2025
9 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant