diff --git a/apps/studio/components/ui/Charts/ChartHighlightActions.tsx b/apps/studio/components/ui/Charts/ChartHighlightActions.tsx index ddc0460555e..2184b6ff766 100644 --- a/apps/studio/components/ui/Charts/ChartHighlightActions.tsx +++ b/apps/studio/components/ui/Charts/ChartHighlightActions.tsx @@ -48,7 +48,7 @@ export const ChartHighlightActions = ({ const formatChartDate = useFormatDateTime() useEffect(() => { - setIsOpen(!!chartHighlight?.popoverPosition && selectedRangeStart !== selectedRangeEnd) + setIsOpen(!!chartHighlight?.popoverPosition) }, [chartHighlight?.popoverPosition]) const ctx: ChartHighlightActionContext | undefined = @@ -80,8 +80,12 @@ export const ChartHighlightActions = ({ return [...defaultActions, ...provided] }, [defaultActions, actions]) + const positionKey = chartHighlight?.popoverPosition + ? `${chartHighlight.popoverPosition.x}-${chartHighlight.popoverPosition.y}` + : 'closed' + return ( - + clearHighlight?.()} onInteractOutside={(e) => { const target = e.target as Element | null diff --git a/apps/studio/components/ui/Charts/useChartHighlight.tsx b/apps/studio/components/ui/Charts/useChartHighlight.tsx index e4f8593e9af..1fa248b8b80 100644 --- a/apps/studio/components/ui/Charts/useChartHighlight.tsx +++ b/apps/studio/components/ui/Charts/useChartHighlight.tsx @@ -4,8 +4,12 @@ import { useState } from 'react' type ChartHighlightMouseEvent = { activeLabel?: string coordinates?: string + chartX?: number + chartY?: number } +type Pixel = { x: number; y: number } + export interface ChartHighlight { left: string | undefined right: string | undefined @@ -28,6 +32,7 @@ export function useChartHighlight(): ChartHighlight { const [isSelecting, setIsSelecting] = useState(false) const [popoverPosition, setPopoverPosition] = useState<{ x: number; y: number } | null>(null) const [initialPoint, setInitialPoint] = useState(undefined) + const [anchorPixel, setAnchorPixel] = useState(undefined) const handleMouseDown = (e: ChartHighlightMouseEvent) => { clearHighlight() @@ -37,6 +42,9 @@ export function useChartHighlight(): ChartHighlight { setRight(e.activeLabel) setInitialPoint(e.activeLabel) setCoordinates({ left: e.coordinates, right: e.coordinates }) + if (typeof e.chartX === 'number' && typeof e.chartY === 'number') { + setAnchorPixel({ x: e.chartX, y: e.chartY }) + } } const handleMouseMove = (e: ChartHighlightMouseEvent) => { @@ -69,7 +77,11 @@ export function useChartHighlight(): ChartHighlight { setIsSelecting(false) setInitialPoint(undefined) - if ( + // Anchor the popover to where the selection started rather than wherever + // the mouse happened to be released. + if (anchorPixel) { + setPopoverPosition(anchorPixel) + } else if ( typeof e === 'object' && e !== null && 'chartX' in e && @@ -87,6 +99,7 @@ export function useChartHighlight(): ChartHighlight { setCoordinates({ left: undefined, right: undefined }) setPopoverPosition(null) setInitialPoint(undefined) + setAnchorPixel(undefined) } return { diff --git a/apps/studio/components/ui/DataTable/TimelineChart.tsx b/apps/studio/components/ui/DataTable/TimelineChart.tsx index 64524e9cc1d..3532853b4e3 100644 --- a/apps/studio/components/ui/DataTable/TimelineChart.tsx +++ b/apps/studio/components/ui/DataTable/TimelineChart.tsx @@ -48,8 +48,7 @@ export function TimelineChart({ const { table } = useDataTable() const chartHighlight = useChartHighlight() - const showHighlight = - chartHighlight?.left && chartHighlight?.right && chartHighlight?.left !== chartHighlight?.right + const showHighlight = !!chartHighlight?.left && !!chartHighlight?.right // REMINDER: date has to be a string for tooltip label to work - don't ask me why const chart = useMemo( @@ -69,6 +68,11 @@ export function TimelineChart({ return { interval, period: calculatePeriod(interval) } }, [data]) + const bucketWidthMs = useMemo( + () => (data.length > 1 ? Math.abs(data[1].timestamp - data[0].timestamp) : 0), + [data] + ) + const highlightActions: ChartHighlightAction[] = useMemo( () => [ { @@ -76,7 +80,9 @@ export function TimelineChart({ label: 'Filter logs to selected range', icon: , onSelect: ({ start, end, clear }) => { - const [left, right] = [start, end].sort( + const resolvedEnd = + start === end ? new Date(new Date(start).getTime() + bucketWidthMs).toString() : end + const [left, right] = [start, resolvedEnd].sort( (a, b) => new Date(a).getTime() - new Date(b).getTime() ) table.getColumn(resolvedFilterColumnId)?.setFilterValue([new Date(left), new Date(right)]) @@ -101,13 +107,23 @@ export function TimelineChart({ { + onMouseDown={({ activeLabel, activeTooltipIndex, chartX, chartY }) => { if (activeTooltipIndex === undefined || activeTooltipIndex === null) return - chartHighlight.handleMouseDown({ activeLabel, coordinates: activeLabel }) + chartHighlight.handleMouseDown({ + activeLabel, + coordinates: activeLabel, + chartX, + chartY, + }) }} - onMouseMove={({ activeLabel, activeTooltipIndex }) => { + onMouseMove={({ activeLabel, activeTooltipIndex, chartX, chartY }) => { if (activeTooltipIndex === undefined || activeTooltipIndex === null) return - chartHighlight.handleMouseMove({ activeLabel, coordinates: activeLabel }) + chartHighlight.handleMouseMove({ + activeLabel, + coordinates: activeLabel, + chartX, + chartY, + }) }} onMouseUp={chartHighlight.handleMouseUp} style={{ cursor: 'crosshair' }} @@ -131,26 +147,37 @@ export function TimelineChart({ return format(date, 'LLL dd, y') }} /> - {!chartHighlight.popoverPosition && ( - { - const date = new Date(value) - if (isNaN(date.getTime())) return 'N/A' - if (timerange.period === '10m') { - return format(date, 'LLL dd, HH:mm:ss') - } - return format(date, 'LLL dd, y HH:mm') - }} - /> - } - /> - )} + { + const date = new Date(value) + if (isNaN(date.getTime())) return 'N/A' + if (timerange.period === '10m') { + return format(date, 'LLL dd, HH:mm:ss') + } + return format(date, 'LLL dd, y HH:mm') + }} + /> + } + /> {/* TODO: we could use the `{timestamp, ...rest} = data[0]` to dynamically create the bars but that would mean the order can be very much random */} - - - + + + {showHighlight && (