@@ -2,7 +2,7 @@ import { addBreadcrumb, getCurrentHub } from '@sentry/core';
2
2
import type { SeverityLevel } from '@sentry/types' ;
3
3
import { logger } from '@sentry/utils' ;
4
4
import * as React from 'react' ;
5
- import type { GestureResponderEvent } from 'react-native' ;
5
+ import type { GestureResponderEvent } from 'react-native' ;
6
6
import { StyleSheet , View } from 'react-native' ;
7
7
8
8
import { createIntegration } from './integrations/factory' ;
@@ -53,6 +53,9 @@ const DEFAULT_BREADCRUMB_TYPE = 'user';
53
53
const DEFAULT_MAX_COMPONENT_TREE_SIZE = 20 ;
54
54
55
55
const SENTRY_LABEL_PROP_KEY = 'sentry-label' ;
56
+ const SENTRY_COMPONENT_PROP_KEY = 'data-sentry-component' ;
57
+ const SENTRY_ELEMENT_PROP_KEY = 'data-sentry-element' ;
58
+ const SENTRY_FILE_PROP_KEY = 'data-sentry-source-file' ;
56
59
57
60
interface ElementInstance {
58
61
elementType ?: {
@@ -63,6 +66,13 @@ interface ElementInstance {
63
66
return ?: ElementInstance ;
64
67
}
65
68
69
+ interface TouchedComponentInfo {
70
+ name ?: string ;
71
+ label ?: string ;
72
+ element ?: string ;
73
+ file ?: string ;
74
+ }
75
+
66
76
interface PrivateGestureResponderEvent extends GestureResponderEvent {
67
77
_targetInst ?: ElementInstance ;
68
78
}
@@ -71,7 +81,6 @@ interface PrivateGestureResponderEvent extends GestureResponderEvent {
71
81
* Boundary to log breadcrumbs for interaction events.
72
82
*/
73
83
class TouchEventBoundary extends React . Component < TouchEventBoundaryProps > {
74
-
75
84
public static displayName : string = '__Sentry.TouchEventBoundary' ;
76
85
public static defaultProps : Partial < TouchEventBoundaryProps > = {
77
86
breadcrumbCategory : DEFAULT_BREADCRUMB_CATEGORY ,
@@ -113,18 +122,17 @@ class TouchEventBoundary extends React.Component<TouchEventBoundaryProps> {
113
122
/**
114
123
* Logs the touch event given the component tree names and a label.
115
124
*/
116
- private _logTouchEvent (
117
- componentTreeNames : string [ ] ,
118
- activeLabel ?: string
119
- ) : void {
125
+ private _logTouchEvent ( touchPath : TouchedComponentInfo [ ] , label ?: string ) : void {
120
126
const level = 'info' as SeverityLevel ;
127
+
128
+ const root = touchPath [ 0 ] ;
129
+ const detail = label ? label : `${ root . name } ${ root . file ? ` (${ root . file } )` : '' } ` ;
130
+
121
131
const crumb = {
122
132
category : this . props . breadcrumbCategory ,
123
- data : { componentTree : componentTreeNames } ,
133
+ data : { path : touchPath } ,
124
134
level : level ,
125
- message : activeLabel
126
- ? `Touch event within element: ${ activeLabel } `
127
- : 'Touch event within component tree' ,
135
+ message : `Touch event within element: ${ detail } ` ,
128
136
type : this . props . breadcrumbType ,
129
137
} ;
130
138
addBreadcrumb ( crumb ) ;
@@ -147,7 +155,7 @@ class TouchEventBoundary extends React.Component<TouchEventBoundaryProps> {
147
155
return ignoreNames . some (
148
156
( ignoreName : string | RegExp ) =>
149
157
( typeof ignoreName === 'string' && name === ignoreName ) ||
150
- ( ignoreName instanceof RegExp && name . match ( ignoreName ) )
158
+ ( ignoreName instanceof RegExp && name . match ( ignoreName ) ) ,
151
159
) ;
152
160
}
153
161
@@ -166,80 +174,91 @@ class TouchEventBoundary extends React.Component<TouchEventBoundaryProps> {
166
174
}
167
175
168
176
let currentInst : ElementInstance | undefined = e . _targetInst ;
169
-
170
- let activeLabel : string | undefined ;
171
- let activeDisplayName : string | undefined ;
172
- const componentTreeNames : string [ ] = [ ] ;
177
+ const touchPath : TouchedComponentInfo [ ] = [ ] ;
173
178
174
179
while (
175
180
currentInst &&
176
181
// maxComponentTreeSize will always be defined as we have a defaultProps. But ts needs a check so this is here.
177
182
this . props . maxComponentTreeSize &&
178
- componentTreeNames . length < this . props . maxComponentTreeSize
183
+ touchPath . length < this . props . maxComponentTreeSize
179
184
) {
180
185
if (
181
186
// If the loop gets to the boundary itself, break.
182
- currentInst . elementType ?. displayName ===
183
- TouchEventBoundary . displayName
187
+ currentInst . elementType ?. displayName === TouchEventBoundary . displayName
184
188
) {
185
189
break ;
186
190
}
187
191
188
- const props = currentInst . memoizedProps ;
189
- const sentryLabel =
190
- typeof props ?. [ SENTRY_LABEL_PROP_KEY ] !== 'undefined'
191
- ? `${ props [ SENTRY_LABEL_PROP_KEY ] } `
192
+ const props = currentInst . memoizedProps ?? { } ;
193
+ const info : TouchedComponentInfo = { } ;
194
+
195
+ // provided by @sentry /babel-plugin-component-annotate
196
+ if ( typeof props [ SENTRY_COMPONENT_PROP_KEY ] === 'string' && props [ SENTRY_COMPONENT_PROP_KEY ] . length > 0 && props [ SENTRY_COMPONENT_PROP_KEY ] !== 'unknown' ) {
197
+ info . name = props [ SENTRY_COMPONENT_PROP_KEY ] ;
198
+ }
199
+ if ( typeof props [ SENTRY_ELEMENT_PROP_KEY ] === 'string' && props [ SENTRY_ELEMENT_PROP_KEY ] . length > 0 && props [ SENTRY_ELEMENT_PROP_KEY ] !== 'unknown' ) {
200
+ info . element = props [ SENTRY_ELEMENT_PROP_KEY ] ;
201
+ }
202
+ if ( typeof props [ SENTRY_FILE_PROP_KEY ] === 'string' && props [ SENTRY_FILE_PROP_KEY ] . length > 0 && props [ SENTRY_FILE_PROP_KEY ] !== 'unknown' ) {
203
+ info . file = props [ SENTRY_FILE_PROP_KEY ] ;
204
+ }
205
+
206
+ // use custom label if provided by the user, or displayName if available
207
+ const labelValue =
208
+ typeof props [ SENTRY_LABEL_PROP_KEY ] === 'string'
209
+ ? props [ SENTRY_LABEL_PROP_KEY ]
210
+ : // For some reason type narrowing doesn't work as expected with indexing when checking it all in one go in
211
+ // the "check-label" if sentence, so we have to assign it to a variable here first
212
+ typeof this . props . labelName === 'string'
213
+ ? props [ this . props . labelName ]
192
214
: undefined ;
193
215
194
- // For some reason type narrowing doesn't work as expected with indexing when checking it all in one go in
195
- // the "check-label" if sentence, so we have to assign it to a variable here first
196
- let labelValue ;
197
- if ( typeof this . props . labelName === 'string' )
198
- labelValue = props ?. [ this . props . labelName ] ;
199
-
200
- // Check the label first
201
- if ( sentryLabel && ! this . _isNameIgnored ( sentryLabel ) ) {
202
- if ( ! activeLabel ) {
203
- activeLabel = sentryLabel ;
204
- }
205
- componentTreeNames . push ( sentryLabel ) ;
206
- } else if (
207
- typeof labelValue === 'string' &&
208
- ! this . _isNameIgnored ( labelValue )
209
- ) {
210
- if ( ! activeLabel ) {
211
- activeLabel = labelValue ;
212
- }
213
- componentTreeNames . push ( labelValue ) ;
214
- } else if ( currentInst . elementType ) {
215
- const { elementType } = currentInst ;
216
-
217
- if (
218
- elementType . displayName &&
219
- ! this . _isNameIgnored ( elementType . displayName )
220
- ) {
221
- // Check display name
222
- if ( ! activeDisplayName ) {
223
- activeDisplayName = elementType . displayName ;
224
- }
225
- componentTreeNames . push ( elementType . displayName ) ;
226
- }
216
+ if ( typeof labelValue === 'string' && labelValue . length > 0 ) {
217
+ info . label = labelValue ;
218
+ }
219
+
220
+ if ( ! info . name && currentInst . elementType ?. displayName ) {
221
+ info . name = currentInst . elementType ?. displayName ;
227
222
}
228
223
224
+ this . _pushIfNotIgnored ( touchPath , info ) ;
225
+
229
226
currentInst = currentInst . return ;
230
227
}
231
228
232
- const finalLabel = activeLabel ?? activeDisplayName ;
233
-
234
- if ( componentTreeNames . length > 0 || finalLabel ) {
235
- this . _logTouchEvent ( componentTreeNames , finalLabel ) ;
229
+ const label = touchPath . find ( info => info . label ) ?. label ;
230
+ if ( touchPath . length > 0 ) {
231
+ this . _logTouchEvent ( touchPath , label ) ;
236
232
}
237
233
238
234
this . _tracingIntegration ?. startUserInteractionTransaction ( {
239
- elementId : activeLabel ,
235
+ elementId : label ,
240
236
op : UI_ACTION_TOUCH ,
241
237
} ) ;
242
238
}
239
+
240
+ /**
241
+ * Pushes the name to the componentTreeNames array if it is not ignored.
242
+ */
243
+ private _pushIfNotIgnored ( touchPath : TouchedComponentInfo [ ] , value : TouchedComponentInfo ) : boolean {
244
+ if ( ! value . name && ! value . label ) {
245
+ return false ;
246
+ }
247
+ if ( value . name && this . _isNameIgnored ( value . name ) ) {
248
+ return false ;
249
+ }
250
+ if ( value . label && this . _isNameIgnored ( value . label ) ) {
251
+ return false ;
252
+ }
253
+
254
+ // Deduplicate same subsequent items.
255
+ if ( touchPath . length > 0 && JSON . stringify ( touchPath [ touchPath . length - 1 ] ) === JSON . stringify ( value ) ) {
256
+ return false ;
257
+ }
258
+
259
+ touchPath . push ( value ) ;
260
+ return true ;
261
+ }
243
262
}
244
263
245
264
/**
@@ -250,9 +269,9 @@ class TouchEventBoundary extends React.Component<TouchEventBoundaryProps> {
250
269
const withTouchEventBoundary = (
251
270
// eslint-disable-next-line @typescript-eslint/no-explicit-any
252
271
InnerComponent : React . ComponentType < any > ,
253
- boundaryProps ?: TouchEventBoundaryProps
272
+ boundaryProps ?: TouchEventBoundaryProps ,
254
273
) : React . FunctionComponent => {
255
- const WrappedComponent : React . FunctionComponent = ( props ) => (
274
+ const WrappedComponent : React . FunctionComponent = props => (
256
275
< TouchEventBoundary { ...( boundaryProps ?? { } ) } >
257
276
< InnerComponent { ...props } />
258
277
</ TouchEventBoundary >
0 commit comments