Skip to content

Commit 395eed6

Browse files
ref(stack-trace-interfaces): Add Native Stack Trace V2 - Part 1 - [INGEST-508] (#29434)
1 parent 10afe09 commit 395eed6

File tree

7 files changed

+691
-30
lines changed

7 files changed

+691
-30
lines changed

static/app/components/events/eventDataSection.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class EventDataSection extends React.Component<Props> {
8181
{titleNode}
8282
</Permalink>
8383
) : (
84-
<div>{titleNode}</div>
84+
titleNode
8585
)}
8686
</Title>
8787
{type === 'extra' && (
@@ -123,6 +123,7 @@ const StyledIconAnchor = styled(IconAnchor)`
123123
`;
124124

125125
const Permalink = styled('a')`
126+
width: 100%;
126127
:hover ${StyledIconAnchor} {
127128
display: block;
128129
color: ${p => p.theme.gray300};

static/app/components/events/eventEntry.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Spans from 'app/components/events/interfaces/spans';
1212
import Stacktrace from 'app/components/events/interfaces/stacktrace';
1313
import Template from 'app/components/events/interfaces/template';
1414
import Threads from 'app/components/events/interfaces/threads';
15+
import ThreadsV2 from 'app/components/events/interfaces/threadsV2';
1516
import {Group, Organization, Project, SharedViewOrganization} from 'app/types';
1617
import {Entry, EntryType, Event, EventTransaction} from 'app/types/event';
1718

@@ -36,6 +37,10 @@ function EventEntry({
3637
!!organization.features?.includes('grouping-stacktrace-ui') &&
3738
!!(event.metadata.current_tree_label || event.metadata.finest_tree_label);
3839

40+
const hasNativeStackTraceV2 = !!organization.features?.includes(
41+
'native-stack-trace-v2'
42+
);
43+
3944
const groupingCurrentLevel = group?.metadata?.current_level;
4045

4146
switch (entry.type) {
@@ -102,7 +107,16 @@ function EventEntry({
102107
}
103108
case EntryType.THREADS: {
104109
const {data, type} = entry;
105-
return (
110+
return hasNativeStackTraceV2 ? (
111+
<ThreadsV2
112+
type={type}
113+
event={event}
114+
data={data}
115+
projectId={projectSlug}
116+
groupingCurrentLevel={groupingCurrentLevel}
117+
hasHierarchicalGrouping={hasHierarchicalGrouping}
118+
/>
119+
) : (
106120
<Threads
107121
type={type}
108122
event={event}

static/app/components/events/interfaces/threads/threadSelector/index.tsx

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {ClassNames} from '@emotion/react';
12
import styled from '@emotion/styled';
23
import partition from 'lodash/partition';
34

@@ -18,13 +19,21 @@ type Props = {
1819
threads: Array<Thread>;
1920
activeThread: Thread;
2021
event: Event;
22+
fullWidth?: boolean;
2123
exception?: Required<ExceptionType>;
2224
onChange?: (thread: Thread) => void;
2325
};
2426

2527
const DROPDOWN_MAX_HEIGHT = 400;
2628

27-
const ThreadSelector = ({threads, event, exception, activeThread, onChange}: Props) => {
29+
const ThreadSelector = ({
30+
threads,
31+
event,
32+
exception,
33+
activeThread,
34+
onChange,
35+
fullWidth = false,
36+
}: Props) => {
2837
const getDropDownItem = (thread: Thread) => {
2938
const {label, filename, crashedInfo} = filterThreadInfo(event, thread, exception);
3039
const threadInfo = {label, filename};
@@ -56,35 +65,46 @@ const ThreadSelector = ({threads, event, exception, activeThread, onChange}: Pro
5665
};
5766

5867
return (
59-
<StyledDropdownAutoComplete
60-
items={getItems()}
61-
onSelect={item => {
62-
handleChange(item.thread);
63-
}}
64-
maxHeight={DROPDOWN_MAX_HEIGHT}
65-
searchPlaceholder={t('Filter Threads')}
66-
emptyMessage={t('You have no threads')}
67-
noResultsMessage={t('No threads found')}
68-
menuHeader={<Header />}
69-
closeOnSelect
70-
emptyHidesInput
71-
>
72-
{({isOpen, selectedItem}) => (
73-
<StyledDropdownButton size="small" isOpen={isOpen} align="left">
74-
{selectedItem ? (
75-
<SelectedOption
76-
id={selectedItem.thread.id}
77-
details={selectedItem.threadInfo}
78-
/>
79-
) : (
80-
<SelectedOption
81-
id={activeThread.id}
82-
details={filterThreadInfo(event, activeThread, exception)}
83-
/>
68+
<ClassNames>
69+
{({css}) => (
70+
<StyledDropdownAutoComplete
71+
items={getItems()}
72+
onSelect={item => {
73+
handleChange(item.thread);
74+
}}
75+
maxHeight={DROPDOWN_MAX_HEIGHT}
76+
searchPlaceholder={t('Filter Threads')}
77+
emptyMessage={t('You have no threads')}
78+
noResultsMessage={t('No threads found')}
79+
menuHeader={<Header />}
80+
rootClassName={
81+
fullWidth
82+
? css`
83+
width: 100%;
84+
`
85+
: undefined
86+
}
87+
closeOnSelect
88+
emptyHidesInput
89+
>
90+
{({isOpen, selectedItem}) => (
91+
<StyledDropdownButton isOpen={isOpen} size="small" align="left">
92+
{selectedItem ? (
93+
<SelectedOption
94+
id={selectedItem.thread.id}
95+
details={selectedItem.threadInfo}
96+
/>
97+
) : (
98+
<SelectedOption
99+
id={activeThread.id}
100+
details={filterThreadInfo(event, activeThread, exception)}
101+
/>
102+
)}
103+
</StyledDropdownButton>
84104
)}
85-
</StyledDropdownButton>
105+
</StyledDropdownAutoComplete>
86106
)}
87-
</StyledDropdownAutoComplete>
107+
</ClassNames>
88108
);
89109
};
90110

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
import {Fragment, useState} from 'react';
2+
import styled from '@emotion/styled';
3+
import isNil from 'lodash/isNil';
4+
5+
import {isStacktraceNewestFirst} from 'app/components/events/interfaces/stacktrace';
6+
import Pill from 'app/components/pill';
7+
import Pills from 'app/components/pills';
8+
import {t} from 'app/locale';
9+
import {PlatformType, Project} from 'app/types';
10+
import {Event} from 'app/types/event';
11+
import {Thread} from 'app/types/events';
12+
import {STACK_TYPE, STACK_VIEW} from 'app/types/stacktrace';
13+
import {defined} from 'app/utils';
14+
15+
import TraceEventDataSection from '../../traceEventDataSection';
16+
import {DisplayOption} from '../../traceEventDataSection/displayOptions';
17+
import Exception from '../crashContent/exception';
18+
import StackTrace from '../crashContent/stackTrace';
19+
import NoStackTraceMessage from '../noStackTraceMessage';
20+
import ThreadSelector from '../threads/threadSelector';
21+
import findBestThread from '../threads/threadSelector/findBestThread';
22+
import getThreadException from '../threads/threadSelector/getThreadException';
23+
import getThreadStacktrace from '../threads/threadSelector/getThreadStacktrace';
24+
25+
type ExceptionProps = React.ComponentProps<typeof Exception>;
26+
27+
type Props = Pick<ExceptionProps, 'groupingCurrentLevel' | 'hasHierarchicalGrouping'> & {
28+
event: Event;
29+
projectId: Project['id'];
30+
type: string;
31+
data: {
32+
values?: Array<Thread>;
33+
};
34+
};
35+
36+
type State = {
37+
stackType: STACK_TYPE;
38+
activeThread?: Thread;
39+
};
40+
41+
function getIntendedStackView(thread: Thread, event: Event) {
42+
const exception = getThreadException(event, thread);
43+
if (exception) {
44+
return !!exception.values.find(value => !!value.stacktrace?.hasSystemFrames)
45+
? STACK_VIEW.APP
46+
: STACK_VIEW.FULL;
47+
}
48+
49+
const stacktrace = getThreadStacktrace(false, thread);
50+
51+
return stacktrace?.hasSystemFrames ? STACK_VIEW.APP : STACK_VIEW.FULL;
52+
}
53+
54+
function Threads({
55+
data,
56+
event,
57+
projectId,
58+
type,
59+
hasHierarchicalGrouping,
60+
groupingCurrentLevel,
61+
}: Props) {
62+
const [state, setState] = useState<State>(() => {
63+
const thread = defined(data.values) ? findBestThread(data.values) : undefined;
64+
return {
65+
activeThread: thread,
66+
stackType: STACK_TYPE.ORIGINAL,
67+
};
68+
});
69+
70+
if (!data.values) {
71+
return null;
72+
}
73+
74+
const threads = data.values;
75+
const {stackType, activeThread} = state;
76+
77+
const platform = (event.platform ?? 'other') as PlatformType;
78+
const hasMoreThanOneThread = threads.length > 1;
79+
const exception = getThreadException(event, activeThread);
80+
const stackView = activeThread ? getIntendedStackView(activeThread, event) : undefined;
81+
82+
function renderPills() {
83+
const {id, name, current, crashed} = activeThread ?? {};
84+
85+
if (isNil(id) || !name) {
86+
return null;
87+
}
88+
89+
return (
90+
<Pills>
91+
{!isNil(id) && <Pill name={t('id')} value={String(id)} />}
92+
{!!name?.trim() && <Pill name={t('name')} value={name} />}
93+
{current !== undefined && <Pill name={t('was active')} value={current} />}
94+
{crashed !== undefined && (
95+
<Pill name={t('errored')} className={crashed ? 'false' : 'true'}>
96+
{crashed ? t('yes') : t('no')}
97+
</Pill>
98+
)}
99+
</Pills>
100+
);
101+
}
102+
103+
function renderContent({
104+
recentFirst,
105+
raw,
106+
activeDisplayOptions,
107+
}: Parameters<React.ComponentProps<typeof TraceEventDataSection>['children']>[0]) {
108+
if (exception) {
109+
return (
110+
<Exception
111+
stackType={stackType}
112+
stackView={
113+
raw
114+
? STACK_VIEW.RAW
115+
: activeDisplayOptions.includes(DisplayOption.FULL_STACK_TRACE)
116+
? STACK_VIEW.FULL
117+
: STACK_VIEW.APP
118+
}
119+
projectId={projectId}
120+
newestFirst={recentFirst}
121+
event={event}
122+
platform={platform}
123+
values={exception.values}
124+
groupingCurrentLevel={groupingCurrentLevel}
125+
hasHierarchicalGrouping={hasHierarchicalGrouping}
126+
/>
127+
);
128+
}
129+
130+
const stacktrace = getThreadStacktrace(
131+
stackType !== STACK_TYPE.ORIGINAL,
132+
activeThread
133+
);
134+
135+
if (stacktrace) {
136+
return (
137+
<StackTrace
138+
stacktrace={stacktrace}
139+
stackView={
140+
raw
141+
? STACK_VIEW.RAW
142+
: activeDisplayOptions.includes(DisplayOption.FULL_STACK_TRACE)
143+
? STACK_VIEW.FULL
144+
: STACK_VIEW.APP
145+
}
146+
newestFirst={recentFirst}
147+
event={event}
148+
platform={platform}
149+
groupingCurrentLevel={groupingCurrentLevel}
150+
hasHierarchicalGrouping={hasHierarchicalGrouping}
151+
/>
152+
);
153+
}
154+
155+
return (
156+
<NoStackTraceMessage
157+
message={activeThread?.crashed ? t('Thread Errored') : undefined}
158+
/>
159+
);
160+
}
161+
162+
function getTitle() {
163+
if (hasMoreThanOneThread && activeThread) {
164+
return (
165+
<ThreadSelector
166+
threads={threads}
167+
activeThread={activeThread}
168+
event={event}
169+
onChange={thread => {
170+
setState({
171+
...state,
172+
activeThread: thread,
173+
stackType: STACK_TYPE.ORIGINAL,
174+
});
175+
}}
176+
exception={exception}
177+
fullWidth
178+
/>
179+
);
180+
}
181+
182+
return <Title>{t('Stack Trace')}</Title>;
183+
}
184+
185+
return (
186+
<TraceEventDataSection
187+
type={type}
188+
stackType={stackType}
189+
projectId={projectId}
190+
eventId={event.id}
191+
recentFirst={isStacktraceNewestFirst()}
192+
fullStackTrace={stackView === STACK_VIEW.FULL}
193+
title={getTitle()}
194+
platform={platform}
195+
showPermalink={!hasMoreThanOneThread}
196+
wrapTitle={false}
197+
>
198+
{childrenProps => (
199+
<Fragment>
200+
{renderPills()}
201+
{renderContent(childrenProps)}
202+
</Fragment>
203+
)}
204+
</TraceEventDataSection>
205+
);
206+
}
207+
208+
export default Threads;
209+
210+
const Title = styled('h3')`
211+
margin-bottom: 0;
212+
`;

0 commit comments

Comments
 (0)