1- import { Fragment , useEffect , useState } from 'react' ;
1+ import { Fragment , useEffect , useMemo , useState } from 'react' ;
22import styled from '@emotion/styled' ;
33
4+ import { Button } from 'sentry/components/button' ;
5+ import ButtonBar from 'sentry/components/buttonBar' ;
46import { CommitRow } from 'sentry/components/commitRow' ;
57import { Flex } from 'sentry/components/container/flex' ;
68import ErrorBoundary from 'sentry/components/errorBoundary' ;
@@ -15,7 +17,14 @@ import Pill from 'sentry/components/pill';
1517import Pills from 'sentry/components/pills' ;
1618import QuestionTooltip from 'sentry/components/questionTooltip' ;
1719import TextOverflow from 'sentry/components/textOverflow' ;
18- import { IconClock , IconInfo , IconLock , IconPlay , IconTimer } from 'sentry/icons' ;
20+ import {
21+ IconChevron ,
22+ IconClock ,
23+ IconInfo ,
24+ IconLock ,
25+ IconPlay ,
26+ IconTimer ,
27+ } from 'sentry/icons' ;
1928import { t , tn } from 'sentry/locale' ;
2029import { space } from 'sentry/styles/space' ;
2130import type { Event , Thread } from 'sentry/types/event' ;
@@ -101,15 +110,22 @@ const useActiveThreadState = (
101110} ;
102111
103112export function Threads ( { data, event, projectSlug, groupingCurrentLevel, group} : Props ) {
104- const threads = data . values ?? [ ] ;
113+ // Sort threads by crashed first
114+ const threads = useMemo (
115+ ( ) => ( data . values ?? [ ] ) . toSorted ( ( a , b ) => Number ( b . crashed ) - Number ( a . crashed ) ) ,
116+ [ data . values ]
117+ ) ;
105118 const hasStreamlinedUI = useHasStreamlinedUI ( ) ;
106119 const [ activeThread , setActiveThread ] = useActiveThreadState ( event , threads ) ;
107120
108121 const stackTraceNotFound = ! threads . length ;
109122
110123 const hasMoreThanOneThread = threads . length > 1 ;
111124
112- const exception = getThreadException ( event , activeThread ) ;
125+ const exception = useMemo (
126+ ( ) => getThreadException ( event , activeThread ) ,
127+ [ event , activeThread ]
128+ ) ;
113129
114130 const entryIndex = exception
115131 ? event . entries . findIndex ( entry => entry . type === EntryType . EXCEPTION )
@@ -226,6 +242,18 @@ export function Threads({data, event, projectSlug, groupingCurrentLevel, group}:
226242 const { id : activeThreadId , name : activeThreadName } = activeThread ?? { } ;
227243 const hideThreadTags = activeThreadId === undefined || ! activeThreadName ;
228244
245+ function handleChangeThread ( direction : 'previous' | 'next' ) {
246+ const currentIndex = threads . findIndex ( thread => thread . id === activeThreadId ) ;
247+ let nextIndex = direction === 'previous' ? currentIndex - 1 : currentIndex + 1 ;
248+ if ( nextIndex < 0 ) {
249+ nextIndex = threads . length - 1 ;
250+ } else if ( nextIndex >= threads . length ) {
251+ nextIndex = 0 ;
252+ }
253+
254+ setActiveThread ( threads [ nextIndex ] ) ;
255+ }
256+
229257 const threadComponent = (
230258 < Fragment >
231259 { hasMoreThanOneThread && (
@@ -235,6 +263,28 @@ export function Threads({data, event, projectSlug, groupingCurrentLevel, group}:
235263 < ThreadHeading > { t ( 'Threads' ) } </ ThreadHeading >
236264 { activeThread && (
237265 < Wrapper >
266+ < ButtonBar merged >
267+ < Button
268+ title = { t ( 'Previous Thread' ) }
269+ tooltipProps = { { delay : 1000 } }
270+ icon = { < IconChevron direction = "left" /> }
271+ aria-label = { t ( 'Previous Thread' ) }
272+ size = "xs"
273+ onClick = { ( ) => {
274+ handleChangeThread ( 'previous' ) ;
275+ } }
276+ />
277+ < Button
278+ title = { t ( 'Next Thread' ) }
279+ tooltipProps = { { delay : 1000 } }
280+ icon = { < IconChevron direction = "right" /> }
281+ aria-label = { t ( 'Next Thread' ) }
282+ size = "xs"
283+ onClick = { ( ) => {
284+ handleChangeThread ( 'next' ) ;
285+ } }
286+ />
287+ </ ButtonBar >
238288 < ThreadSelector
239289 threads = { threads }
240290 activeThread = { activeThread }
@@ -398,6 +448,8 @@ const LockReason = styled(TextOverflow)`
398448` ;
399449
400450const Wrapper = styled ( 'div' ) `
451+ display: flex;
452+ gap: ${ space ( 1 ) } ;
401453 align-items: center;
402454 flex-wrap: wrap;
403455 flex-grow: 1;
0 commit comments