@@ -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,77 +174,80 @@ 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 ;
192
+ const props = currentInst . memoizedProps ?? { } ;
193
+ const info : TouchedComponentInfo = { } ;
194
+ if ( typeof props [ SENTRY_COMPONENT_PROP_KEY ] === 'string' && props [ SENTRY_COMPONENT_PROP_KEY ] . length > 0 ) {
195
+ info . name = props [ SENTRY_COMPONENT_PROP_KEY ] ;
196
+ }
197
+ if ( typeof props [ SENTRY_ELEMENT_PROP_KEY ] === 'string' && props [ SENTRY_ELEMENT_PROP_KEY ] . length > 0 ) {
198
+ info . element = props [ SENTRY_ELEMENT_PROP_KEY ] ;
199
+ }
200
+ if ( typeof props [ SENTRY_FILE_PROP_KEY ] === 'string' && props [ SENTRY_FILE_PROP_KEY ] . length > 0 ) {
201
+ info . file = props [ SENTRY_FILE_PROP_KEY ] ;
202
+ }
203
+
189
204
const labelValue =
190
- typeof props ?. [ SENTRY_LABEL_PROP_KEY ] !== 'undefined'
191
- ? `${ props [ SENTRY_LABEL_PROP_KEY ] } `
192
- // For some reason type narrowing doesn't work as expected with indexing when checking it all in one go in
193
- // the "check-label" if sentence, so we have to assign it to a variable here first
194
- : ( typeof this . props . labelName === 'string' ) ? props ?. [ this . props . labelName ] : undefined ;
195
-
196
- // Check the label first
197
- if ( labelValue && typeof labelValue === 'string' ) {
198
- if ( this . _pushIfNotIgnored ( componentTreeNames , labelValue ) ) {
199
- if ( ! activeLabel ) {
200
- activeLabel = labelValue ;
201
- }
202
- }
203
- } else if ( currentInst . elementType ) {
204
- const { elementType } = currentInst ;
205
-
206
- // Check display name
207
- if ( elementType . displayName ) {
208
- if ( this . _pushIfNotIgnored ( componentTreeNames , elementType . displayName ) ) {
209
- if ( ! activeDisplayName ) {
210
- activeDisplayName = elementType . displayName ;
211
- }
212
- }
213
- }
205
+ typeof props [ SENTRY_LABEL_PROP_KEY ] === 'string'
206
+ ? props [ SENTRY_LABEL_PROP_KEY ]
207
+ : // For some reason type narrowing doesn't work as expected with indexing when checking it all in one go in
208
+ // the "check-label" if sentence, so we have to assign it to a variable here first
209
+ typeof this . props . labelName === 'string'
210
+ ? props [ this . props . labelName ]
211
+ : undefined ;
212
+
213
+ if ( typeof labelValue === 'string' && labelValue . length > 0 ) {
214
+ info . label = labelValue ;
215
+ }
216
+
217
+ if ( ! info . name && currentInst . elementType ?. displayName ) {
218
+ info . name = currentInst . elementType ?. displayName ;
214
219
}
215
220
221
+ this . _pushIfNotIgnored ( touchPath , info ) ;
222
+
216
223
currentInst = currentInst . return ;
217
224
}
218
225
219
- const finalLabel = activeLabel ?? activeDisplayName ;
220
-
221
- if ( componentTreeNames . length > 0 || finalLabel ) {
222
- this . _logTouchEvent ( componentTreeNames , finalLabel ) ;
226
+ const label = touchPath . find ( info => info . label ) ?. label ;
227
+ if ( touchPath . length > 0 ) {
228
+ this . _logTouchEvent ( touchPath , label ) ;
223
229
}
224
230
225
231
this . _tracingIntegration ?. startUserInteractionTransaction ( {
226
- elementId : activeLabel ,
232
+ elementId : label ,
227
233
op : UI_ACTION_TOUCH ,
228
234
} ) ;
229
235
}
230
236
231
237
/**
232
238
* Pushes the name to the componentTreeNames array if it is not ignored.
233
239
*/
234
- private _pushIfNotIgnored ( componentTreeNames : string [ ] , name : string , file ?: string ) : boolean {
235
- const value = file ? `${ name } (${ file } )` : name ;
236
- if ( this . _isNameIgnored ( value ) ) {
240
+ private _pushIfNotIgnored ( touchPath : TouchedComponentInfo [ ] , value : TouchedComponentInfo ) : boolean {
241
+ if ( ! value . name && ! value . label ) {
242
+ return false ;
243
+ }
244
+ if ( value . name && this . _isNameIgnored ( value . name ) ) {
245
+ return false ;
246
+ }
247
+ if ( value . label && this . _isNameIgnored ( value . label ) ) {
237
248
return false ;
238
249
}
239
- componentTreeNames . push ( value ) ;
250
+ touchPath . push ( value ) ;
240
251
return true ;
241
252
}
242
253
}
@@ -249,9 +260,9 @@ class TouchEventBoundary extends React.Component<TouchEventBoundaryProps> {
249
260
const withTouchEventBoundary = (
250
261
// eslint-disable-next-line @typescript-eslint/no-explicit-any
251
262
InnerComponent : React . ComponentType < any > ,
252
- boundaryProps ?: TouchEventBoundaryProps
263
+ boundaryProps ?: TouchEventBoundaryProps ,
253
264
) : React . FunctionComponent => {
254
- const WrappedComponent : React . FunctionComponent = ( props ) => (
265
+ const WrappedComponent : React . FunctionComponent = props => (
255
266
< TouchEventBoundary { ...( boundaryProps ?? { } ) } >
256
267
< InnerComponent { ...props } />
257
268
</ TouchEventBoundary >
0 commit comments