Skip to content

Commit c236b1c

Browse files
Abdkhan14Abdullah Khan
authored andcommitted
feat(trace-eap-waterfall): Rendering orphan errors from new eap endpoint (#88444)
The goal of the PR is to support rendering an individual row for orphan errors (type below), in the trace waterfall: ``` type EAPError = { event_id: string; event_type: string; issue_id: number; level: Level; project_id: number; project_slug: string; start_timestamp: number; transaction: string; description?: string; }; ``` Bulk of the PR involves using guards to ensure feature parity with other event types already existing in the waterfall. Added tests. --------- Co-authored-by: Abdullah Khan <[email protected]>
1 parent 52fb172 commit c236b1c

File tree

19 files changed

+275
-151
lines changed

19 files changed

+275
-151
lines changed

static/app/components/quickTrace/utils.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {getTraceTimeRangeFromEvent} from 'sentry/utils/performance/quickTrace/ut
2121
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
2222
import {hasDatasetSelector} from 'sentry/views/dashboards/utils';
2323
import type {TraceViewSources} from 'sentry/views/performance/newTraceDetails/traceHeader/breadcrumbs';
24+
import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree';
2425
import {getTraceDetailsUrl} from 'sentry/views/performance/traceDetails/utils';
2526

2627
export function isQuickTraceEvent(
@@ -34,7 +35,7 @@ export type ErrorDestination = 'discover' | 'issue';
3435
export type TransactionDestination = 'discover' | 'performance';
3536

3637
export function generateIssueEventTarget(
37-
event: TraceError | TracePerformanceIssue,
38+
event: TraceError | TracePerformanceIssue | TraceTree.EAPError,
3839
organization: Organization,
3940
referrer?: string
4041
): LocationDescriptor {

static/app/views/performance/newTraceDetails/issuesTraceWaterfall.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import useOrganization from 'sentry/utils/useOrganization';
1919
import useProjects from 'sentry/utils/useProjects';
2020
import {IssueTraceWaterfallOverlay} from 'sentry/views/performance/newTraceDetails/issuesTraceWaterfallOverlay';
2121
import {
22+
isEAPErrorNode,
2223
isEAPSpanNode,
2324
isEAPTransactionNode,
2425
isNonTransactionEAPSpanNode,
@@ -183,7 +184,7 @@ export function IssuesTraceWaterfall(props: IssuesTraceWaterfallProps) {
183184
// Find all the nodes that match the event id from the error so that we can try and
184185
// link the user to the most specific one.
185186
const nodes = IssuesTraceTree.FindAll(props.tree.root, n => {
186-
if (isTraceErrorNode(n)) {
187+
if (isTraceErrorNode(n) || isEAPErrorNode(n)) {
187188
return n.value.event_id === props.event.eventID;
188189
}
189190
if (isTransactionNode(n)) {

static/app/views/performance/newTraceDetails/trace.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import {useTraceState, useTraceStateDispatch} from './traceState/traceStateProvi
6666
import {
6767
isAutogroupedNode,
6868
isCollapsedNode,
69+
isEAPErrorNode,
6970
isEAPSpanNode,
7071
isEAPTraceNode,
7172
isMissingInstrumentationNode,
@@ -680,7 +681,7 @@ function RenderTraceRow(props: {
680681
return <TraceAutogroupedRow {...rowProps} node={node} />;
681682
}
682683

683-
if (isTraceErrorNode(node)) {
684+
if (isTraceErrorNode(node) || isEAPErrorNode(node)) {
684685
return <TraceErrorRow {...rowProps} node={node} />;
685686
}
686687

static/app/views/performance/newTraceDetails/traceDrawer/details/error.tsx

+17-9
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {generateIssueEventTarget} from 'sentry/components/quickTrace/utils';
1212
import {t} from 'sentry/locale';
1313
import type {EventError} from 'sentry/types/event';
1414
import {useApiQuery} from 'sentry/utils/queryClient';
15+
import {isTraceErrorNode} from 'sentry/views/performance/newTraceDetails/traceGuards';
1516

1617
import type {TraceTreeNodeDetailsProps} from '../../traceDrawer/tabs/traceTreeNodeDetails';
1718
import {TraceIcons} from '../../traceIcons';
@@ -25,7 +26,9 @@ import {IssueList} from './issues/issues';
2526
import {type SectionCardKeyValueList, TraceDrawerComponents} from './styles';
2627

2728
export function ErrorNodeDetails(
28-
props: TraceTreeNodeDetailsProps<TraceTreeNode<TraceTree.TraceError>>
29+
props: TraceTreeNodeDetailsProps<
30+
TraceTreeNode<TraceTree.TraceError> | TraceTreeNode<TraceTree.EAPError>
31+
>
2932
) {
3033
const hasTraceNewUi = useHasTraceNewUi();
3134
const {node, organization, onTabScrollToNode} = props;
@@ -72,7 +75,9 @@ function LegacyErrorNodeDetails({
7275
organization,
7376
onTabScrollToNode,
7477
onParentClick,
75-
}: TraceTreeNodeDetailsProps<TraceTreeNode<TraceTree.TraceError>>) {
78+
}: TraceTreeNodeDetailsProps<
79+
TraceTreeNode<TraceTree.TraceError> | TraceTreeNode<TraceTree.EAPError>
80+
>) {
7681
const issues = useMemo(() => {
7782
return [...node.errors];
7883
}, [node.errors]);
@@ -97,13 +102,15 @@ function LegacyErrorNodeDetails({
97102
const theme = useTheme();
98103
const parentTransaction = TraceTree.ParentTransaction(node);
99104

100-
const items: SectionCardKeyValueList = [
101-
{
105+
const items: SectionCardKeyValueList = [];
106+
107+
if (isTraceErrorNode(node)) {
108+
items.push({
102109
key: 'title',
103110
subject: t('Title'),
104111
value: <TraceDrawerComponents.CopyableCardValueWithLink value={node.value.title} />,
105-
},
106-
];
112+
});
113+
}
107114

108115
if (parentTransaction) {
109116
items.push({
@@ -117,6 +124,9 @@ function LegacyErrorNodeDetails({
117124
});
118125
}
119126

127+
const description = isTraceErrorNode(node)
128+
? (node.value.message ?? node.value.title)
129+
: node.value.description;
120130
return isPending ? (
121131
<LoadingIndicator />
122132
) : data ? (
@@ -130,9 +140,7 @@ function LegacyErrorNodeDetails({
130140
</TraceDrawerComponents.IconBorder>
131141
<TraceDrawerComponents.LegacyTitleText>
132142
<div>{node.value.level ?? t('error')}</div>
133-
<TraceDrawerComponents.TitleOp
134-
text={node.value.message ?? node.value.title ?? 'Error'}
135-
/>
143+
<TraceDrawerComponents.TitleOp text={description ?? 'Error'} />
136144
</TraceDrawerComponents.LegacyTitleText>
137145
</TraceDrawerComponents.Title>
138146
<TraceDrawerComponents.Actions>

static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceTreeNodeDetails.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {ReplayRecord} from 'sentry/views/replays/types';
33

44
import {
55
isAutogroupedNode,
6+
isEAPErrorNode,
67
isEAPSpanNode,
78
isMissingInstrumentationNode,
89
isSpanNode,
@@ -37,7 +38,7 @@ export function TraceTreeNodeDetails(props: TraceTreeNodeDetailsProps<any>) {
3738
return <SpanNodeDetails {...props} />;
3839
}
3940

40-
if (isTraceErrorNode(props.node)) {
41+
if (isTraceErrorNode(props.node) || isEAPErrorNode(props.node)) {
4142
return <ErrorNodeDetails {...props} />;
4243
}
4344

static/app/views/performance/newTraceDetails/traceGuards.tsx

+14-5
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,12 @@ export function isSpanNode(
2323
);
2424
}
2525

26+
export function isEAPSpan(value: TraceTree.NodeValue): value is TraceTree.EAPSpan {
27+
return !!(value && 'is_transaction' in value);
28+
}
29+
2630
export function isEAPTransaction(value: TraceTree.NodeValue): value is TraceTree.EAPSpan {
27-
return !!(value && 'is_transaction' in value && value.is_transaction);
31+
return isEAPSpan(value) && value.is_transaction;
2832
}
2933

3034
export function isEAPTransactionNode(
@@ -36,7 +40,7 @@ export function isEAPTransactionNode(
3640
export function isEAPSpanNode(
3741
node: TraceTreeNode<TraceTree.NodeValue>
3842
): node is TraceTreeNode<TraceTree.EAPSpan> {
39-
return !!(node.value && 'is_transaction' in node.value);
43+
return isEAPSpan(node.value);
4044
}
4145

4246
export function isNonTransactionEAPSpanNode(
@@ -51,7 +55,8 @@ export function isTransactionNode(
5155
return (
5256
!!(node.value && 'transaction' in node.value) &&
5357
!isAutogroupedNode(node) &&
54-
!isEAPSpanNode(node)
58+
!isEAPSpanNode(node) &&
59+
!isEAPErrorNode(node)
5560
);
5661
}
5762

@@ -60,7 +65,7 @@ export function isEAPError(value: TraceTree.NodeValue): value is TraceTree.EAPEr
6065
value &&
6166
'event_type' in value &&
6267
value.event_type === 'error' &&
63-
!('message' in value) // a bit gross, but we won't need this soon as we remove the legacy error type
68+
'description' in value // a bit gross, but we won't need this soon as we remove the legacy error type
6469
);
6570
}
6671

@@ -94,10 +99,14 @@ export function isCollapsedNode(
9499
return node instanceof CollapsedNode;
95100
}
96101

102+
export function isTraceError(value: TraceTree.NodeValue): value is TraceTree.TraceError {
103+
return !!(value && 'level' in value && 'message' in value);
104+
}
105+
97106
export function isTraceErrorNode(
98107
node: TraceTreeNode<TraceTree.NodeValue>
99108
): node is TraceTreeNode<TraceTree.TraceError> {
100-
return !!(node.value && 'level' in node.value);
109+
return isTraceError(node.value);
101110
}
102111

103112
export function isRootNode(

static/app/views/performance/newTraceDetails/traceHeader/index.tsx

+21-12
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,7 @@ const StyledPlaceholder = styled(Placeholder)<{_height: number; _width: number}>
169169

170170
const CANDIDATE_TRACE_TITLE_OPS = ['pageload', 'navigation'];
171171

172-
export const getRepresentativeEvent = (
173-
tree: TraceTree
174-
): TraceTree.Transaction | TraceTree.EAPSpan | null => {
172+
const getRepresentativeEvent = (tree: TraceTree): TraceTree.TraceEvent | null => {
175173
const traceNode = tree.root.children[0];
176174

177175
if (!traceNode) {
@@ -182,11 +180,13 @@ export const getRepresentativeEvent = (
182180
throw new TypeError('Not trace node');
183181
}
184182

185-
let firstRootEvent: TraceTree.Transaction | TraceTree.EAPSpan | null = null;
186-
let candidateEvent: TraceTree.Transaction | TraceTree.EAPSpan | null = null;
187-
let firstEvent: TraceTree.Transaction | TraceTree.EAPSpan | null = null;
183+
let firstRootEvent: TraceTree.TraceEvent | null = null;
184+
let candidateEvent: TraceTree.TraceEvent | null = null;
185+
let firstEvent: TraceTree.TraceEvent | null = null;
188186

189-
const events = isTraceNode(traceNode) ? traceNode.value.transactions : traceNode.value;
187+
const events = isTraceNode(traceNode)
188+
? [...traceNode.value.transactions, ...traceNode.value.orphan_errors]
189+
: traceNode.value;
190190
for (const event of events) {
191191
// If we find a root transaction, we can stop looking and use it for the title.
192192
if (!firstRootEvent && isRootEvent(event)) {
@@ -198,7 +198,11 @@ export const getRepresentativeEvent = (
198198
// a root.
199199
!candidateEvent &&
200200
CANDIDATE_TRACE_TITLE_OPS.includes(
201-
'transaction.op' in event ? event['transaction.op'] : event.op
201+
'transaction.op' in event
202+
? event['transaction.op']
203+
: 'op' in event
204+
? event.op
205+
: ''
202206
)
203207
) {
204208
candidateEvent = event;
@@ -287,11 +291,16 @@ export function TraceMetaDataHeader(props: TraceMetadataHeaderProps) {
287291
props.rootEventResults.isLoading ||
288292
props.tree.type === 'loading';
289293

290-
if (isLoading) {
294+
const isError =
295+
props.metaResults.status === 'error' ||
296+
props.rootEventResults.status === 'error' ||
297+
props.tree.type === 'error';
298+
299+
if (isLoading || isError) {
291300
return <PlaceHolder organization={props.organization} />;
292301
}
293302

294-
const representativeTransaction = getRepresentativeEvent(props.tree);
303+
const representativeEvent = getRepresentativeEvent(props.tree);
295304

296305
return (
297306
<HeaderLayout>
@@ -317,14 +326,14 @@ export function TraceMetaDataHeader(props: TraceMetadataHeaderProps) {
317326
<Title
318327
tree={props.tree}
319328
traceSlug={props.traceSlug}
320-
representativeTransaction={representativeTransaction}
329+
representativeEvent={representativeEvent}
321330
/>
322331
<Meta
323332
organization={props.organization}
324333
rootEventResults={props.rootEventResults}
325334
tree={props.tree}
326335
meta={props.metaResults.data}
327-
representativeTransaction={representativeTransaction}
336+
representativeEvent={representativeEvent}
328337
/>
329338
</HeaderRow>
330339
{props.rootEventResults.data ? (

static/app/views/performance/newTraceDetails/traceHeader/meta.tsx

+18-13
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import getDuration from 'sentry/utils/duration/getDuration';
1111
import type {TraceMeta} from 'sentry/utils/performance/quickTrace/types';
1212
import type {UseApiQueryResult} from 'sentry/utils/queryClient';
1313
import type RequestError from 'sentry/utils/requestError/requestError';
14+
import {
15+
isEAPError,
16+
isTraceError,
17+
} from 'sentry/views/performance/newTraceDetails/traceGuards';
1418

1519
import {TraceDrawerComponents} from '../traceDrawer/details/styles';
1620
import type {TraceTree} from '../traceModels/traceTree';
@@ -49,11 +53,23 @@ const SectionBody = styled('div')<{rightAlign?: boolean}>`
4953
interface MetaProps {
5054
meta: TraceMeta | undefined;
5155
organization: Organization;
52-
representativeTransaction: TraceTree.Transaction | TraceTree.EAPSpan | null;
56+
representativeEvent: TraceTree.TraceEvent | null;
5357
rootEventResults: UseApiQueryResult<EventTransaction, RequestError>;
5458
tree: TraceTree;
5559
}
5660

61+
function getRootDuration(event: TraceTree.TraceEvent | null) {
62+
if (!event || isEAPError(event) || isTraceError(event)) {
63+
return '\u2014';
64+
}
65+
66+
return getDuration(
67+
('timestamp' in event ? event.timestamp : event.end_timestamp) -
68+
event.start_timestamp,
69+
2,
70+
true
71+
);
72+
}
5773
export function Meta(props: MetaProps) {
5874
const traceNode = props.tree.root.children[0]!;
5975

@@ -134,18 +150,7 @@ export function Meta(props: MetaProps) {
134150
<MetaSection
135151
headingText={t('Root Duration')}
136152
rightAlignBody
137-
bodyText={
138-
props.representativeTransaction
139-
? getDuration(
140-
('timestamp' in props.representativeTransaction
141-
? props.representativeTransaction.timestamp
142-
: props.representativeTransaction.end_timestamp) -
143-
props.representativeTransaction.start_timestamp,
144-
2,
145-
true
146-
)
147-
: '\u2014'
148-
}
153+
bodyText={getRootDuration(props.representativeEvent)}
149154
/>
150155
) : null}
151156
</MetaWrapper>

0 commit comments

Comments
 (0)