From bc036a73c2c80d08039edb893beddbc671a00162 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 17 Jun 2024 17:06:05 +0200 Subject: [PATCH 1/7] cleanup & fix touchevent ignore behaviour --- src/js/touchevents.tsx | 55 +++++++++++++++++++-------------------- test/touchevents.test.tsx | 2 +- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/js/touchevents.tsx b/src/js/touchevents.tsx index fa58273baf..aff09a6192 100644 --- a/src/js/touchevents.tsx +++ b/src/js/touchevents.tsx @@ -186,43 +186,30 @@ class TouchEventBoundary extends React.Component { } const props = currentInst.memoizedProps; - const sentryLabel = + const labelValue = typeof props?.[SENTRY_LABEL_PROP_KEY] !== 'undefined' ? `${props[SENTRY_LABEL_PROP_KEY]}` - : undefined; - - // For some reason type narrowing doesn't work as expected with indexing when checking it all in one go in - // the "check-label" if sentence, so we have to assign it to a variable here first - let labelValue; - if (typeof this.props.labelName === 'string') - labelValue = props?.[this.props.labelName]; + // For some reason type narrowing doesn't work as expected with indexing when checking it all in one go in + // the "check-label" if sentence, so we have to assign it to a variable here first + : (typeof this.props.labelName === 'string') ? props?.[this.props.labelName] : undefined; // Check the label first - if (sentryLabel && !this._isNameIgnored(sentryLabel)) { - if (!activeLabel) { - activeLabel = sentryLabel; - } - componentTreeNames.push(sentryLabel); - } else if ( - typeof labelValue === 'string' && - !this._isNameIgnored(labelValue) - ) { - if (!activeLabel) { - activeLabel = labelValue; + if (labelValue && typeof labelValue === 'string') { + if (this._pushIfNotIgnored(componentTreeNames, labelValue)) { + if (!activeLabel) { + activeLabel = labelValue; + } } - componentTreeNames.push(labelValue); } else if (currentInst.elementType) { const { elementType } = currentInst; - if ( - elementType.displayName && - !this._isNameIgnored(elementType.displayName) - ) { - // Check display name - if (!activeDisplayName) { - activeDisplayName = elementType.displayName; + // Check display name + if (elementType.displayName) { + if (this._pushIfNotIgnored(componentTreeNames, elementType.displayName)) { + if (!activeDisplayName) { + activeDisplayName = elementType.displayName; + } } - componentTreeNames.push(elementType.displayName); } } @@ -240,6 +227,18 @@ class TouchEventBoundary extends React.Component { op: UI_ACTION_TOUCH, }); } + + /** + * Pushes the name to the componentTreeNames array if it is not ignored. + */ + private _pushIfNotIgnored(componentTreeNames: string[], name: string, file?: string): boolean { + const value = file ? `${name} (${file})` : name; + if (this._isNameIgnored(value)) { + return false; + } + componentTreeNames.push(value); + return true; + } } /** diff --git a/test/touchevents.test.tsx b/test/touchevents.test.tsx index c3a18b246e..673515ca10 100644 --- a/test/touchevents.test.tsx +++ b/test/touchevents.test.tsx @@ -163,7 +163,7 @@ describe('TouchEventBoundary._onTouchStart', () => { componentTree: ['Styled(View2)', 'Styled(View)'], }, level: 'info' as SeverityLevel, - message: 'Touch event within element: Styled(View2)', + message: 'Touch event within element: Styled(View)', type: defaultProps.breadcrumbType, }); }); From b063a5edd22ce2201b7daff78c50f33d2bc6f007 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 18 Jun 2024 13:25:01 +0200 Subject: [PATCH 2/7] feat: support annotated components in touch evetns --- samples/expo/babel.config.js | 5 +- samples/expo/package.json | 1 + samples/expo/yarn.lock | 5 ++ samples/react-native/babel.config.js | 3 + samples/react-native/package.json | 1 + samples/react-native/yarn.lock | 5 ++ src/js/touchevents.tsx | 119 +++++++++++++++------------ test/touchevents.test.tsx | 72 ++++++++++++++-- 8 files changed, 151 insertions(+), 60 deletions(-) diff --git a/samples/expo/babel.config.js b/samples/expo/babel.config.js index 0f75e07239..7a13872315 100644 --- a/samples/expo/babel.config.js +++ b/samples/expo/babel.config.js @@ -1,4 +1,6 @@ -module.exports = function(api) { +const componentAnnotatePlugin = require('@sentry/babel-plugin-component-annotate'); + +module.exports = function (api) { api.cache(false); return { presets: ['babel-preset-expo'], @@ -11,6 +13,7 @@ module.exports = function(api) { }, }, ], + componentAnnotatePlugin, ], }; }; diff --git a/samples/expo/package.json b/samples/expo/package.json index c5a3506800..9809687549 100644 --- a/samples/expo/package.json +++ b/samples/expo/package.json @@ -33,6 +33,7 @@ "devDependencies": { "@babel/core": "^7.20.0", "@babel/preset-env": "7.1.6", + "@sentry/babel-plugin-component-annotate": "^2.18.0", "@types/node": "20.10.4" }, "overrides": { diff --git a/samples/expo/yarn.lock b/samples/expo/yarn.lock index 923a71ec6e..c0cc28dfff 100644 --- a/samples/expo/yarn.lock +++ b/samples/expo/yarn.lock @@ -2413,6 +2413,11 @@ component-type "^1.2.1" join-component "^1.1.0" +"@sentry/babel-plugin-component-annotate@^2.18.0": + version "2.18.0" + resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.18.0.tgz#3bee98f94945643b0762ceed1f6cca60db52bdbd" + integrity sha512-9L4RbhS3WNtc/SokIhc0dwgcvs78YSQPakZejsrIgnzLzCi8mS6PeT+BY0+QCtsXxjd1egM8hqcJeB0lukBkXA== + "@sideway/address@^4.1.3": version "4.1.4" resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0" diff --git a/samples/react-native/babel.config.js b/samples/react-native/babel.config.js index 66309ee5ed..8ad75b81a3 100644 --- a/samples/react-native/babel.config.js +++ b/samples/react-native/babel.config.js @@ -1,3 +1,5 @@ +const componentAnnotatePlugin = require('@sentry/babel-plugin-component-annotate'); + module.exports = { presets: ['module:@react-native/babel-preset'], plugins: [ @@ -9,5 +11,6 @@ module.exports = { }, }, ], + componentAnnotatePlugin, ], }; diff --git a/samples/react-native/package.json b/samples/react-native/package.json index 1f58d11bc0..5bdcc35af5 100644 --- a/samples/react-native/package.json +++ b/samples/react-native/package.json @@ -43,6 +43,7 @@ "@react-native/eslint-config": "^0.73.1", "@react-native/metro-config": "^0.73.1", "@react-native/typescript-config": "^0.73.1", + "@sentry/babel-plugin-component-annotate": "^2.18.0", "@types/react": "^18.2.65", "@types/react-native-vector-icons": "^6.4.18", "@types/react-test-renderer": "^18.0.0", diff --git a/samples/react-native/yarn.lock b/samples/react-native/yarn.lock index 07789f3bd5..efba588fb4 100644 --- a/samples/react-native/yarn.lock +++ b/samples/react-native/yarn.lock @@ -3127,6 +3127,11 @@ color "^4.2.3" warn-once "^0.1.0" +"@sentry/babel-plugin-component-annotate@^2.18.0": + version "2.18.0" + resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.18.0.tgz#3bee98f94945643b0762ceed1f6cca60db52bdbd" + integrity sha512-9L4RbhS3WNtc/SokIhc0dwgcvs78YSQPakZejsrIgnzLzCi8mS6PeT+BY0+QCtsXxjd1egM8hqcJeB0lukBkXA== + "@sideway/address@^4.1.3": version "4.1.4" resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0" diff --git a/src/js/touchevents.tsx b/src/js/touchevents.tsx index aff09a6192..126bf6d8c3 100644 --- a/src/js/touchevents.tsx +++ b/src/js/touchevents.tsx @@ -2,7 +2,7 @@ import { addBreadcrumb, getCurrentHub } from '@sentry/core'; import type { SeverityLevel } from '@sentry/types'; import { logger } from '@sentry/utils'; import * as React from 'react'; -import type { GestureResponderEvent} from 'react-native'; +import type { GestureResponderEvent } from 'react-native'; import { StyleSheet, View } from 'react-native'; import { createIntegration } from './integrations/factory'; @@ -53,6 +53,9 @@ const DEFAULT_BREADCRUMB_TYPE = 'user'; const DEFAULT_MAX_COMPONENT_TREE_SIZE = 20; const SENTRY_LABEL_PROP_KEY = 'sentry-label'; +const SENTRY_COMPONENT_PROP_KEY = 'data-sentry-component'; +const SENTRY_ELEMENT_PROP_KEY = 'data-sentry-element'; +const SENTRY_FILE_PROP_KEY = 'data-sentry-source-file'; interface ElementInstance { elementType?: { @@ -63,6 +66,13 @@ interface ElementInstance { return?: ElementInstance; } +interface TouchedComponentInfo { + name?: string; + label?: string; + element?: string; + file?: string; +} + interface PrivateGestureResponderEvent extends GestureResponderEvent { _targetInst?: ElementInstance; } @@ -71,7 +81,6 @@ interface PrivateGestureResponderEvent extends GestureResponderEvent { * Boundary to log breadcrumbs for interaction events. */ class TouchEventBoundary extends React.Component { - public static displayName: string = '__Sentry.TouchEventBoundary'; public static defaultProps: Partial = { breadcrumbCategory: DEFAULT_BREADCRUMB_CATEGORY, @@ -113,18 +122,17 @@ class TouchEventBoundary extends React.Component { /** * Logs the touch event given the component tree names and a label. */ - private _logTouchEvent( - componentTreeNames: string[], - activeLabel?: string - ): void { + private _logTouchEvent(touchPath: TouchedComponentInfo[], label?: string): void { const level = 'info' as SeverityLevel; + + const root = touchPath[0]; + const detail = label ? label : `${root.name}${root.file ? ` (${root.file})` : ''}`; + const crumb = { category: this.props.breadcrumbCategory, - data: { componentTree: componentTreeNames }, + data: { path: touchPath }, level: level, - message: activeLabel - ? `Touch event within element: ${activeLabel}` - : 'Touch event within component tree', + message: `Touch event within element: ${detail}`, type: this.props.breadcrumbType, }; addBreadcrumb(crumb); @@ -147,7 +155,7 @@ class TouchEventBoundary extends React.Component { return ignoreNames.some( (ignoreName: string | RegExp) => (typeof ignoreName === 'string' && name === ignoreName) || - (ignoreName instanceof RegExp && name.match(ignoreName)) + (ignoreName instanceof RegExp && name.match(ignoreName)), ); } @@ -166,64 +174,62 @@ class TouchEventBoundary extends React.Component { } let currentInst: ElementInstance | undefined = e._targetInst; - - let activeLabel: string | undefined; - let activeDisplayName: string | undefined; - const componentTreeNames: string[] = []; + const touchPath: TouchedComponentInfo[] = []; while ( currentInst && // maxComponentTreeSize will always be defined as we have a defaultProps. But ts needs a check so this is here. this.props.maxComponentTreeSize && - componentTreeNames.length < this.props.maxComponentTreeSize + touchPath.length < this.props.maxComponentTreeSize ) { if ( // If the loop gets to the boundary itself, break. - currentInst.elementType?.displayName === - TouchEventBoundary.displayName + currentInst.elementType?.displayName === TouchEventBoundary.displayName ) { break; } - const props = currentInst.memoizedProps; + const props = currentInst.memoizedProps ?? {}; + const info: TouchedComponentInfo = {}; + if (typeof props[SENTRY_COMPONENT_PROP_KEY] === 'string' && props[SENTRY_COMPONENT_PROP_KEY].length > 0) { + info.name = props[SENTRY_COMPONENT_PROP_KEY]; + } + if (typeof props[SENTRY_ELEMENT_PROP_KEY] === 'string' && props[SENTRY_ELEMENT_PROP_KEY].length > 0) { + info.element = props[SENTRY_ELEMENT_PROP_KEY]; + } + if (typeof props[SENTRY_FILE_PROP_KEY] === 'string' && props[SENTRY_FILE_PROP_KEY].length > 0) { + info.file = props[SENTRY_FILE_PROP_KEY]; + } + const labelValue = - typeof props?.[SENTRY_LABEL_PROP_KEY] !== 'undefined' - ? `${props[SENTRY_LABEL_PROP_KEY]}` - // For some reason type narrowing doesn't work as expected with indexing when checking it all in one go in - // the "check-label" if sentence, so we have to assign it to a variable here first - : (typeof this.props.labelName === 'string') ? props?.[this.props.labelName] : undefined; - - // Check the label first - if (labelValue && typeof labelValue === 'string') { - if (this._pushIfNotIgnored(componentTreeNames, labelValue)) { - if (!activeLabel) { - activeLabel = labelValue; - } - } - } else if (currentInst.elementType) { - const { elementType } = currentInst; - - // Check display name - if (elementType.displayName) { - if (this._pushIfNotIgnored(componentTreeNames, elementType.displayName)) { - if (!activeDisplayName) { - activeDisplayName = elementType.displayName; - } - } - } + typeof props[SENTRY_LABEL_PROP_KEY] === 'string' + ? props[SENTRY_LABEL_PROP_KEY] + : // For some reason type narrowing doesn't work as expected with indexing when checking it all in one go in + // the "check-label" if sentence, so we have to assign it to a variable here first + typeof this.props.labelName === 'string' + ? props[this.props.labelName] + : undefined; + + if (typeof labelValue === 'string' && labelValue.length > 0) { + info.label = labelValue; + } + + if (!info.name && currentInst.elementType?.displayName) { + info.name = currentInst.elementType?.displayName; } + this._pushIfNotIgnored(touchPath, info); + currentInst = currentInst.return; } - const finalLabel = activeLabel ?? activeDisplayName; - - if (componentTreeNames.length > 0 || finalLabel) { - this._logTouchEvent(componentTreeNames, finalLabel); + const label = touchPath.find(info => info.label)?.label; + if (touchPath.length > 0) { + this._logTouchEvent(touchPath, label); } this._tracingIntegration?.startUserInteractionTransaction({ - elementId: activeLabel, + elementId: label, op: UI_ACTION_TOUCH, }); } @@ -231,12 +237,17 @@ class TouchEventBoundary extends React.Component { /** * Pushes the name to the componentTreeNames array if it is not ignored. */ - private _pushIfNotIgnored(componentTreeNames: string[], name: string, file?: string): boolean { - const value = file ? `${name} (${file})` : name; - if (this._isNameIgnored(value)) { + private _pushIfNotIgnored(touchPath: TouchedComponentInfo[], value: TouchedComponentInfo): boolean { + if (!value.name && !value.label) { + return false; + } + if (value.name && this._isNameIgnored(value.name)) { + return false; + } + if (value.label && this._isNameIgnored(value.label)) { return false; } - componentTreeNames.push(value); + touchPath.push(value); return true; } } @@ -249,9 +260,9 @@ class TouchEventBoundary extends React.Component { const withTouchEventBoundary = ( // eslint-disable-next-line @typescript-eslint/no-explicit-any InnerComponent: React.ComponentType, - boundaryProps?: TouchEventBoundaryProps + boundaryProps?: TouchEventBoundaryProps, ): React.FunctionComponent => { - const WrappedComponent: React.FunctionComponent = (props) => ( + const WrappedComponent: React.FunctionComponent = props => ( diff --git a/test/touchevents.test.tsx b/test/touchevents.test.tsx index 673515ca10..2f448dc5cd 100644 --- a/test/touchevents.test.tsx +++ b/test/touchevents.test.tsx @@ -5,7 +5,7 @@ import * as core from '@sentry/core'; import type { SeverityLevel } from '@sentry/types'; import { TouchEventBoundary } from '../src/js/touchevents'; -import { getDefaultTestClientOptions,TestClient } from './mocks/client'; +import { getDefaultTestClientOptions, TestClient } from './mocks/client'; describe('TouchEventBoundary._onTouchStart', () => { let addBreadcrumb: jest.SpyInstance; @@ -101,7 +101,7 @@ describe('TouchEventBoundary._onTouchStart', () => { expect(addBreadcrumb).toBeCalledWith({ category: defaultProps.breadcrumbCategory, data: { - componentTree: ['View', 'Connect(View)', 'LABEL!'], + path: [{ name: 'View' }, { name: 'Connect(View)' }, { label: 'LABEL!' }], }, level: 'info' as SeverityLevel, message: 'Touch event within element: LABEL!', @@ -160,7 +160,7 @@ describe('TouchEventBoundary._onTouchStart', () => { expect(addBreadcrumb).toBeCalledWith({ category: defaultProps.breadcrumbCategory, data: { - componentTree: ['Styled(View2)', 'Styled(View)'], + path: [{ name: 'Styled(View)' }], }, level: 'info' as SeverityLevel, message: 'Touch event within element: Styled(View)', @@ -168,7 +168,7 @@ describe('TouchEventBoundary._onTouchStart', () => { }); }); - it('maxComponentTreeSize', () => { + it('maxpathSize', () => { const { defaultProps } = TouchEventBoundary; const boundary = new TouchEventBoundary({ ...defaultProps, @@ -210,11 +210,73 @@ describe('TouchEventBoundary._onTouchStart', () => { expect(addBreadcrumb).toBeCalledWith({ category: defaultProps.breadcrumbCategory, data: { - componentTree: ['Connect(View)', 'Styled(View)'], + path: [{ label: 'Connect(View)' }, { name: 'Styled(View)' }], }, level: 'info' as SeverityLevel, message: 'Touch event within element: Connect(View)', type: defaultProps.breadcrumbType, }); }); + + // see https://docs.sentry.io/platforms/javascript/guides/react/features/component-names/ + it('uses custom names provided by babel plugin', () => { + const { defaultProps } = TouchEventBoundary; + const boundary = new TouchEventBoundary(defaultProps); + + const event = { + _targetInst: { + elementType: { + displayName: 'View', + }, + memoizedProps: { + 'data-sentry-component': 'Screen', + 'data-sentry-element': 'AnimatedNativeScreen', + 'data-sentry-source-file': 'screen.tsx', + }, + return: { + elementType: { + displayName: 'Text', + }, + return: { + memoizedProps: { + 'custom-sentry-label-name': 'Connect(View)', + 'data-sentry-component': 'MyView', + 'data-sentry-source-file': 'myview.tsx', + }, + return: { + elementType: { + displayName: 'Styled(View)', + }, + return: { + memoizedProps: { + 'data-sentry-component': 'Happy', + 'data-sentry-element': 'View', + 'data-sentry-source-file': 'happyview.js', + }, + }, + }, + }, + }, + }, + }; + + // @ts-expect-error Calling private member + boundary._onTouchStart(event); + + expect(addBreadcrumb).toBeCalledWith({ + category: defaultProps.breadcrumbCategory, + data: { + path: [ + { element: 'AnimatedNativeScreen', file: 'screen.tsx', name: 'Screen' }, + { name: 'Text' }, + { file: 'myview.tsx', name: 'MyView' }, + { name: 'Styled(View)' }, + { element: 'View', file: 'happyview.js', name: 'Happy' }, + ], + }, + level: 'info' as SeverityLevel, + message: 'Touch event within element: Screen (screen.tsx)', + type: defaultProps.breadcrumbType, + }); + }); }); From 3ebc41bc6e060959efce8b0795c4120cbbbd3d9c Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 18 Jun 2024 14:05:00 +0200 Subject: [PATCH 3/7] feat: deduplicate subsequent events in the touch path --- src/js/touchevents.tsx | 15 ++++++++++++--- test/touchevents.test.tsx | 28 +++++++++++++++++----------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/js/touchevents.tsx b/src/js/touchevents.tsx index 126bf6d8c3..88ba178864 100644 --- a/src/js/touchevents.tsx +++ b/src/js/touchevents.tsx @@ -191,16 +191,19 @@ class TouchEventBoundary extends React.Component { const props = currentInst.memoizedProps ?? {}; const info: TouchedComponentInfo = {}; - if (typeof props[SENTRY_COMPONENT_PROP_KEY] === 'string' && props[SENTRY_COMPONENT_PROP_KEY].length > 0) { + + // provided by @sentry/babel-plugin-component-annotate + if (typeof props[SENTRY_COMPONENT_PROP_KEY] === 'string' && props[SENTRY_COMPONENT_PROP_KEY].length > 0 && props[SENTRY_COMPONENT_PROP_KEY] !== 'unknown') { info.name = props[SENTRY_COMPONENT_PROP_KEY]; } - if (typeof props[SENTRY_ELEMENT_PROP_KEY] === 'string' && props[SENTRY_ELEMENT_PROP_KEY].length > 0) { + if (typeof props[SENTRY_ELEMENT_PROP_KEY] === 'string' && props[SENTRY_ELEMENT_PROP_KEY].length > 0 && props[SENTRY_ELEMENT_PROP_KEY] !== 'unknown') { info.element = props[SENTRY_ELEMENT_PROP_KEY]; } - if (typeof props[SENTRY_FILE_PROP_KEY] === 'string' && props[SENTRY_FILE_PROP_KEY].length > 0) { + if (typeof props[SENTRY_FILE_PROP_KEY] === 'string' && props[SENTRY_FILE_PROP_KEY].length > 0 && props[SENTRY_FILE_PROP_KEY] !== 'unknown') { info.file = props[SENTRY_FILE_PROP_KEY]; } + // use custom label if provided by the user, or displayName if available const labelValue = typeof props[SENTRY_LABEL_PROP_KEY] === 'string' ? props[SENTRY_LABEL_PROP_KEY] @@ -247,6 +250,12 @@ class TouchEventBoundary extends React.Component { if (value.label && this._isNameIgnored(value.label)) { return false; } + + // Deduplicate same subsequent items. + if (touchPath.length > 0 && JSON.stringify(touchPath[touchPath.length - 1]) === JSON.stringify(value)) { + return false; + } + touchPath.push(value); return true; } diff --git a/test/touchevents.test.tsx b/test/touchevents.test.tsx index 2f448dc5cd..1df574cf09 100644 --- a/test/touchevents.test.tsx +++ b/test/touchevents.test.tsx @@ -238,23 +238,29 @@ describe('TouchEventBoundary._onTouchStart', () => { displayName: 'Text', }, return: { - memoizedProps: { - 'custom-sentry-label-name': 'Connect(View)', - 'data-sentry-component': 'MyView', - 'data-sentry-source-file': 'myview.tsx', + elementType: { + displayName: 'Text', }, return: { - elementType: { - displayName: 'Styled(View)', + memoizedProps: { + 'custom-sentry-label-name': 'Connect(View)', + 'data-sentry-component': 'MyView', + 'data-sentry-element': 'unknown', // should be ignored + 'data-sentry-source-file': 'myview.tsx', }, return: { - memoizedProps: { - 'data-sentry-component': 'Happy', - 'data-sentry-element': 'View', - 'data-sentry-source-file': 'happyview.js', + elementType: { + displayName: 'Styled(View)', + }, + return: { + memoizedProps: { + 'data-sentry-component': 'Happy', + 'data-sentry-element': 'View', + 'data-sentry-source-file': 'happyview.js', + }, }, }, - }, + } }, }, }, From af51f86ac65d6ea3e6f3a601b40ff3dbf25d2baa Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 18 Jun 2024 14:13:17 +0200 Subject: [PATCH 4/7] chore: add changelog --- CHANGELOG.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2e7f8902a..14525eb6e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Features + +- Improve touch event component info if annotated with [`@sentry/babel-plugin-component-annotate`](https://www.npmjs.com/package/@sentry/babel-plugin-component-annotate) ([#3899](https://github.com/getsentry/sentry-react-native/pull/3899)) + ## 5.24.1 ### Fixes @@ -642,7 +648,7 @@ This release is compatible with `expo@50.0.0-preview.6` and newer. }); ``` - Read more at https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md#7690 + Read more at - Report current screen in `contexts.app.view_names` ([#3339](https://github.com/getsentry/sentry-react-native/pull/3339)) @@ -1033,6 +1039,7 @@ This has been fixed in [version `5.9.1`](https://github.com/getsentry/sentry-rea ## 5.4.0 ### Features + - Add TS 4.1 typings ([#2995](https://github.com/getsentry/sentry-react-native/pull/2995)) - TS 3.8 are present and work automatically with older projects - Add CPU Info to Device Context ([#2984](https://github.com/getsentry/sentry-react-native/pull/2984)) @@ -2680,7 +2687,7 @@ We are looking into ways making this more stable and plan to re-enable it again ## v0.23.2 -- Fixed #228 again ¯\\_(ツ)_/¯ +- Fixed #228 again ¯\\*(ツ)*/¯ ## v0.23.1 From f7d9a353e5465b2b58ea1046d1da9936cf9c0d39 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Thu, 20 Jun 2024 15:25:39 +0200 Subject: [PATCH 5/7] Update CHANGELOG.md Co-authored-by: LucasZF --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14525eb6e6..a31a0e33da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2687,7 +2687,7 @@ We are looking into ways making this more stable and plan to re-enable it again ## v0.23.2 -- Fixed #228 again ¯\\*(ツ)*/¯ +- Fixed #228 again ¯\\_(ツ)_/¯ ## v0.23.1 From 9c5514c74a393e5ee0c0053c849b52f798fcdfa9 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich <31292499+krystofwoldrich@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:32:18 +0200 Subject: [PATCH 6/7] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a31a0e33da..959f05bec7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -648,7 +648,7 @@ This release is compatible with `expo@50.0.0-preview.6` and newer. }); ``` - Read more at + Read more at https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md#7690 - Report current screen in `contexts.app.view_names` ([#3339](https://github.com/getsentry/sentry-react-native/pull/3339)) From 99baa5570ac3cc9b1f414a079f8356023d59a0fa Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 25 Jun 2024 13:42:08 +0200 Subject: [PATCH 7/7] review comments --- test/touchevents.test.tsx | 60 ++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/test/touchevents.test.tsx b/test/touchevents.test.tsx index 1df574cf09..4d5dd1f3cd 100644 --- a/test/touchevents.test.tsx +++ b/test/touchevents.test.tsx @@ -238,29 +238,24 @@ describe('TouchEventBoundary._onTouchStart', () => { displayName: 'Text', }, return: { - elementType: { - displayName: 'Text', + memoizedProps: { + 'custom-sentry-label-name': 'Connect(View)', + 'data-sentry-component': 'MyView', + 'data-sentry-element': 'unknown', // should be ignored + 'data-sentry-source-file': 'myview.tsx', }, return: { - memoizedProps: { - 'custom-sentry-label-name': 'Connect(View)', - 'data-sentry-component': 'MyView', - 'data-sentry-element': 'unknown', // should be ignored - 'data-sentry-source-file': 'myview.tsx', + elementType: { + displayName: 'Styled(View)', }, return: { - elementType: { - displayName: 'Styled(View)', - }, - return: { - memoizedProps: { - 'data-sentry-component': 'Happy', - 'data-sentry-element': 'View', - 'data-sentry-source-file': 'happyview.js', - }, + memoizedProps: { + 'data-sentry-component': 'Happy', + 'data-sentry-element': 'View', + 'data-sentry-source-file': 'happyview.js', }, }, - } + }, }, }, }, @@ -285,4 +280,35 @@ describe('TouchEventBoundary._onTouchStart', () => { type: defaultProps.breadcrumbType, }); }); + + it('deduplicates', () => { + const { defaultProps } = TouchEventBoundary; + const boundary = new TouchEventBoundary(defaultProps); + + const event = { + _targetInst: { + elementType: { + displayName: 'Text', + }, + return: { + elementType: { + displayName: 'Text', + }, + }, + }, + }; + + // @ts-expect-error Calling private member + boundary._onTouchStart(event); + + expect(addBreadcrumb).toBeCalledWith({ + category: defaultProps.breadcrumbCategory, + data: { + path: [{ name: 'Text' }], + }, + level: 'info' as SeverityLevel, + message: 'Touch event within element: Text', + type: defaultProps.breadcrumbType, + }); + }); });