From f1cbb9acb8cad7f67b0e4101f1d03084e4e9930a Mon Sep 17 00:00:00 2001 From: Yomi Eluwande Date: Thu, 5 Dec 2024 15:55:10 +0100 Subject: [PATCH 1/4] Add a popover menu to the AreaGraph component --- .../src/MetricsGraph/MetricsTooltip/index.tsx | 2 +- .../MetricsGraphStrips/AreaGraph/Tooltip.tsx | 83 +++++++++++++++++++ .../MetricsGraphStrips/AreaGraph/index.tsx | 51 +++++++++++- .../profile/src/MetricsGraphStrips/index.tsx | 5 +- 4 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 ui/packages/shared/profile/src/MetricsGraphStrips/AreaGraph/Tooltip.tsx diff --git a/ui/packages/shared/profile/src/MetricsGraph/MetricsTooltip/index.tsx b/ui/packages/shared/profile/src/MetricsGraph/MetricsTooltip/index.tsx index dd671ed3cae..6cfe8a7bd48 100644 --- a/ui/packages/shared/profile/src/MetricsGraph/MetricsTooltip/index.tsx +++ b/ui/packages/shared/profile/src/MetricsGraph/MetricsTooltip/index.tsx @@ -105,7 +105,7 @@ const MetricsTooltip = ({ const highlightedNameLabel: Label = nameLabel !== undefined ? nameLabel : {name: '', value: ''}; return ( -
+
(null); + const [tooltipPosition, setTooltipPosition] = useState({x, y}); + + const baseOffset = { + x: 16, + y: -8, + }; + + useEffect(() => { + if (tooltipRef.current != null) { + const tooltipWidth = tooltipRef.current.offsetWidth; + + let newX = x + baseOffset.x; + let newY = y + baseOffset.y; + + if (newX + tooltipWidth > containerWidth) { + newX = x - tooltipWidth - baseOffset.x; + } + + if (newY < 0) { + newY = y + Math.abs(baseOffset.y); + } + + setTooltipPosition({x: newX, y: newY}); + } + }, [x, y, containerWidth, baseOffset.x, baseOffset.y]); + + return ( +
+
+
+
Timestamp:
+
+ {formatDate(timestamp, timePattern(timezone), timezone)} +
+
+ +
+
Value:
+
+ {valueFormatter(value, 'nanoseconds', 2)} +
+
+
+
+ ); +} diff --git a/ui/packages/shared/profile/src/MetricsGraphStrips/AreaGraph/index.tsx b/ui/packages/shared/profile/src/MetricsGraphStrips/AreaGraph/index.tsx index 44b915280d4..556ccfd036f 100644 --- a/ui/packages/shared/profile/src/MetricsGraphStrips/AreaGraph/index.tsx +++ b/ui/packages/shared/profile/src/MetricsGraphStrips/AreaGraph/index.tsx @@ -18,6 +18,7 @@ import cx from 'classnames'; import * as d3 from 'd3'; import {NumberDuo} from '../../utils'; +import {Tooltip} from './Tooltip'; export interface DataPoint { timestamp: number; @@ -143,7 +144,7 @@ const ZoomWindow = ({
(undefined); const [dragStart, setDragStart] = useState(undefined); const [isHoveringDragHandle, setIsHoveringDragHandle] = useState(false); + const [hoverData, setHoverData] = useState<{timestamp: number; value: number} | null>(null); + const [isMouseOverGraph, setIsMouseOverGraph] = useState(false); const isDragging = dragStart !== undefined; // Declare the x (horizontal position) scale. @@ -245,12 +248,39 @@ export const AreaGraph = ({
{ - const [x, y] = d3.pointer(e); - setMousePosition([x, y]); + const [xPos, yPos] = d3.pointer(e); + + if ( + xPos >= marginLeft && + xPos <= width - marginRight && + yPos >= marginTop && + yPos <= height - marginBottom + ) { + setMousePosition([xPos, yPos]); + + // Find the closest data point + if (!isHoveringDragHandle && !isDragging) { + const xDate = x.invert(xPos); + const bisect = d3.bisector((d: DataPoint) => d.timestamp).left; + const index = bisect(data, xDate.getTime()); + const dataPoint = data[index]; + if (dataPoint !== undefined) { + setHoverData(dataPoint); + } + } + } else { + setMousePosition(undefined); + setHoverData(null); + } + }} + onMouseEnter={() => { + setIsMouseOverGraph(true); }} onMouseLeave={() => { + setIsMouseOverGraph(false); setMousePosition(undefined); setDragStart(undefined); + setHoverData(null); }} onMouseDown={e => { // only left mouse button @@ -313,6 +343,21 @@ export const AreaGraph = ({ })} >
+ {/* Update Tooltip conditional render */} + {mousePosition !== undefined && + hoverData !== null && + !isDragging && + !isHoveringDragHandle && + isMouseOverGraph && ( + + )} + diff --git a/ui/packages/shared/profile/src/MetricsGraphStrips/index.tsx b/ui/packages/shared/profile/src/MetricsGraphStrips/index.tsx index 62e9b2f60e3..fffc403efa4 100644 --- a/ui/packages/shared/profile/src/MetricsGraphStrips/index.tsx +++ b/ui/packages/shared/profile/src/MetricsGraphStrips/index.tsx @@ -95,7 +95,10 @@ export const MetricsGraphStrips = ({ return (
{ const newCollapsedIndices = [...collapsedIndices]; if (collapsedIndices.includes(i)) { From 66c383c3a258c56587ef870e2194d7c5ccccd400 Mon Sep 17 00:00:00 2001 From: Yomi Eluwande Date: Tue, 10 Dec 2024 14:17:06 +0100 Subject: [PATCH 2/4] display the time with milliseconds precision --- .../src/MetricsGraphStrips/AreaGraph/Tooltip.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ui/packages/shared/profile/src/MetricsGraphStrips/AreaGraph/Tooltip.tsx b/ui/packages/shared/profile/src/MetricsGraphStrips/AreaGraph/Tooltip.tsx index a49c53f2bb5..fc6c5526e6f 100644 --- a/ui/packages/shared/profile/src/MetricsGraphStrips/AreaGraph/Tooltip.tsx +++ b/ui/packages/shared/profile/src/MetricsGraphStrips/AreaGraph/Tooltip.tsx @@ -13,8 +13,10 @@ import {useEffect, useRef, useState} from 'react'; +import {format} from 'date-fns-tz'; + import {useParcaContext} from '@parca/components'; -import {formatDate, timePattern, valueFormatter} from '@parca/utilities'; +import {valueFormatter} from '@parca/utilities'; interface TooltipProps { x: number; @@ -24,6 +26,12 @@ interface TooltipProps { containerWidth: number; } +function formatDateTime(timestamp: Date, timezone?: string): string { + return timezone !== undefined + ? format(timestamp, "yyyy:MM:dd'T'HH:mm:ss.SSS", {timeZone: timezone}) + : format(timestamp, "yyyy:MM:dd'T'HH:mm:ss.SSS", {timeZone: 'UTC'}); +} + export function Tooltip({x, y, timestamp, value, containerWidth}: TooltipProps): JSX.Element { const {timezone} = useParcaContext(); const tooltipRef = useRef(null); @@ -67,7 +75,7 @@ export function Tooltip({x, y, timestamp, value, containerWidth}: TooltipProps):
Timestamp:
- {formatDate(timestamp, timePattern(timezone), timezone)} + {formatDateTime(timestamp, timezone)}
From 16d0fdd65267b85c304059843dd65d0872b348c4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:28:06 +0000 Subject: [PATCH 3/4] [pre-commit.ci lite] apply automatic fixes --- proto/buf.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proto/buf.lock b/proto/buf.lock index c5c24b2df84..ccc8af7b084 100644 --- a/proto/buf.lock +++ b/proto/buf.lock @@ -4,8 +4,8 @@ deps: - remote: buf.build owner: googleapis repository: googleapis - commit: c0913f24652a4cfc95f77d97443a5005 - digest: shake256:0ef3248c6235d420fe61f373154adcde6b94e3297f82472b1d8d8c3747240b61b4a10405e2a6f8ac1c98816ac6e690ea7871024aa5ae0e035cd540214667ceed + commit: acd896313c55464b993332136ded1b6e + digest: shake256:66626d5e4d9c8ecf25cd72bdbfbbf62b9a68e9e9c33dab6b9b39a53a67063eeba6b8493247dd6d6240b1ac1c32eb2dc311484e67dd7d271884a960c2e5ce8c9a - remote: buf.build owner: grpc-ecosystem repository: grpc-gateway From 407f04c85c9914832511e991fbeb2f6eb3ca10d5 Mon Sep 17 00:00:00 2001 From: Yomi Eluwande Date: Tue, 10 Dec 2024 14:28:18 +0100 Subject: [PATCH 4/4] Move function to utilities --- .../src/MetricsGraphStrips/AreaGraph/Tooltip.tsx | 12 ++---------- ui/packages/shared/utilities/src/index.ts | 7 +++++++ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/ui/packages/shared/profile/src/MetricsGraphStrips/AreaGraph/Tooltip.tsx b/ui/packages/shared/profile/src/MetricsGraphStrips/AreaGraph/Tooltip.tsx index fc6c5526e6f..ef2ad07f8b5 100644 --- a/ui/packages/shared/profile/src/MetricsGraphStrips/AreaGraph/Tooltip.tsx +++ b/ui/packages/shared/profile/src/MetricsGraphStrips/AreaGraph/Tooltip.tsx @@ -13,10 +13,8 @@ import {useEffect, useRef, useState} from 'react'; -import {format} from 'date-fns-tz'; - import {useParcaContext} from '@parca/components'; -import {valueFormatter} from '@parca/utilities'; +import {formatDateTimeDownToMS, valueFormatter} from '@parca/utilities'; interface TooltipProps { x: number; @@ -26,12 +24,6 @@ interface TooltipProps { containerWidth: number; } -function formatDateTime(timestamp: Date, timezone?: string): string { - return timezone !== undefined - ? format(timestamp, "yyyy:MM:dd'T'HH:mm:ss.SSS", {timeZone: timezone}) - : format(timestamp, "yyyy:MM:dd'T'HH:mm:ss.SSS", {timeZone: 'UTC'}); -} - export function Tooltip({x, y, timestamp, value, containerWidth}: TooltipProps): JSX.Element { const {timezone} = useParcaContext(); const tooltipRef = useRef(null); @@ -75,7 +67,7 @@ export function Tooltip({x, y, timestamp, value, containerWidth}: TooltipProps):
Timestamp:
- {formatDateTime(timestamp, timezone)} + {formatDateTimeDownToMS(timestamp, timezone)}
diff --git a/ui/packages/shared/utilities/src/index.ts b/ui/packages/shared/utilities/src/index.ts index 8be80d93979..813f96e1548 100644 --- a/ui/packages/shared/utilities/src/index.ts +++ b/ui/packages/shared/utilities/src/index.ts @@ -11,6 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import {format} from 'date-fns-tz'; import colors from 'tailwindcss/colors'; import {Label} from '@parca/client'; @@ -376,3 +377,9 @@ export const isUrlEncoded = (str: string): boolean => { return false; // Invalid encoding } }; + +export function formatDateTimeDownToMS(timestamp: Date, timezone?: string): string { + return timezone !== undefined + ? format(timestamp, "yyyy:MM:dd'T'HH:mm:ss.SSS", {timeZone: timezone}) + : format(timestamp, "yyyy:MM:dd'T'HH:mm:ss.SSS", {timeZone: 'UTC'}); +}