Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions proto/buf.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ const MetricsTooltip = ({
const highlightedNameLabel: Label = nameLabel !== undefined ? nameLabel : {name: '', value: ''};

return (
<div ref={setPopperElement} style={styles.popper} {...attributes.popper} className="z-10">
<div ref={setPopperElement} style={styles.popper} {...attributes.popper} className="z-20">
<div className="flex max-w-md">
<div className="m-auto">
<div
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2022 The Parca Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {useEffect, useRef, useState} from 'react';

import {useParcaContext} from '@parca/components';
import {formatDateTimeDownToMS, valueFormatter} from '@parca/utilities';

interface TooltipProps {
x: number;
y: number;
timestamp: Date;
value: number;
containerWidth: number;
}

export function Tooltip({x, y, timestamp, value, containerWidth}: TooltipProps): JSX.Element {
const {timezone} = useParcaContext();
const tooltipRef = useRef<HTMLDivElement>(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 (
<div
ref={tooltipRef}
className="absolute bg-white dark:bg-gray-800 rounded-md shadow-lg border border-gray-200 dark:border-gray-700 p-2 text-sm z-20"
style={{
left: `${tooltipPosition.x}px`,
top: `${tooltipPosition.y}px`,
pointerEvents: 'none',
}}
>
<div className="flex flex-col gap-1">
<div className="flex gap-1 items-center">
<div>Timestamp:</div>
<div className="text-gray-600 dark:text-gray-300">
{formatDateTimeDownToMS(timestamp, timezone)}
</div>
</div>

<div className="flex gap-1 items-center">
<div>Value:</div>
<div className="text-gray-600 dark:text-gray-300">
{valueFormatter(value, 'nanoseconds', 2)}
</div>
</div>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -143,7 +144,7 @@ const ZoomWindow = ({
<div
style={{height: '100%', width: beforeWidth, left: beforeStart}}
className={cx(
'bg-gray-500/50 absolute top-0 border-r-2 border-gray-900 dark:border-gray-100 z-20'
'bg-gray-500/50 absolute top-0 border-r-2 border-gray-900 dark:border-gray-100 z-10'
)}
>
<div
Expand Down Expand Up @@ -208,6 +209,8 @@ export const AreaGraph = ({
const [mousePosition, setMousePosition] = useState<NumberDuo | undefined>(undefined);
const [dragStart, setDragStart] = useState<number | undefined>(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.
Expand Down Expand Up @@ -245,12 +248,39 @@ export const AreaGraph = ({
<div
style={{height, width}}
onMouseMove={e => {
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
Expand Down Expand Up @@ -313,6 +343,21 @@ export const AreaGraph = ({
})}
></div>

{/* Update Tooltip conditional render */}
{mousePosition !== undefined &&
hoverData !== null &&
!isDragging &&
!isHoveringDragHandle &&
isMouseOverGraph && (
<Tooltip
x={mousePosition[0]}
y={mousePosition[1]}
timestamp={new Date(hoverData.timestamp)}
value={hoverData.value}
containerWidth={width}
/>
)}

<svg style={{width: '100%', height: '100%'}}>
<path fill={fill} d={area(data) as string} className="opacity-80" />
</svg>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ export const MetricsGraphStrips = ({
return (
<div className="relative min-h-5" style={{width: width ?? 1468}} key={labelStr}>
<div
className="text-xs absolute top-0 left-0 flex gap-[2px] items-center bg-white/50 px-1 rounded-sm cursor-pointer z-30"
className="text-xs absolute top-0 left-0 flex gap-[2px] items-center bg-white/50 px-1 rounded-sm cursor-pointer"
style={{
zIndex: 15,
}}
onClick={() => {
const newCollapsedIndices = [...collapsedIndices];
if (collapsedIndices.includes(i)) {
Expand Down
7 changes: 7 additions & 0 deletions ui/packages/shared/utilities/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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'});
}