Skip to content

Commit

Permalink
feat(issues): Add copy to tag details, visibility on hover (#82412)
Browse files Browse the repository at this point in the history
  • Loading branch information
scttcper authored Dec 19, 2024
1 parent b9b590d commit f9a9dcf
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ describe('TagDetailsDrawerContent', () => {
expect(screen.getByText('17%')).toBeInTheDocument();
// Count column
expect(screen.getByText('3')).toBeInTheDocument();

// Displays dropdown menu
await userEvent.hover(screen.getByText('David Cramer'));
expect(
screen.getByRole('button', {name: 'Tag Value Actions Menu'})
).toBeInTheDocument();
await userEvent.click(screen.getByRole('button', {name: 'Tag Value Actions Menu'}));
expect(
await screen.findByRole('menuitemradio', {name: 'Copy tag value to clipboard'})
).toBeInTheDocument();
});

it('can page through tag values', async () => {
Expand Down
132 changes: 80 additions & 52 deletions static/app/views/issueDetails/groupTags/tagDetailsDrawerContent.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {Fragment, useState} from 'react';
import {css} from '@emotion/react';
import styled from '@emotion/styled';
import type {LocationDescriptor} from 'history';

Expand All @@ -21,6 +20,7 @@ import type {Group, Tag, TagValue} from 'sentry/types/group';
import {percent} from 'sentry/utils';
import {SavedQueryDatasets} from 'sentry/utils/discover/types';
import {isUrl} from 'sentry/utils/string/isUrl';
import useCopyToClipboard from 'sentry/utils/useCopyToClipboard';
import {useLocation} from 'sentry/utils/useLocation';
import {useNavigate} from 'sentry/utils/useNavigate';
import useOrganization from 'sentry/utils/useOrganization';
Expand Down Expand Up @@ -155,10 +155,9 @@ function TagDetailsRow({
tagValue: TagValue;
}) {
const organization = useOrganization();
const [isHovered, setIsHovered] = useState(false);

const key = tagValue.key ?? tag.key;
const query = {query: tagValue.query || `${key}:"${tagValue.value}"`};
const eventView = useIssueDetailsEventView({group, queryProps: query});
const allEventsLocation = {
pathname: `/organizations/${organization.slug}/issues/${group.id}/events/`,
query,
Expand All @@ -167,7 +166,7 @@ function TagDetailsRow({
const displayPercentage = percentage < 1 ? '<1%' : `${percentage.toFixed(0)}%`;

return (
<Row onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>
<Row>
<TagDetailsValue
valueLocation={allEventsLocation}
tagKey={key}
Expand All @@ -181,43 +180,7 @@ function TagDetailsRow({
) : (
'--'
)}
<DropdownMenu
size="xs"
trigger={triggerProps => (
<ActionButton
{...triggerProps}
isHidden={!isHovered}
size="xs"
icon={<IconEllipsis />}
aria-label={t('Tag Value Actions Menu')}
/>
)}
items={[
{
key: 'open-in-discover',
label: t('Open in Discover'),
to: eventView.getResultsViewUrlTarget(
organization.slug,
false,
hasDatasetSelector(organization) ? SavedQueryDatasets.ERRORS : undefined
),
hidden: !group || !organization.features.includes('discover-basic'),
},
{
key: 'view-events',
label: t('View other events with this tag value'),
to: allEventsLocation,
},
{
key: 'view-issues',
label: t('View issues with this tag value'),
to: {
pathname: `/organizations/${organization.slug}/issues/`,
query,
},
},
]}
/>
<TagValueActionsMenu group={group} tag={tag} tagValue={tagValue} />
</Row>
);
}
Expand Down Expand Up @@ -263,6 +226,72 @@ function TagDetailsValue({
);
}

function TagValueActionsMenu({
group,
tag,
tagValue,
}: {
group: Group;
tag: Tag;
tagValue: TagValue;
}) {
const organization = useOrganization();
const {onClick: handleCopy} = useCopyToClipboard({
text: tagValue.value,
});
const key = tagValue.key ?? tag.key;
const query = {query: tagValue.query || `${key}:"${tagValue.value}"`};
const eventView = useIssueDetailsEventView({group, queryProps: query});
const [isVisible, setIsVisible] = useState(false);

return (
<DropdownMenu
size="xs"
className={isVisible ? '' : 'invisible'}
onOpenChange={isOpen => setIsVisible(isOpen)}
triggerProps={{
'aria-label': t('Tag Value Actions Menu'),
icon: <IconEllipsis />,
showChevron: false,
size: 'xs',
}}
items={[
{
key: 'open-in-discover',
label: t('Open in Discover'),
to: eventView.getResultsViewUrlTarget(
organization.slug,
false,
hasDatasetSelector(organization) ? SavedQueryDatasets.ERRORS : undefined
),
hidden: !group || !organization.features.includes('discover-basic'),
},
{
key: 'view-events',
label: t('View other events with this tag value'),
to: {
pathname: `/organizations/${organization.slug}/issues/${group.id}/events/`,
query,
},
},
{
key: 'view-issues',
label: t('Search issues with this tag value'),
to: {
pathname: `/organizations/${organization.slug}/issues/`,
query,
},
},
{
key: 'copy-value',
label: t('Copy tag value to clipboard'),
onAction: handleCopy,
},
]}
/>
);
}

const Table = styled('div')`
display: grid;
grid-template-columns: repeat(4, auto) 1fr auto;
Expand Down Expand Up @@ -313,6 +342,16 @@ const Row = styled(Body)`
align-items: center;
border-radius: 4px;
padding: ${space(0.25)} ${space(1)};
.invisible {
visibility: hidden;
}
&:hover,
&:active {
.invisible {
visibility: visible;
}
}
`;

const Value = styled('div')`
Expand Down Expand Up @@ -343,17 +382,6 @@ const OverflowTimeSince = styled(TimeSince)`
overflow: hidden;
`;

// We need to do the hiding here so that focus styles from the `Button` component take precedent
const ActionButton = styled(Button)<{isHidden: boolean}>`
${p =>
p.isHidden &&
css`
border-color: transparent;
color: transparent;
background: transparent;
`}
`;

const ExternalLinkbutton = styled(Button)`
color: ${p => p.theme.subText};
`;

0 comments on commit f9a9dcf

Please sign in to comment.