Skip to content

feat(replay): add hovercard to release tag and filter dropdown #90701

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
72 changes: 72 additions & 0 deletions static/app/components/replays/releaseDropdownFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import styled from '@emotion/styled';

import {Button} from 'sentry/components/core/button';
import {DropdownMenu} from 'sentry/components/dropdownMenu';
import {IconEllipsis} from 'sentry/icons';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import {useLocation} from 'sentry/utils/useLocation';
import {useNavigate} from 'sentry/utils/useNavigate';
import useOrganization from 'sentry/utils/useOrganization';
import {makeReleasesPathname} from 'sentry/views/releases/utils/pathnames';
import {makeReplaysPathname} from 'sentry/views/replays/pathnames';
import type {ReplayListLocationQuery} from 'sentry/views/replays/types';

export default function ReleaseDropdownFilter({val}: {val: string}) {
const location = useLocation<ReplayListLocationQuery>();
const navigate = useNavigate();
const organization = useOrganization();

return (
<DropdownMenu
items={[
{
key: 'search',
label: t('Search for replays in this release'),
onAction: () =>
navigate({
pathname: makeReplaysPathname({
path: '/',
organization,
}),
query: {
...location.query,
query: `release:"${val}"`,
},
}),
},
{
key: 'details',
label: t('Go to release details'),
onAction: () =>
navigate(
makeReleasesPathname({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for myself, I'll have to update these calls when #90428 lands

organization,
path: `/${encodeURIComponent(val)}/`,
})
),
},
]}
usePortal
size="xs"
offset={4}
position="bottom"
preventOverflowOptions={{padding: 4}}
flipOptions={{
fallbackPlacements: ['top', 'right-start', 'right-end', 'left-start', 'left-end'],
}}
trigger={triggerProps => (
<TriggerButton
{...triggerProps}
aria-label={t('Actions')}
icon={<IconEllipsis size="xs" />}
size="zero"
/>
)}
/>
);
}

const TriggerButton = styled(Button)`
padding: ${space(0.5)};
`;
36 changes: 33 additions & 3 deletions static/app/components/replays/replayTagsTableRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ import {Tooltip} from 'sentry/components/core/tooltip';
import {AnnotatedText} from 'sentry/components/events/meta/annotatedText';
import {KeyValueTableRow} from 'sentry/components/keyValueTable';
import Link from 'sentry/components/links/link';
import ReleaseDropdownFilter from 'sentry/components/replays/releaseDropdownFilter';
import {CollapsibleValue} from 'sentry/components/structuredEventData/collapsibleValue';
import Version from 'sentry/components/version';
import {space} from 'sentry/styles/space';
import useOrganization from 'sentry/utils/useOrganization';
import {QuickContextHoverWrapper} from 'sentry/views/discover/table/quickContext/quickContextWrapper';
import {ContextType} from 'sentry/views/discover/table/quickContext/utils';

interface Props {
name: string;
Expand All @@ -25,6 +29,8 @@ const expandedViewKeys = [
'sdk.replay.maskingRules',
];

const releaseKeys = ['release', 'releases'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l: this could be a Set


function renderValueList(values: ReactNode[]) {
if (typeof values[0] === 'string') {
return values[0];
Expand All @@ -44,15 +50,32 @@ function renderValueList(values: ReactNode[]) {
}

function ReplayTagsTableRow({name, values, generateUrl}: Props) {
const organization = useOrganization();

const renderTagValue = useMemo(() => {
if (name === 'release') {
if (releaseKeys.includes(name)) {
return values.map((value, index) => (
<Fragment key={`${name}-${index}-${value}`}>
{index > 0 && ', '}
<Version key={index} version={String(value)} anchor={false} withPackage />
<StyledVersionContainer>
<ReleaseDropdownFilter val={String(value)} />
<QuickContextHoverWrapper
dataRow={{release: String(value)}}
contextType={ContextType.RELEASE}
organization={organization}
>
<Version
key={index}
version={String(value)}
truncate={false}
anchor={false}
/>
</QuickContextHoverWrapper>
</StyledVersionContainer>
</Fragment>
));
}

if (
expandedViewKeys.includes(name) &&
renderValueList(values) &&
Expand All @@ -75,7 +98,7 @@ function ReplayTagsTableRow({name, values, generateUrl}: Props) {
</Fragment>
);
});
}, [name, values, generateUrl]);
}, [name, values, generateUrl, organization]);

return (
<KeyValueTableRow
Expand All @@ -87,6 +110,7 @@ function ReplayTagsTableRow({name, values, generateUrl}: Props) {
value={
<ValueContainer>
<StyledTooltip
disabled={releaseKeys.includes(name)}
overlayStyle={
expandedViewKeys.includes(name) ? {textAlign: 'left'} : undefined
}
Expand Down Expand Up @@ -118,3 +142,9 @@ const ValueContainer = styled('div')`
const StyledTooltip = styled(Tooltip)`
${p => p.theme.overflowEllipsis};
`;

const StyledVersionContainer = styled('div')`
display: flex;
justify-content: flex-end;
gap: ${space(0.75)};
`;
18 changes: 16 additions & 2 deletions static/app/views/replays/replayTable/tableCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ import type {ValidSize} from 'sentry/styles/space';
import {space} from 'sentry/styles/space';
import type {Organization} from 'sentry/types/organization';
import {trackAnalytics} from 'sentry/utils/analytics';
import {browserHistory} from 'sentry/utils/browserHistory';
import type EventView from 'sentry/utils/discover/eventView';
import {spanOperationRelativeBreakdownRenderer} from 'sentry/utils/discover/fieldRenderers';
import {getShortEventId} from 'sentry/utils/events';
import {decodeScalar} from 'sentry/utils/queryString';
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
import {useLocation} from 'sentry/utils/useLocation';
import useMedia from 'sentry/utils/useMedia';
import {useNavigate} from 'sentry/utils/useNavigate';
import useProjects from 'sentry/utils/useProjects';
import type {ReplayListRecordWithTx} from 'sentry/views/performance/transactionSummary/transactionReplays/useReplaysWithTxData';
import {makeReplaysPathname} from 'sentry/views/replays/pathnames';
Expand All @@ -53,10 +53,12 @@ function generateAction({
value,
edit,
location,
navigate,
}: {
edit: EditType;
key: string;
location: Location<ReplayListLocationQuery>;
navigate: ReturnType<typeof useNavigate>;
value: string;
}) {
const search = new MutableSearch(decodeScalar(location.query.query) || '');
Expand All @@ -65,7 +67,7 @@ function generateAction({
edit === 'set' ? search.setFilterValues(key, [value]) : search.removeFilter(key);

const onAction = () => {
browserHistory.push({
navigate({
pathname: location.pathname,
query: {
...location.query,
Expand All @@ -87,6 +89,8 @@ function OSBrowserDropdownFilter({
version: string | null;
}) {
const location = useLocation<ReplayListLocationQuery>();
const navigate = useNavigate();

return (
<DropdownMenu
items={[
Expand All @@ -107,6 +111,7 @@ function OSBrowserDropdownFilter({
value: name ?? '',
edit: 'set',
location,
navigate,
}),
},
{
Expand All @@ -117,6 +122,7 @@ function OSBrowserDropdownFilter({
value: name ?? '',
edit: 'remove',
location,
navigate,
}),
},
],
Expand All @@ -140,6 +146,7 @@ function OSBrowserDropdownFilter({
value: version ?? '',
edit: 'set',
location,
navigate,
}),
},
{
Expand All @@ -150,6 +157,7 @@ function OSBrowserDropdownFilter({
value: version ?? '',
edit: 'remove',
location,
navigate,
}),
},
],
Expand Down Expand Up @@ -192,6 +200,8 @@ function NumericDropdownFilter({
triggerOverlay?: boolean;
}) {
const location = useLocation<ReplayListLocationQuery>();
const navigate = useNavigate();

return (
<DropdownMenu
items={[
Expand All @@ -203,6 +213,7 @@ function NumericDropdownFilter({
value: formatter(val),
edit: 'set',
location,
navigate,
}),
},
{
Expand All @@ -213,6 +224,7 @@ function NumericDropdownFilter({
value: '>' + formatter(val),
edit: 'set',
location,
navigate,
}),
},
{
Expand All @@ -223,6 +235,7 @@ function NumericDropdownFilter({
value: '<' + formatter(val),
edit: 'set',
location,
navigate,
}),
},
{
Expand All @@ -233,6 +246,7 @@ function NumericDropdownFilter({
value: formatter(val),
edit: 'remove',
location,
navigate,
}),
},
]}
Expand Down
Loading