From c4aab310563d0758bff9b4e2d8de8f3fffe3987d Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 1 Apr 2026 14:40:34 +0100 Subject: [PATCH] feat: enhance DayUptimeGraph and UptimeBarTooltip with status duration handling and improved tooltip display --- .../UI/Components/Graphs/DayUptimeGraph.tsx | 19 +- .../UI/Components/Graphs/UptimeBarTooltip.tsx | 293 ++++++++++++++---- Common/UI/Components/Tooltip/Tooltip.tsx | 4 +- 3 files changed, 256 insertions(+), 60 deletions(-) diff --git a/Common/UI/Components/Graphs/DayUptimeGraph.tsx b/Common/UI/Components/Graphs/DayUptimeGraph.tsx index a44028b34e..70ef0d3c2d 100644 --- a/Common/UI/Components/Graphs/DayUptimeGraph.tsx +++ b/Common/UI/Components/Graphs/DayUptimeGraph.tsx @@ -1,5 +1,5 @@ import Tooltip from "../Tooltip/Tooltip"; -import UptimeBarTooltip from "./UptimeBarTooltip"; +import UptimeBarTooltip, { StatusDuration } from "./UptimeBarTooltip"; import { Green } from "../../../Types/BrandColors"; import Color from "../../../Types/Color"; import OneUptimeDate from "../../../Types/Date"; @@ -113,6 +113,7 @@ const DayUptimeGraph: FunctionComponent = ( }); const secondsOfEvent: Dictionary = {}; + const eventColors: Dictionary = {}; let currentPriority: number = 1; @@ -144,6 +145,7 @@ const DayUptimeGraph: FunctionComponent = ( secondsOfEvent[event.eventStatusId.toString()]! += seconds; eventLabels[event.eventStatusId.toString()] = event.label; + eventColors[event.eventStatusId.toString()] = event.color; // set bar color. if (currentPriority <= event.priority) { @@ -225,6 +227,17 @@ const DayUptimeGraph: FunctionComponent = ( className = "w-20 h-" + props.height; } + // Build status durations for tooltip + const statusDurations: Array = []; + for (const key in secondsOfEvent) { + statusDurations.push({ + label: eventLabels[key] || "Unknown", + seconds: secondsOfEvent[key] || 0, + color: eventColors[key] || (props.defaultBarColor || Green), + isDowntime: downtimeStatusIds.includes(key), + }); + } + const hasDayIncidents: boolean = dayIncidents.length > 0; const isClickable: boolean = hasDayIncidents && Boolean(props.onBarClick); @@ -237,9 +250,7 @@ const DayUptimeGraph: FunctionComponent = ( date={todaysDay} uptimePercent={uptimePercentForTheDay} hasEvents={hasEvents} - eventLabels={eventLabels} - secondsOfEvent={secondsOfEvent} - downtimeEventStatusIds={downtimeStatusIds} + statusDurations={statusDurations} incidents={dayIncidents} /> } diff --git a/Common/UI/Components/Graphs/UptimeBarTooltip.tsx b/Common/UI/Components/Graphs/UptimeBarTooltip.tsx index a2918cafd7..93f1455f98 100644 --- a/Common/UI/Components/Graphs/UptimeBarTooltip.tsx +++ b/Common/UI/Components/Graphs/UptimeBarTooltip.tsx @@ -1,15 +1,20 @@ import OneUptimeDate from "../../../Types/Date"; -import Dictionary from "../../../Types/Dictionary"; +import Color from "../../../Types/Color"; import UptimeBarTooltipIncident from "../../../Types/Monitor/UptimeBarTooltipIncident"; import React, { FunctionComponent, ReactElement } from "react"; +export interface StatusDuration { + label: string; + seconds: number; + color: Color; + isDowntime: boolean; +} + export interface ComponentProps { date: Date; uptimePercent: number; hasEvents: boolean; - eventLabels: Dictionary; - secondsOfEvent: Dictionary; - downtimeEventStatusIds: Array; + statusDurations: Array; incidents: Array; } @@ -19,78 +24,256 @@ const UptimeBarTooltip: FunctionComponent = ( const dateStr: string = OneUptimeDate.getDateAsUserFriendlyLocalFormattedString(props.date, true); - return ( -
-
{dateStr}
+ const uptimeColor: string = + props.uptimePercent >= 99.9 + ? "#22c55e" + : props.uptimePercent >= 99 + ? "#eab308" + : "#ef4444"; + return ( +
+ {/* Header */} +
+ + {dateStr} + +
+ + {/* Uptime bar */} {props.hasEvents && ( -
- - {props.uptimePercent.toFixed(2)}% uptime - +
+
+ Uptime + + {props.uptimePercent.toFixed(2)}% + +
+
+
+
)} {!props.hasEvents && ( -
No data for this day.
- )} - - {/* Status durations */} - {Object.keys(props.secondsOfEvent).length > 0 && ( -
- {Object.keys(props.secondsOfEvent).map((key: string) => { - const isDowntime: boolean = props.downtimeEventStatusIds.includes(key); - if (!isDowntime) { - return null; - } - return ( -
- {props.eventLabels[key]} for{" "} - {OneUptimeDate.secondsToFormattedFriendlyTimeString( - props.secondsOfEvent[key] || 0, - )} -
- ); - })} +
+ No data available for this day
)} - {/* Incidents */} - {props.incidents.length > 0 && ( -
-
- {props.incidents.length} Incident - {props.incidents.length > 1 ? "s" : ""}: -
- {props.incidents.slice(0, 5).map( - (incident: UptimeBarTooltipIncident) => { + {/* Status breakdown */} + {props.statusDurations.length > 0 && ( +
0 ? "8px" : "0" }}> + {props.statusDurations.map( + (status: StatusDuration, index: number) => { return ( -
- {incident.incidentSeverity?.color && ( +
+
- )} - {incident.title} + /> + + {status.label} + +
+ + {OneUptimeDate.secondsToFormattedFriendlyTimeString( + status.seconds, + )} +
); }, )} - {props.incidents.length > 5 && ( -
- +{props.incidents.length - 5} more... -
- )}
)} + {/* Incidents section */} {props.incidents.length > 0 && ( -
- Click for details +
+
+ {props.incidents.length} Incident + {props.incidents.length !== 1 ? "s" : ""} +
+ {props.incidents.slice(0, 3).map( + (incident: UptimeBarTooltipIncident) => { + return ( +
+
+ {incident.title} +
+
+ {incident.incidentSeverity && ( + + {incident.incidentSeverity.name} + + )} + {incident.currentIncidentState && ( + + {incident.currentIncidentState.name} + + )} +
+
+ ); + }, + )} + {props.incidents.length > 3 && ( +
+ +{props.incidents.length - 3} more +
+ )} +
+ Click bar for full details +
)}
diff --git a/Common/UI/Components/Tooltip/Tooltip.tsx b/Common/UI/Components/Tooltip/Tooltip.tsx index 474a67aef5..e7d0624b68 100644 --- a/Common/UI/Components/Tooltip/Tooltip.tsx +++ b/Common/UI/Components/Tooltip/Tooltip.tsx @@ -28,7 +28,9 @@ const Tooltip: FunctionComponent = ( interactive={true} trigger="mouseenter focus" hideOnClick={false} - maxWidth={350} + maxWidth={props.richContent ? 380 : 350} + delay={[100, 0]} + duration={[150, 100]} aria={{ content: "describedby", expanded: "auto",