diff --git a/static/app/components/events/breadcrumbs/breadcrumbItemContent.tsx b/static/app/components/events/breadcrumbs/breadcrumbItemContent.tsx index 375e09333a7ba8..a30cb52650b6bb 100644 --- a/static/app/components/events/breadcrumbs/breadcrumbItemContent.tsx +++ b/static/app/components/events/breadcrumbs/breadcrumbItemContent.tsx @@ -145,7 +145,7 @@ function HTTPCrumbContent({ {Object.keys(otherData).length > 0 ? ( - + ) : null} @@ -163,7 +163,7 @@ function SQLCrumbContent({ return ( - + {tokens.map((line, i) => (
{line.map((token, j) => ( @@ -173,7 +173,7 @@ function SQLCrumbContent({ ))}
))} -
+
{children}
@@ -201,7 +201,7 @@ function ExceptionCrumbContent({ {children} {Object.keys(otherData).length > 0 ? ( - + ) : null} @@ -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; } `; diff --git a/static/app/components/events/breadcrumbs/breadcrumbsDataSection.tsx b/static/app/components/events/breadcrumbs/breadcrumbsDataSection.tsx index 527479b1b4f629..ab3cbf1ac3c385 100644 --- a/static/app/components/events/breadcrumbs/breadcrumbsDataSection.tsx +++ b/static/app/components/events/breadcrumbs/breadcrumbsDataSection.tsx @@ -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'; @@ -52,6 +52,7 @@ export default function BreadcrumbsDataSection({ project, }: BreadcrumbsDataSectionProps) { const viewAllButtonRef = useRef(null); + const [container, setContainer] = useState(null); const openForm = useFeedbackForm(); const {closeDrawer, isDrawerOpen, openDrawer} = useDrawer(); const organization = useOrganization(); @@ -183,13 +184,16 @@ export default function BreadcrumbsDataSection({ actions={actions} > - +
+ +
{hasViewAll && ( diff --git a/static/app/components/events/breadcrumbs/breadcrumbsDrawer.tsx b/static/app/components/events/breadcrumbs/breadcrumbsDrawer.tsx index 3d71b22c605125..0b33dd5b68da83 100644 --- a/static/app/components/events/breadcrumbs/breadcrumbsDrawer.tsx +++ b/static/app/components/events/breadcrumbs/breadcrumbsDrawer.tsx @@ -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'; @@ -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', @@ -72,6 +73,7 @@ export function BreadcrumbsDrawer({ }: BreadcrumbsDrawerProps) { const organization = useOrganization(); const theme = useTheme(); + const [container, setContainer] = useState(null); const [search, setSearch] = useState(''); const [filters, setFilters] = useState([]); @@ -205,8 +207,8 @@ export function BreadcrumbsDrawer({ ); return ( - - + + - - - -
{t('Breadcrumbs')}
- {actions} -
+ + +
{t('Breadcrumbs')}
+ {actions} +
+ {displayCrumbs.length === 0 ? ( @@ -249,11 +251,12 @@ export function BreadcrumbsDrawer({ )} -
-
+ + ); } @@ -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')` diff --git a/static/app/components/events/breadcrumbs/breadcrumbsTimeline.tsx b/static/app/components/events/breadcrumbs/breadcrumbsTimeline.tsx index b8015564ee6a8a..90acfde5dbfb67 100644 --- a/static/app/components/events/breadcrumbs/breadcrumbsTimeline.tsx +++ b/static/app/components/events/breadcrumbs/breadcrumbsTimeline.tsx @@ -1,4 +1,3 @@ -import {useRef} from 'react'; import styled from '@emotion/styled'; import {useVirtualizer} from '@tanstack/react-virtual'; import moment from 'moment-timezone'; @@ -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(null); + * return ( + *
+ * + *
+ * ) + * ``` + */ + containerElement: HTMLElement | null; /** * If true, expands the contents of the breadcrumbs' data payload */ @@ -34,14 +49,14 @@ interface BreadcrumbsTimelineProps { export default function BreadcrumbsTimeline({ breadcrumbs, + containerElement, startTimeString, fullyExpanded = true, showLastLine = false, }: BreadcrumbsTimelineProps) { - const containerRef = useRef(null); const virtualizer = useVirtualizer({ count: breadcrumbs.length, - getScrollElement: () => containerRef.current, + getScrollElement: () => containerElement, estimateSize: () => 35, // Must match rendered item margins. gap: 8, @@ -114,17 +129,26 @@ export default function BreadcrumbsTimeline({ return (
- {items} + + {items} +
); } +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; diff --git a/static/app/components/events/eventProcessingErrors.tsx b/static/app/components/events/eventProcessingErrors.tsx index 6eb246f7883c0b..694d76bd6a7770 100644 --- a/static/app/components/events/eventProcessingErrors.tsx +++ b/static/app/components/events/eventProcessingErrors.tsx @@ -118,6 +118,10 @@ export function EventProcessingErrors({event, project, isShare}: Props) { })) ); + if (!errors.length) { + return null; + } + return ( - {children} - + ); }); interface DrawerHeaderProps { children?: React.ReactNode; + className?: string; /** * If true, hides the spacer bar separating close button from custom header content */ @@ -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 ) { const {onClose} = useDrawerContentContext(); return ( -
+
{!hideCloseButton && ( p.theme.border}; + box-shadow: ${p => p.theme.border} 0 1px; padding-left: 24px; `; @@ -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, diff --git a/static/app/components/slideOverPanel.tsx b/static/app/components/slideOverPanel.tsx index 4ebf71009e6da5..c848b99f5b51ba 100644 --- a/static/app/components/slideOverPanel.tsx +++ b/static/app/components/slideOverPanel.tsx @@ -24,6 +24,7 @@ type SlideOverPanelProps = { children: React.ReactNode; collapsed: boolean; ariaLabel?: string; + className?: string; onOpen?: () => void; slidePosition?: 'right' | 'bottom'; transitionProps?: AnimationProps['transition']; @@ -36,6 +37,7 @@ function SlideOverPanel( ariaLabel, collapsed, children, + className, onOpen, slidePosition, transitionProps = {}, @@ -70,6 +72,7 @@ function SlideOverPanel( role="complementary" aria-hidden={collapsed} aria-label={ariaLabel ?? 'slide out drawer'} + className={className} > {children} diff --git a/static/app/views/issueDetails/streamline/eventDetails.tsx b/static/app/views/issueDetails/streamline/eventDetails.tsx index 9080283b02551a..f51444e650fbea 100644 --- a/static/app/views/issueDetails/streamline/eventDetails.tsx +++ b/static/app/views/issueDetails/streamline/eventDetails.tsx @@ -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; diff --git a/static/app/views/issueDetails/streamline/eventNavigation.tsx b/static/app/views/issueDetails/streamline/eventNavigation.tsx index 55387e1bd47ae1..42821887855d90 100644 --- a/static/app/views/issueDetails/streamline/eventNavigation.tsx +++ b/static/app/views/issueDetails/streamline/eventNavigation.tsx @@ -35,6 +35,8 @@ import {useParams} from 'sentry/utils/useParams'; import {FoldSectionKey} from 'sentry/views/issueDetails/streamline/foldSection'; import {useDefaultIssueEvent} from 'sentry/views/issueDetails/utils'; +export const MIN_NAV_HEIGHT = 44; + type EventNavigationProps = { event: Event; group: Group; @@ -258,7 +260,6 @@ export const EventNavigation = forwardRef( - @@ -354,7 +355,6 @@ export const EventNavigation = forwardRef( - ); } @@ -365,6 +365,8 @@ const EventNavigationWrapper = styled('div')` justify-content: space-between; font-size: ${p => p.theme.fontSizeSmall}; padding: ${space(1)} ${space(1.5)}; + min-height: ${MIN_NAV_HEIGHT}px; + border-bottom: 1px solid ${p => p.theme.border}; `; const NavigationWrapper = styled('div')` @@ -390,10 +392,11 @@ const EventInfoJumpToWrapper = styled('div')` align-items: center; padding: ${space(1)} ${space(2)}; flex-wrap: wrap; - + min-height: ${MIN_NAV_HEIGHT}px; @media (min-width: ${p => p.theme.breakpoints.small}) { flex-wrap: nowrap; } + box-shadow: ${p => p.theme.translucentBorder} 0 1px; `; const EventInfo = styled('div')` @@ -418,11 +421,6 @@ const JumpTo = styled('div')` } `; -const NavigationDivider = styled('hr')` - border-color: ${p => p.theme.border}; - margin: 0; -`; - const EventIdInfo = styled('span')` display: flex; align-items: center; diff --git a/static/app/views/issueDetails/streamline/foldSection.tsx b/static/app/views/issueDetails/streamline/foldSection.tsx index ec7378468fd532..88f2d63413b6f3 100644 --- a/static/app/views/issueDetails/streamline/foldSection.tsx +++ b/static/app/views/issueDetails/streamline/foldSection.tsx @@ -170,7 +170,6 @@ const Summary = styled('summary')<{preventCollapse: boolean}>` border-radius: ${p => p.theme.borderRadius}; cursor: ${p => (p.preventCollapse ? 'initial' : 'pointer')}; position: relative; - overflow: hidden; &::marker, &::-webkit-details-marker { display: none; diff --git a/static/app/views/issueDetails/streamline/interimSection.tsx b/static/app/views/issueDetails/streamline/interimSection.tsx index c7f0e094f46b11..37873159c4fa5d 100644 --- a/static/app/views/issueDetails/streamline/interimSection.tsx +++ b/static/app/views/issueDetails/streamline/interimSection.tsx @@ -45,7 +45,7 @@ export const InterimSection = forwardRef( ); export const SectionDivider = styled('hr')` - border-color: ${p => p.theme.border}; + border-color: ${p => p.theme.translucentBorder}; margin: ${space(1)} 0; &:last-child { display: none;