diff --git a/static/app/components/events/contexts/knownContext/user.spec.tsx b/static/app/components/events/contexts/knownContext/user.spec.tsx index 6332006eb7f94a..57f490837428c4 100644 --- a/static/app/components/events/contexts/knownContext/user.spec.tsx +++ b/static/app/components/events/contexts/knownContext/user.spec.tsx @@ -1,6 +1,6 @@ import {EventFixture} from 'sentry-fixture/event'; -import {render, screen} from 'sentry-test/reactTestingLibrary'; +import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; import ContextCard from 'sentry/components/events/contexts/contextCard'; import { @@ -36,7 +36,21 @@ const MOCK_REDACTION = { describe('UserContext', () => { it('returns values and according to the parameters', () => { - expect(getUserContextData({data: MOCK_USER_CONTEXT})).toEqual([ + const result = getUserContextData({data: MOCK_USER_CONTEXT}); + + // Extract the actionButton from the email item for comparison + const emailItem = result.find(item => item.key === 'email'); + const {actionButton, ...emailItemWithoutButton} = emailItem || {}; + + expect( + result.map(item => { + if (item.key === 'email') { + const {actionButton: _, ...rest} = item; + return rest; + } + return item; + }) + ).toEqual([ { key: 'email', subject: 'Email', @@ -65,6 +79,9 @@ describe('UserContext', () => { meta: undefined, }, ]); + + // Verify actionButton exists for email + expect(actionButton).toBeDefined(); }); it('renders with meta annotations correctly', () => { @@ -90,4 +107,22 @@ describe('UserContext', () => { expect(screen.getByText('Name')).toBeInTheDocument(); expect(screen.getByText(/redacted/)).toBeInTheDocument(); }); + + it('allows copying email to clipboard', async () => { + const mockCopy = jest.fn().mockResolvedValue(''); + Object.assign(navigator, { + clipboard: { + writeText: mockCopy, + }, + }); + + const event = EventFixture(); + render( + + ); + + const copyButton = screen.getByRole('button', {name: 'Copy email to clipboard'}); + await userEvent.click(copyButton); + expect(mockCopy).toHaveBeenCalledWith('leander.rodrigues@sentry.io'); + }); }); diff --git a/static/app/components/events/contexts/knownContext/user.tsx b/static/app/components/events/contexts/knownContext/user.tsx index 261000f7ed2d39..d44a385fe529b4 100644 --- a/static/app/components/events/contexts/knownContext/user.tsx +++ b/static/app/components/events/contexts/knownContext/user.tsx @@ -1,7 +1,12 @@ +import {useCallback} from 'react'; + +import {Button} from 'sentry/components/core/button'; import {getContextKeys} from 'sentry/components/events/contexts/utils'; +import {IconCopy} from 'sentry/icons'; import {t} from 'sentry/locale'; import type {KeyValueListData} from 'sentry/types/group'; import {defined} from 'sentry/utils'; +import useCopyToClipboard from 'sentry/utils/useCopyToClipboard'; enum UserContextKeys { ID = 'id', @@ -104,6 +109,9 @@ export function getUserContextData({ ? `mailto:${data.email}` : undefined, }, + actionButton: defined(data.email) ? ( + + ) : undefined, }; case UserContextKeys.GEO: return { @@ -125,3 +133,27 @@ export function getUserContextData({ .filter(item => defined(item.value) || defined(meta?.[item.key])) ); } + +function CopyEmailButton({email}: {email: string}) { + const {copy} = useCopyToClipboard(); + + const handleCopy = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + copy(email, {successMessage: t('Email copied to clipboard')}); + }, + [copy, email] + ); + + return ( +