Skip to content

Commit e68b618

Browse files
authored
ref(replay): Refactor some replay breadcrumb rendering (#97443)
Moved some things around to make the props simpler. Also converting from custom components to more Flex and Text. I was near here already, re-using some patterns, and took the time to poke around.
1 parent 15b60e3 commit e68b618

File tree

3 files changed

+97
-97
lines changed

3 files changed

+97
-97
lines changed
Lines changed: 39 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,66 @@
11
import type {ReactNode} from 'react';
2-
import {isValidElement} from 'react';
2+
import {useCallback} from 'react';
33
import styled from '@emotion/styled';
44

55
import {Button} from 'sentry/components/core/button';
6+
import {Flex} from 'sentry/components/core/layout';
7+
import {Text} from 'sentry/components/core/text';
68
import {Tooltip} from 'sentry/components/core/tooltip';
7-
import StructuredEventData from 'sentry/components/structuredEventData';
89
import {t} from 'sentry/locale';
9-
import {space} from 'sentry/styles/space';
10+
import {trackAnalytics} from 'sentry/utils/analytics';
1011
import type {ReplayFrame, WebVitalFrame} from 'sentry/utils/replays/types';
1112
import {isSpanFrame} from 'sentry/utils/replays/types';
12-
import type {OnExpandCallback} from 'sentry/views/replays/detail/useVirtualizedInspector';
13+
import useOrganization from 'sentry/utils/useOrganization';
1314

1415
interface Props {
1516
allowShowSnippet: boolean;
1617
description: ReactNode;
1718
frame: ReplayFrame | WebVitalFrame;
18-
onClickViewHtml: (e: React.MouseEvent<HTMLButtonElement>) => void;
19-
onInspectorExpanded: OnExpandCallback;
19+
onShowSnippet: () => void;
2020
showSnippet: boolean;
21-
className?: string;
22-
expandPaths?: string[];
2321
}
2422

2523
export function BreadcrumbDescription({
26-
description,
2724
allowShowSnippet,
28-
showSnippet,
25+
description,
2926
frame,
30-
expandPaths,
31-
onInspectorExpanded,
32-
onClickViewHtml,
27+
onShowSnippet,
28+
showSnippet,
3329
}: Props) {
34-
if (
35-
typeof description === 'string' ||
36-
(description !== undefined && isValidElement(description))
37-
) {
38-
return (
39-
<DescriptionWrapper>
40-
<Description title={description} showOnlyOnOverflow isHoverable>
41-
{description}
42-
</Description>
43-
44-
{allowShowSnippet &&
45-
!showSnippet &&
46-
frame.data?.nodeId !== undefined &&
47-
!isSpanFrame(frame) && (
48-
<ViewHtmlButton priority="link" onClick={onClickViewHtml} size="xs">
49-
{t('View HTML')}
50-
</ViewHtmlButton>
51-
)}
52-
</DescriptionWrapper>
53-
);
54-
}
30+
const organization = useOrganization();
31+
const handleViewHtml = useCallback(
32+
(e: React.MouseEvent<HTMLButtonElement>) => {
33+
onShowSnippet();
34+
e.preventDefault();
35+
e.stopPropagation();
36+
trackAnalytics('replay.view-html', {
37+
organization,
38+
breadcrumb_type: 'category' in frame ? frame.category : 'unknown',
39+
});
40+
},
41+
[onShowSnippet, organization, frame]
42+
);
5543

5644
return (
57-
<Wrapper>
58-
<StructuredEventData
59-
initialExpandedPaths={expandPaths ?? []}
60-
onToggleExpand={(expandedPaths, path) => {
61-
onInspectorExpanded(
62-
path,
63-
Object.fromEntries(expandedPaths.map(item => [item, true]))
64-
);
65-
}}
66-
data={description}
67-
withAnnotatedText
68-
/>
69-
</Wrapper>
45+
<Flex gap="lg" justify="between" align="start">
46+
<Tooltip title={description} showOnlyOnOverflow isHoverable skipWrapper>
47+
<Text ellipsis size="xs" tabular variant="muted">
48+
{description}
49+
</Text>
50+
</Tooltip>
51+
52+
{allowShowSnippet &&
53+
!showSnippet &&
54+
frame.data?.nodeId !== undefined &&
55+
!isSpanFrame(frame) && (
56+
<NoWrapButton priority="link" onClick={handleViewHtml} size="xs">
57+
{t('View HTML')}
58+
</NoWrapButton>
59+
)}
60+
</Flex>
7061
);
7162
}
7263

73-
const Description = styled(Tooltip)`
74-
${p => p.theme.overflowEllipsis};
75-
font-size: 0.7rem;
76-
font-variant-numeric: tabular-nums;
77-
line-height: ${p => p.theme.text.lineHeightBody};
78-
color: ${p => p.theme.subText};
79-
`;
80-
81-
const DescriptionWrapper = styled('div')`
82-
display: flex;
83-
gap: ${space(1)};
84-
justify-content: space-between;
85-
`;
86-
87-
const ViewHtmlButton = styled(Button)`
64+
const NoWrapButton = styled(Button)`
8865
white-space: nowrap;
8966
`;
90-
91-
const Wrapper = styled('div')`
92-
pre {
93-
margin: 0;
94-
}
95-
`;

static/app/components/replays/breadcrumbs/breadcrumbItem.tsx

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type {CSSProperties} from 'react';
2-
import {useCallback, useEffect, useRef} from 'react';
2+
import {isValidElement, useEffect, useRef} from 'react';
33
import {useTheme} from '@emotion/react';
44
import styled from '@emotion/styled';
55

@@ -8,17 +8,16 @@ import {BreadcrumbCodeSnippet} from 'sentry/components/replays/breadcrumbs/bread
88
import {BreadcrumbComparisonButton} from 'sentry/components/replays/breadcrumbs/breadcrumbComparisonButton';
99
import {BreadcrumbDescription} from 'sentry/components/replays/breadcrumbs/breadcrumbDescription';
1010
import {BreadcrumbIssueLink} from 'sentry/components/replays/breadcrumbs/breadcrumbIssueLink';
11+
import {BreadcrumbStructuredData} from 'sentry/components/replays/breadcrumbs/breadcrumbStructuredData';
1112
import {BreadcrumbWebVital} from 'sentry/components/replays/breadcrumbs/breadcrumbWebVital';
1213
import {Timeline} from 'sentry/components/timeline';
1314
import {space} from 'sentry/styles/space';
14-
import {trackAnalytics} from 'sentry/utils/analytics';
1515
import type {Extraction} from 'sentry/utils/replays/extractDomNodes';
1616
import getFrameDetails from 'sentry/utils/replays/getFrameDetails';
1717
import useExtractDomNodes from 'sentry/utils/replays/hooks/useExtractDomNodes';
1818
import {useReplayReader} from 'sentry/utils/replays/playback/providers/replayReaderProvider';
1919
import type {ReplayFrame} from 'sentry/utils/replays/types';
2020
import {isErrorFrame} from 'sentry/utils/replays/types';
21-
import useOrganization from 'sentry/utils/useOrganization';
2221
import TimestampButton from 'sentry/views/replays/detail/timestampButton';
2322
import type {OnExpandCallback} from 'sentry/views/replays/detail/useVirtualizedInspector';
2423

@@ -42,7 +41,7 @@ interface Props {
4241
updateDimensions?: () => void;
4342
}
4443

45-
function BreadcrumbItem({
44+
export default function BreadcrumbItem({
4645
className,
4746
frame,
4847
expandPaths,
@@ -62,7 +61,6 @@ function BreadcrumbItem({
6261
const {colorGraphicsToken, description, title, icon} = getFrameDetails(frame);
6362
const colorHex = theme.tokens.graphics[colorGraphicsToken];
6463
const replay = useReplayReader();
65-
const organization = useOrganization();
6664
const {data: extraction, isPending} = useExtractDomNodes({
6765
replay,
6866
frame,
@@ -82,19 +80,6 @@ function BreadcrumbItem({
8280
}
8381
}, [isPending, updateDimensions, showSnippet]);
8482

85-
const handleViewHtml = useCallback(
86-
(e: React.MouseEvent<HTMLButtonElement>) => {
87-
onShowSnippet();
88-
e.preventDefault();
89-
e.stopPropagation();
90-
trackAnalytics('replay.view-html', {
91-
organization,
92-
breadcrumb_type: 'category' in frame ? frame.category : 'unknown',
93-
});
94-
},
95-
[onShowSnippet, organization, frame]
96-
);
97-
9883
return (
9984
<StyledTimelineItem
10085
ref={ref}
@@ -120,15 +105,22 @@ function BreadcrumbItem({
120105
onMouseLeave={() => onMouseLeave(frame)}
121106
>
122107
<ErrorBoundary mini>
123-
<BreadcrumbDescription
124-
description={description}
125-
frame={frame}
126-
allowShowSnippet={allowShowSnippet}
127-
showSnippet={showSnippet}
128-
onClickViewHtml={handleViewHtml}
129-
expandPaths={expandPaths}
130-
onInspectorExpanded={onInspectorExpanded}
131-
/>
108+
{typeof description === 'string' ||
109+
(description !== undefined && isValidElement(description)) ? (
110+
<BreadcrumbDescription
111+
description={description}
112+
frame={frame}
113+
allowShowSnippet={allowShowSnippet}
114+
showSnippet={showSnippet}
115+
onShowSnippet={onShowSnippet}
116+
/>
117+
) : (
118+
<BreadcrumbStructuredData
119+
description={description}
120+
expandPaths={expandPaths}
121+
onInspectorExpanded={onInspectorExpanded}
122+
/>
123+
)}
132124
<BreadcrumbComparisonButton frame={frame} replay={replay} />
133125
<BreadcrumbWebVital
134126
frame={frame}
@@ -183,5 +175,3 @@ const ReplayTimestamp = styled('div')`
183175
font-size: ${p => p.theme.fontSize.sm};
184176
align-self: flex-start;
185177
`;
186-
187-
export default BreadcrumbItem;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type {ReactNode} from 'react';
2+
import styled from '@emotion/styled';
3+
4+
import StructuredEventData from 'sentry/components/structuredEventData';
5+
import type {OnExpandCallback} from 'sentry/views/replays/detail/useVirtualizedInspector';
6+
7+
interface Props {
8+
description: ReactNode;
9+
onInspectorExpanded: OnExpandCallback;
10+
expandPaths?: string[];
11+
}
12+
13+
export function BreadcrumbStructuredData({
14+
description,
15+
expandPaths,
16+
onInspectorExpanded,
17+
}: Props) {
18+
return (
19+
<NoMarginWrapper>
20+
<StructuredEventData
21+
initialExpandedPaths={expandPaths ?? []}
22+
onToggleExpand={(expandedPaths, path) => {
23+
onInspectorExpanded(
24+
path,
25+
Object.fromEntries(expandedPaths.map(item => [item, true]))
26+
);
27+
}}
28+
data={description}
29+
withAnnotatedText
30+
/>
31+
</NoMarginWrapper>
32+
);
33+
}
34+
35+
const NoMarginWrapper = styled('div')`
36+
pre {
37+
margin: 0;
38+
}
39+
`;

0 commit comments

Comments
 (0)