@@ -2,7 +2,7 @@ import { addBreadcrumb, getCurrentHub } from '@sentry/core';
22import type { SeverityLevel } from '@sentry/types' ;
33import { logger } from '@sentry/utils' ;
44import * as React from 'react' ;
5- import type { GestureResponderEvent } from 'react-native' ;
5+ import type { GestureResponderEvent } from 'react-native' ;
66import { StyleSheet , View } from 'react-native' ;
77
88import { createIntegration } from './integrations/factory' ;
@@ -53,6 +53,9 @@ const DEFAULT_BREADCRUMB_TYPE = 'user';
5353const DEFAULT_MAX_COMPONENT_TREE_SIZE = 20 ;
5454
5555const 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' ;
5659
5760interface ElementInstance {
5861 elementType ?: {
@@ -63,6 +66,13 @@ interface ElementInstance {
6366 return ?: ElementInstance ;
6467}
6568
69+ interface TouchedComponentInfo {
70+ name ?: string ;
71+ label ?: string ;
72+ element ?: string ;
73+ file ?: string ;
74+ }
75+
6676interface PrivateGestureResponderEvent extends GestureResponderEvent {
6777 _targetInst ?: ElementInstance ;
6878}
@@ -71,7 +81,6 @@ interface PrivateGestureResponderEvent extends GestureResponderEvent {
7181 * Boundary to log breadcrumbs for interaction events.
7282 */
7383class TouchEventBoundary extends React . Component < TouchEventBoundaryProps > {
74-
7584 public static displayName : string = '__Sentry.TouchEventBoundary' ;
7685 public static defaultProps : Partial < TouchEventBoundaryProps > = {
7786 breadcrumbCategory : DEFAULT_BREADCRUMB_CATEGORY ,
@@ -113,18 +122,17 @@ class TouchEventBoundary extends React.Component<TouchEventBoundaryProps> {
113122 /**
114123 * Logs the touch event given the component tree names and a label.
115124 */
116- private _logTouchEvent (
117- componentTreeNames : string [ ] ,
118- activeLabel ?: string
119- ) : void {
125+ private _logTouchEvent ( touchPath : TouchedComponentInfo [ ] , label ?: string ) : void {
120126 const level = 'info' as SeverityLevel ;
127+
128+ const root = touchPath [ 0 ] ;
129+ const detail = label ? label : `${ root . name } ${ root . file ? ` (${ root . file } )` : '' } ` ;
130+
121131 const crumb = {
122132 category : this . props . breadcrumbCategory ,
123- data : { componentTree : componentTreeNames } ,
133+ data : { path : touchPath } ,
124134 level : level ,
125- message : activeLabel
126- ? `Touch event within element: ${ activeLabel } `
127- : 'Touch event within component tree' ,
135+ message : `Touch event within element: ${ detail } ` ,
128136 type : this . props . breadcrumbType ,
129137 } ;
130138 addBreadcrumb ( crumb ) ;
@@ -147,7 +155,7 @@ class TouchEventBoundary extends React.Component<TouchEventBoundaryProps> {
147155 return ignoreNames . some (
148156 ( ignoreName : string | RegExp ) =>
149157 ( typeof ignoreName === 'string' && name === ignoreName ) ||
150- ( ignoreName instanceof RegExp && name . match ( ignoreName ) )
158+ ( ignoreName instanceof RegExp && name . match ( ignoreName ) ) ,
151159 ) ;
152160 }
153161
@@ -166,77 +174,80 @@ class TouchEventBoundary extends React.Component<TouchEventBoundaryProps> {
166174 }
167175
168176 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 [ ] = [ ] ;
173178
174179 while (
175180 currentInst &&
176181 // maxComponentTreeSize will always be defined as we have a defaultProps. But ts needs a check so this is here.
177182 this . props . maxComponentTreeSize &&
178- componentTreeNames . length < this . props . maxComponentTreeSize
183+ touchPath . length < this . props . maxComponentTreeSize
179184 ) {
180185 if (
181186 // If the loop gets to the boundary itself, break.
182- currentInst . elementType ?. displayName ===
183- TouchEventBoundary . displayName
187+ currentInst . elementType ?. displayName === TouchEventBoundary . displayName
184188 ) {
185189 break ;
186190 }
187191
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+
189204 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 ;
214219 }
215220
221+ this . _pushIfNotIgnored ( touchPath , info ) ;
222+
216223 currentInst = currentInst . return ;
217224 }
218225
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 ) ;
223229 }
224230
225231 this . _tracingIntegration ?. startUserInteractionTransaction ( {
226- elementId : activeLabel ,
232+ elementId : label ,
227233 op : UI_ACTION_TOUCH ,
228234 } ) ;
229235 }
230236
231237 /**
232238 * Pushes the name to the componentTreeNames array if it is not ignored.
233239 */
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 ) ) {
237248 return false ;
238249 }
239- componentTreeNames . push ( value ) ;
250+ touchPath . push ( value ) ;
240251 return true ;
241252 }
242253}
@@ -249,9 +260,9 @@ class TouchEventBoundary extends React.Component<TouchEventBoundaryProps> {
249260const withTouchEventBoundary = (
250261 // eslint-disable-next-line @typescript-eslint/no-explicit-any
251262 InnerComponent : React . ComponentType < any > ,
252- boundaryProps ?: TouchEventBoundaryProps
263+ boundaryProps ?: TouchEventBoundaryProps ,
253264) : React . FunctionComponent => {
254- const WrappedComponent : React . FunctionComponent = ( props ) => (
265+ const WrappedComponent : React . FunctionComponent = props => (
255266 < TouchEventBoundary { ...( boundaryProps ?? { } ) } >
256267 < InnerComponent { ...props } />
257268 </ TouchEventBoundary >
0 commit comments