From ad4ce6f1fe291c45570e1045cb52bea17f9a8df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Fri, 13 Nov 2026 09:42:00 -0500 Subject: [PATCH] fix(explore): Portal attribute row action menus out of clipping containers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The attribute "…" menu rendered inline, so the Explore metrics samples table's scroll container clipped it. Row action menus now always portal, into the surrounding drawer or modal when there is one so they still layer above it. Fixes #130918 Fixes EXP-1466 --- .../events/eventTags/eventTagsTreeRow.tsx | 10 +---- .../keyValueTree/keyValueTreeRowActions.tsx | 14 +++++-- .../attributesTree.spec.tsx | 42 +++++++++++++++++-- .../scraps/src/dropdownMenu/dropdownMenu.tsx | 2 +- 4 files changed, 51 insertions(+), 17 deletions(-) diff --git a/static/app/components/events/eventTags/eventTagsTreeRow.tsx b/static/app/components/events/eventTags/eventTagsTreeRow.tsx index 0995f0dea238..b696fd81d653 100644 --- a/static/app/components/events/eventTags/eventTagsTreeRow.tsx +++ b/static/app/components/events/eventTags/eventTagsTreeRow.tsx @@ -283,15 +283,7 @@ function EventTagsTreeRowDropdown({ visitExternalLinkAction(content.value), ]; - return ( - - ); + return ; } function EventTagsTreeValue({ diff --git a/static/app/components/keyValueTree/keyValueTreeRowActions.tsx b/static/app/components/keyValueTree/keyValueTreeRowActions.tsx index a05d1bbcb506..3c1f73dbe605 100644 --- a/static/app/components/keyValueTree/keyValueTreeRowActions.tsx +++ b/static/app/components/keyValueTree/keyValueTreeRowActions.tsx @@ -1,10 +1,12 @@ -import {useState} from 'react'; +import {useContext, useState} from 'react'; +import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; import {IconEllipsis} from '@sentry/icons/ellipsis'; import {type MenuItemProps} from '@sentry/scraps/dropdownMenu'; import {OverlayTrigger} from '@sentry/scraps/overlayTrigger'; import {RevealOnHover} from '@sentry/scraps/revealOnHover'; +import {TooltipContext} from '@sentry/scraps/tooltip'; import {openNavigateToExternalLinkModal} from 'sentry/actionCreators/modal'; import {t} from 'sentry/locale'; @@ -28,18 +30,22 @@ export function visitExternalLinkAction(value: KeyValueTreeValue): MenuItemProps export function KeyValueTreeRowActions({ ariaLabel, items, - usePortal, }: { ariaLabel: string; items: MenuItemProps[]; - usePortal?: boolean; }) { + const theme = useTheme(); const [isMenuOpen, setIsMenuOpen] = useState(false); + const {container} = useContext(TooltipContext); return ( { } // jsdom does not evaluate :hover styles, so bypass the hidden action's pointer-events check. await userEvent.click(actionsButton, {pointerEventsCheck: 0}); - expect(await within(row).findByText('Visible Action')).toBeInTheDocument(); - expect(await within(row).findByText('Disabled Action')).toBeInTheDocument(); - expect(within(row).queryByText('Hidden Action')).not.toBeInTheDocument(); + expect(actionsButton).toHaveAttribute('aria-expanded', 'true'); + expect(within(row).queryByRole('menu')).not.toBeInTheDocument(); + expect(await screen.findByText('Visible Action')).toBeInTheDocument(); + expect(await screen.findByText('Disabled Action')).toBeInTheDocument(); + expect(screen.queryByText('Hidden Action')).not.toBeInTheDocument(); } }); @@ -418,4 +422,36 @@ describe('attributesTree', () => { screen.getByTestId('tree-key-sentry.logger.name') ); }); + + it('renders the actions menu in the surrounding overlay container when one is provided', async () => { + function OverlayContainer({children}: {children: React.ReactNode}) { + const [container, setContainer] = useState(null); + + return ( +
+ {children} +
+ ); + } + + render( + + + + ); + + await userEvent.click(screen.getByRole('button', {name: 'Attribute Actions Menu'}), { + pointerEventsCheck: 0, + }); + + expect( + await within(screen.getByTestId('overlay-container')).findByRole('menu') + ).toBeInTheDocument(); + expect( + within(screen.getByTestId('attribute-tree-row')).queryByRole('menu') + ).not.toBeInTheDocument(); + }); }); diff --git a/static/packages/scraps/src/dropdownMenu/dropdownMenu.tsx b/static/packages/scraps/src/dropdownMenu/dropdownMenu.tsx index 3260a9f24af1..9828ddb641fb 100644 --- a/static/packages/scraps/src/dropdownMenu/dropdownMenu.tsx +++ b/static/packages/scraps/src/dropdownMenu/dropdownMenu.tsx @@ -63,7 +63,7 @@ interface BaseDropdownMenuProps /** * Reference to the container element that the portal should be rendered into. */ - portalContainerRef?: React.RefObject; + portalContainerRef?: React.RefObject; /** * Tag name for the outer wrap, defaults to `div` */