Skip to content
Merged
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
107 changes: 39 additions & 68 deletions static/app/components/replays/breadcrumbs/breadcrumbDescription.tsx
Original file line number Diff line number Diff line change
@@ -1,95 +1,66 @@
import type {ReactNode} from 'react';
import {isValidElement} from 'react';
import {useCallback} from 'react';
import styled from '@emotion/styled';

import {Button} from 'sentry/components/core/button';
import {Flex} from 'sentry/components/core/layout';
import {Text} from 'sentry/components/core/text';
import {Tooltip} from 'sentry/components/core/tooltip';
import StructuredEventData from 'sentry/components/structuredEventData';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import {trackAnalytics} from 'sentry/utils/analytics';
import type {ReplayFrame, WebVitalFrame} from 'sentry/utils/replays/types';
import {isSpanFrame} from 'sentry/utils/replays/types';
import type {OnExpandCallback} from 'sentry/views/replays/detail/useVirtualizedInspector';
import useOrganization from 'sentry/utils/useOrganization';

interface Props {
allowShowSnippet: boolean;
description: ReactNode;
frame: ReplayFrame | WebVitalFrame;
onClickViewHtml: (e: React.MouseEvent<HTMLButtonElement>) => void;
onInspectorExpanded: OnExpandCallback;
onShowSnippet: () => void;
showSnippet: boolean;
className?: string;
expandPaths?: string[];
}

export function BreadcrumbDescription({
description,
allowShowSnippet,
showSnippet,
description,
frame,
expandPaths,
onInspectorExpanded,
onClickViewHtml,
onShowSnippet,
showSnippet,
}: Props) {
if (
typeof description === 'string' ||
(description !== undefined && isValidElement(description))
) {
return (
<DescriptionWrapper>
<Description title={description} showOnlyOnOverflow isHoverable>
{description}
</Description>

{allowShowSnippet &&
!showSnippet &&
frame.data?.nodeId !== undefined &&
!isSpanFrame(frame) && (
<ViewHtmlButton priority="link" onClick={onClickViewHtml} size="xs">
{t('View HTML')}
</ViewHtmlButton>
)}
</DescriptionWrapper>
);
}
const organization = useOrganization();
const handleViewHtml = useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => {
onShowSnippet();
e.preventDefault();
e.stopPropagation();
trackAnalytics('replay.view-html', {
organization,
breadcrumb_type: 'category' in frame ? frame.category : 'unknown',
});
},
[onShowSnippet, organization, frame]
);

return (
<Wrapper>
<StructuredEventData
initialExpandedPaths={expandPaths ?? []}
onToggleExpand={(expandedPaths, path) => {
onInspectorExpanded(
path,
Object.fromEntries(expandedPaths.map(item => [item, true]))
);
}}
data={description}
withAnnotatedText
/>
</Wrapper>
<Flex gap="lg" justify="between" align="start">
<Tooltip title={description} showOnlyOnOverflow isHoverable skipWrapper>
<Text ellipsis size="xs" tabular variant="muted">
{description}
</Text>
</Tooltip>

{allowShowSnippet &&
!showSnippet &&
frame.data?.nodeId !== undefined &&
!isSpanFrame(frame) && (
<NoWrapButton priority="link" onClick={handleViewHtml} size="xs">
{t('View HTML')}
</NoWrapButton>
)}
</Flex>
);
}

const Description = styled(Tooltip)`
${p => p.theme.overflowEllipsis};
font-size: 0.7rem;
font-variant-numeric: tabular-nums;
line-height: ${p => p.theme.text.lineHeightBody};
color: ${p => p.theme.subText};
`;

const DescriptionWrapper = styled('div')`
display: flex;
gap: ${space(1)};
justify-content: space-between;
`;

const ViewHtmlButton = styled(Button)`
const NoWrapButton = styled(Button)`
white-space: nowrap;
`;

const Wrapper = styled('div')`
pre {
margin: 0;
}
`;
48 changes: 19 additions & 29 deletions static/app/components/replays/breadcrumbs/breadcrumbItem.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {CSSProperties} from 'react';
import {useCallback, useEffect, useRef} from 'react';
import {isValidElement, useEffect, useRef} from 'react';
import {useTheme} from '@emotion/react';
import styled from '@emotion/styled';

Expand All @@ -8,17 +8,16 @@ import {BreadcrumbCodeSnippet} from 'sentry/components/replays/breadcrumbs/bread
import {BreadcrumbComparisonButton} from 'sentry/components/replays/breadcrumbs/breadcrumbComparisonButton';
import {BreadcrumbDescription} from 'sentry/components/replays/breadcrumbs/breadcrumbDescription';
import {BreadcrumbIssueLink} from 'sentry/components/replays/breadcrumbs/breadcrumbIssueLink';
import {BreadcrumbStructuredData} from 'sentry/components/replays/breadcrumbs/breadcrumbStructuredData';
import {BreadcrumbWebVital} from 'sentry/components/replays/breadcrumbs/breadcrumbWebVital';
import {Timeline} from 'sentry/components/timeline';
import {space} from 'sentry/styles/space';
import {trackAnalytics} from 'sentry/utils/analytics';
import type {Extraction} from 'sentry/utils/replays/extractDomNodes';
import getFrameDetails from 'sentry/utils/replays/getFrameDetails';
import useExtractDomNodes from 'sentry/utils/replays/hooks/useExtractDomNodes';
import {useReplayReader} from 'sentry/utils/replays/playback/providers/replayReaderProvider';
import type {ReplayFrame} from 'sentry/utils/replays/types';
import {isErrorFrame} from 'sentry/utils/replays/types';
import useOrganization from 'sentry/utils/useOrganization';
import TimestampButton from 'sentry/views/replays/detail/timestampButton';
import type {OnExpandCallback} from 'sentry/views/replays/detail/useVirtualizedInspector';

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

function BreadcrumbItem({
export default function BreadcrumbItem({
className,
frame,
expandPaths,
Expand All @@ -62,7 +61,6 @@ function BreadcrumbItem({
const {colorGraphicsToken, description, title, icon} = getFrameDetails(frame);
const colorHex = theme.tokens.graphics[colorGraphicsToken];
const replay = useReplayReader();
const organization = useOrganization();
const {data: extraction, isPending} = useExtractDomNodes({
replay,
frame,
Expand All @@ -82,19 +80,6 @@ function BreadcrumbItem({
}
}, [isPending, updateDimensions, showSnippet]);

const handleViewHtml = useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => {
onShowSnippet();
e.preventDefault();
e.stopPropagation();
trackAnalytics('replay.view-html', {
organization,
breadcrumb_type: 'category' in frame ? frame.category : 'unknown',
});
},
[onShowSnippet, organization, frame]
);

return (
<StyledTimelineItem
ref={ref}
Expand All @@ -120,15 +105,22 @@ function BreadcrumbItem({
onMouseLeave={() => onMouseLeave(frame)}
>
<ErrorBoundary mini>
<BreadcrumbDescription
description={description}
frame={frame}
allowShowSnippet={allowShowSnippet}
showSnippet={showSnippet}
onClickViewHtml={handleViewHtml}
expandPaths={expandPaths}
onInspectorExpanded={onInspectorExpanded}
/>
{typeof description === 'string' ||
(description !== undefined && isValidElement(description)) ? (
<BreadcrumbDescription
description={description}
frame={frame}
allowShowSnippet={allowShowSnippet}
showSnippet={showSnippet}
onShowSnippet={onShowSnippet}
/>
) : (
<BreadcrumbStructuredData
description={description}
expandPaths={expandPaths}
onInspectorExpanded={onInspectorExpanded}
/>
)}
<BreadcrumbComparisonButton frame={frame} replay={replay} />
<BreadcrumbWebVital
frame={frame}
Expand Down Expand Up @@ -183,5 +175,3 @@ const ReplayTimestamp = styled('div')`
font-size: ${p => p.theme.fontSize.sm};
align-self: flex-start;
`;

export default BreadcrumbItem;
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type {ReactNode} from 'react';
import styled from '@emotion/styled';

import StructuredEventData from 'sentry/components/structuredEventData';
import type {OnExpandCallback} from 'sentry/views/replays/detail/useVirtualizedInspector';

interface Props {
description: ReactNode;
onInspectorExpanded: OnExpandCallback;
expandPaths?: string[];
}

export function BreadcrumbStructuredData({
description,
expandPaths,
onInspectorExpanded,
}: Props) {
return (
<NoMarginWrapper>
<StructuredEventData
initialExpandedPaths={expandPaths ?? []}
onToggleExpand={(expandedPaths, path) => {
onInspectorExpanded(
path,
Object.fromEntries(expandedPaths.map(item => [item, true]))
);
}}
data={description}
withAnnotatedText
/>
</NoMarginWrapper>
);
}

const NoMarginWrapper = styled('div')`
pre {
margin: 0;
}
`;
Loading