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
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ function HTTPCrumbContent({
</BreadcrumbText>
{Object.keys(otherData).length > 0 ? (
<Timeline.Data>
<StructuredData value={otherData} meta={meta} {...structuredDataProps} />
<StructuredData value={otherData} meta={meta?.data} {...structuredDataProps} />
</Timeline.Data>
) : null}
</Fragment>
Expand All @@ -163,7 +163,7 @@ function SQLCrumbContent({
return (
<Fragment>
<Timeline.Data>
<LightenTextColor className="language-sql">
<SQLText className="language-sql">
{tokens.map((line, i) => (
<div key={i}>
{line.map((token, j) => (
Expand All @@ -173,7 +173,7 @@ function SQLCrumbContent({
))}
</div>
))}
</LightenTextColor>
</SQLText>
</Timeline.Data>
{children}
</Fragment>
Expand Down Expand Up @@ -201,7 +201,7 @@ function ExceptionCrumbContent({
{children}
{Object.keys(otherData).length > 0 ? (
<Timeline.Data>
<StructuredData value={otherData} meta={meta} {...structuredDataProps} />
<StructuredData value={otherData} meta={meta?.data} {...structuredDataProps} />
</Timeline.Data>
) : null}
</Fragment>
Expand All @@ -215,12 +215,12 @@ const Link = styled('a')`
word-break: break-all;
`;

const LightenTextColor = styled('pre')`
margin: 0;
const SQLText = styled('pre')`
&.language-sql {
color: ${p => p.theme.subText};
margin: 0;
padding: ${space(0.25)} 0;
font-size: ${p => p.theme.fontSizeSmall};
white-space: pre-wrap;
}
`;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useCallback, useMemo, useRef} from 'react';
import {useCallback, useMemo, useRef, useState} from 'react';
import styled from '@emotion/styled';

import {Button} from 'sentry/components/button';
Expand Down Expand Up @@ -52,6 +52,7 @@ export default function BreadcrumbsDataSection({
project,
}: BreadcrumbsDataSectionProps) {
const viewAllButtonRef = useRef<HTMLButtonElement>(null);
const [container, setContainer] = useState<HTMLDivElement | null>(null);
const openForm = useFeedbackForm();
const {closeDrawer, isDrawerOpen, openDrawer} = useDrawer();
const organization = useOrganization();
Expand Down Expand Up @@ -183,13 +184,16 @@ export default function BreadcrumbsDataSection({
actions={actions}
>
<ErrorBoundary mini message={t('There was an error loading the event breadcrumbs')}>
<BreadcrumbsTimeline
breadcrumbs={summaryCrumbs}
startTimeString={startTimeString}
// We want the timeline to appear connected to the 'View All' button
showLastLine={hasViewAll}
fullyExpanded={false}
/>
<div ref={setContainer}>
<BreadcrumbsTimeline
breadcrumbs={summaryCrumbs}
startTimeString={startTimeString}
// We want the timeline to appear connected to the 'View All' button
showLastLine={hasViewAll}
fullyExpanded={false}
containerElement={container}
/>
</div>
{hasViewAll && (
<ViewAllContainer>
<VerticalEllipsis />
Expand Down
57 changes: 44 additions & 13 deletions static/app/components/events/breadcrumbs/breadcrumbsDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Fragment, useCallback, useMemo, useState} from 'react';
import {useCallback, useMemo, useState} from 'react';
import {useTheme} from '@emotion/react';
import styled from '@emotion/styled';

Expand Down Expand Up @@ -33,6 +33,7 @@ import {trackAnalytics} from 'sentry/utils/analytics';
import {getShortEventId} from 'sentry/utils/events';
import {useLocalStorageState} from 'sentry/utils/useLocalStorageState';
import useOrganization from 'sentry/utils/useOrganization';
import {MIN_NAV_HEIGHT} from 'sentry/views/issueDetails/streamline/eventNavigation';

export const enum BreadcrumbControlOptions {
SEARCH = 'search',
Expand Down Expand Up @@ -72,6 +73,7 @@ export function BreadcrumbsDrawer({
}: BreadcrumbsDrawerProps) {
const organization = useOrganization();
const theme = useTheme();
const [container, setContainer] = useState<HTMLElement | null>(null);

const [search, setSearch] = useState('');
const [filters, setFilters] = useState<string[]>([]);
Expand Down Expand Up @@ -205,8 +207,8 @@ export function BreadcrumbsDrawer({
);

return (
<Fragment>
<DrawerHeader>
<BreadcrumbDrawerContainer>
<BreadcrumbDrawerHeader>
<NavigationCrumbs
crumbs={[
{
Expand All @@ -221,12 +223,12 @@ export function BreadcrumbsDrawer({
{label: t('Breadcrumbs')},
]}
/>
</DrawerHeader>
<DrawerBody>
<HeaderGrid>
<Header>{t('Breadcrumbs')}</Header>
{actions}
</HeaderGrid>
</BreadcrumbDrawerHeader>
<BreadcrumbNavigator>
<Header>{t('Breadcrumbs')}</Header>
{actions}
</BreadcrumbNavigator>
<BreadcrumbDrawerBody ref={setContainer}>
<TimelineContainer>
{displayCrumbs.length === 0 ? (
<EmptyMessage>
Expand All @@ -249,11 +251,12 @@ export function BreadcrumbsDrawer({
<BreadcrumbsTimeline
breadcrumbs={displayCrumbs}
startTimeString={startTimeString}
containerElement={container}
/>
)}
</TimelineContainer>
</DrawerBody>
</Fragment>
</BreadcrumbDrawerBody>
</BreadcrumbDrawerContainer>
);
}

Expand All @@ -262,12 +265,40 @@ const VisibleFocusButton = styled(Button)`
0 0 1px;
`;

const HeaderGrid = styled('div')`
const BreadcrumbDrawerContainer = styled('div')`
height: 100%;
display: grid;
grid-template-rows: auto auto 1fr;
`;

const BreadcrumbDrawerHeader = styled(DrawerHeader)`
position: unset;
max-height: ${MIN_NAV_HEIGHT}px;
box-shadow: none;
border-bottom: 1px solid ${p => p.theme.border};
`;

const BreadcrumbNavigator = styled('div')`
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
column-gap: ${space(1)};
margin: ${space(1)} 0 ${space(2)};
padding: ${space(0.75)} 24px;
background: ${p => p.theme.background};
z-index: 1;
min-height: ${MIN_NAV_HEIGHT}px;
box-shadow: ${p => p.theme.translucentBorder} 0 1px;
`;

const BreadcrumbDrawerBody = styled(DrawerBody)`
overflow: auto;
overscroll-behavior: contain;
/* Move the scrollbar to the left edge */
scroll-margin: 0 ${space(2)};
direction: rtl;
* {
direction: ltr;
}
`;

const Header = styled('h3')`
Expand Down
36 changes: 30 additions & 6 deletions static/app/components/events/breadcrumbs/breadcrumbsTimeline.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {useRef} from 'react';
import styled from '@emotion/styled';
import {useVirtualizer} from '@tanstack/react-virtual';
import moment from 'moment-timezone';
Expand All @@ -17,6 +16,22 @@ import {shouldUse24Hours} from 'sentry/utils/dates';

interface BreadcrumbsTimelineProps {
breadcrumbs: EnhancedCrumb[];
/**
* Required reference to parent container for virtualization. It's recommended to use state instead
* of useRef since this component will not update when the ref changes, causing it to render empty initially.
* To enable virtualization, set a fixed height on the `containerElement` node.
*
* Example:
* ```
* const [container, setContainer] = useState<HTMLElement | null>(null);
* return (
* <div ref={setContainer}>
* <BreadcrumbsTimeline containerElement={container} />
* </div>
* )
* ```
*/
containerElement: HTMLElement | null;
/**
* If true, expands the contents of the breadcrumbs' data payload
*/
Expand All @@ -34,14 +49,14 @@ interface BreadcrumbsTimelineProps {

export default function BreadcrumbsTimeline({
breadcrumbs,
containerElement,
startTimeString,
fullyExpanded = true,
showLastLine = false,
}: BreadcrumbsTimelineProps) {
const containerRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: breadcrumbs.length,
getScrollElement: () => containerRef.current,
getScrollElement: () => containerElement,
estimateSize: () => 35,
// Must match rendered item margins.
gap: 8,
Expand Down Expand Up @@ -114,17 +129,26 @@ export default function BreadcrumbsTimeline({

return (
<div
ref={containerRef}
style={{
height: virtualizer.getTotalSize(),
contain: 'layout size',
position: 'relative',
}}
>
<Timeline.Container>{items}</Timeline.Container>
<VirtualOffset offset={virtualItems?.[0]?.start ?? 0}>
<Timeline.Container>{items}</Timeline.Container>
</VirtualOffset>
</div>
);
}

const VirtualOffset = styled('div')<{offset: number}>`
position: absolute;
top: 0;
left: 0;
width: 100%;
transform: translateY(${p => p.offset}px);
`;

const Header = styled('div')`
display: grid;
grid-template-columns: 1fr auto;
Expand Down
4 changes: 4 additions & 0 deletions static/app/components/events/eventProcessingErrors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ export function EventProcessingErrors({event, project, isShare}: Props) {
}))
);

if (!errors.length) {
return null;
}

return (
<InterimSection
title={t('Event Processing Errors')}
Expand Down
20 changes: 15 additions & 5 deletions static/app/components/globalDrawer/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const DrawerPanel = forwardRef(function _DrawerPanel(
) {
return (
<DrawerContainer>
<SlideOverPanel
<DrawerSlidePanel
ariaLabel={ariaLabel}
slidePosition="right"
collapsed={false}
Expand All @@ -52,13 +52,14 @@ export const DrawerPanel = forwardRef(function _DrawerPanel(
<DrawerContentContext.Provider value={{onClose, ariaLabel}}>
{children}
</DrawerContentContext.Provider>
</SlideOverPanel>
</DrawerSlidePanel>
</DrawerContainer>
);
});

interface DrawerHeaderProps {
children?: React.ReactNode;
className?: string;
/**
* If true, hides the spacer bar separating close button from custom header content
*/
Expand All @@ -70,13 +71,18 @@ interface DrawerHeaderProps {
}

export const DrawerHeader = forwardRef(function _DrawerHeader(
{children = null, hideBar = false, hideCloseButton = false}: DrawerHeaderProps,
{
className,
children = null,
hideBar = false,
hideCloseButton = false,
}: DrawerHeaderProps,
ref: React.ForwardedRef<HTMLHeadingElement>
) {
const {onClose} = useDrawerContentContext();

return (
<Header ref={ref}>
<Header ref={ref} className={className}>
{!hideCloseButton && (
<Fragment>
<CloseButton
Expand Down Expand Up @@ -117,7 +123,7 @@ const Header = styled('header')`
justify-content: flex-start;
display: flex;
padding: ${space(1.5)};
border-bottom: 1px solid ${p => p.theme.border};
box-shadow: ${p => p.theme.border} 0 1px;
padding-left: 24px;
`;

Expand All @@ -133,6 +139,10 @@ const DrawerContainer = styled('div')`
pointer-events: none;
`;

const DrawerSlidePanel = styled(SlideOverPanel)`
box-shadow: 0 0 0 1px ${p => p.theme.translucentBorder};
`;

export const DrawerComponents = {
DrawerBody,
DrawerHeader,
Expand Down
3 changes: 3 additions & 0 deletions static/app/components/slideOverPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type SlideOverPanelProps = {
children: React.ReactNode;
collapsed: boolean;
ariaLabel?: string;
className?: string;
onOpen?: () => void;
slidePosition?: 'right' | 'bottom';
transitionProps?: AnimationProps['transition'];
Expand All @@ -36,6 +37,7 @@ function SlideOverPanel(
ariaLabel,
collapsed,
children,
className,
onOpen,
slidePosition,
transitionProps = {},
Expand Down Expand Up @@ -70,6 +72,7 @@ function SlideOverPanel(
role="complementary"
aria-hidden={collapsed}
aria-label={ariaLabel ?? 'slide out drawer'}
className={className}
>
{children}
</_SlideOverPanel>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ const SearchFilter = styled(EventSearch)`
`;

const GroupContent = styled('div')<{navHeight?: number}>`
border: 1px solid ${p => p.theme.border};
border: 1px solid ${p => p.theme.translucentBorder};
background: ${p => p.theme.background};
border-radius: ${p => p.theme.borderRadius};
position: relative;
Expand Down
Loading