Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
diff --git a/node_modules/@sentry/core/build/cjs/utils/browser.js b/node_modules/@sentry/core/build/cjs/utils/browser.js
index 3169c25..19acc49 100644
--- a/node_modules/@sentry/core/build/cjs/utils/browser.js
+++ b/node_modules/@sentry/core/build/cjs/utils/browser.js
@@ -9,8 +9,8 @@ const DEFAULT_MAX_STRING_LENGTH = 80;

/**
* Given a child DOM element, returns a query-selector statement describing that
- * and its ancestors
- * e.g. [HTMLElement] => body > div > input#foo.btn[name=baz]
+ * and its ancestors, prefixed with data-sentry-label if found on ancestor
+ * e.g. [HTMLElement] => [data-sentry-label="MyLabel"] div.css-146c3p1.r-1udh08x.r-1udbk01.r-1iln25a > svg
* @returns generated DOM path
*/
function htmlTreeAsString(
@@ -53,7 +53,30 @@ function htmlTreeAsString(
currentElem = currentElem.parentNode;
}

- return out.reverse().join(separator);
+ const cssSelector = out.reverse().join(separator);
+
+ // If cssSelector already contains data-sentry-label, return as is
+ if (cssSelector.includes('[data-sentry-label="')) {
+ return cssSelector;
+ }
+
+ // Search for data-sentry-label up to 15 levels (beyond the 5 levels of cssSelector)
+ let labelElem = elem;
+ let dataLabel = null;
+ for (let i = 0; i < 15 && labelElem; i++) {
+ // @ts-expect-error WINDOW has HTMLElement
+ if (WINDOW.HTMLElement && labelElem instanceof HTMLElement && labelElem.dataset && labelElem.dataset['sentryLabel']) {
+ dataLabel = labelElem.dataset['sentryLabel'];
+ break;
+ }
+ labelElem = labelElem.parentNode;
+ }
+
+ if (dataLabel) {
+ return `[data-sentry-label="${dataLabel}"] ${cssSelector}`;
+ }
+
+ return cssSelector;
} catch {
return '<unknown>';
}
@@ -77,8 +100,12 @@ function _htmlElementAsString(el, keyAttrs) {

// @ts-expect-error WINDOW has HTMLElement
if (WINDOW.HTMLElement) {
- // If using the component name annotation plugin, this value may be available on the DOM node
if (elem instanceof HTMLElement && elem.dataset) {
+ // Check for data-sentry-label first - return in attribute format [data-sentry-label="value"]
+ if (elem.dataset['sentryLabel']) {
+ return `[data-sentry-label="${elem.dataset['sentryLabel']}"]`;
+ }
+ // If using the component name annotation plugin, this value may be available on the DOM node
if (elem.dataset['sentryComponent']) {
return elem.dataset['sentryComponent'];
}
diff --git a/node_modules/@sentry/core/build/esm/utils/browser.js b/node_modules/@sentry/core/build/esm/utils/browser.js
index 2ad52b0..fd184fb 100644
--- a/node_modules/@sentry/core/build/esm/utils/browser.js
+++ b/node_modules/@sentry/core/build/esm/utils/browser.js
@@ -7,8 +7,8 @@ const DEFAULT_MAX_STRING_LENGTH = 80;

/**
* Given a child DOM element, returns a query-selector statement describing that
- * and its ancestors
- * e.g. [HTMLElement] => body > div > input#foo.btn[name=baz]
+ * and its ancestors, prefixed with data-sentry-label if found on ancestor
+ * e.g. [HTMLElement] => [data-sentry-label="MyLabel"] div.css-146c3p1.r-1udh08x.r-1udbk01.r-1iln25a > svg
* @returns generated DOM path
*/
function htmlTreeAsString(
@@ -51,7 +51,30 @@ function htmlTreeAsString(
currentElem = currentElem.parentNode;
}

- return out.reverse().join(separator);
+ const cssSelector = out.reverse().join(separator);
+
+ // If cssSelector already contains data-sentry-label, return as is
+ if (cssSelector.includes('[data-sentry-label="')) {
+ return cssSelector;
+ }
+
+ // Search for data-sentry-label up to 15 levels (beyond the 5 levels of cssSelector)
+ let labelElem = elem;
+ let dataLabel = null;
+ for (let i = 0; i < 15 && labelElem; i++) {
+ // @ts-expect-error WINDOW has HTMLElement
+ if (WINDOW.HTMLElement && labelElem instanceof HTMLElement && labelElem.dataset && labelElem.dataset['sentryLabel']) {
+ dataLabel = labelElem.dataset['sentryLabel'];
+ break;
+ }
+ labelElem = labelElem.parentNode;
+ }
+
+ if (dataLabel) {
+ return `[data-sentry-label="${dataLabel}"] ${cssSelector}`;
+ }
+
+ return cssSelector;
} catch {
return '<unknown>';
}
@@ -75,8 +98,12 @@ function _htmlElementAsString(el, keyAttrs) {

// @ts-expect-error WINDOW has HTMLElement
if (WINDOW.HTMLElement) {
- // If using the component name annotation plugin, this value may be available on the DOM node
if (elem instanceof HTMLElement && elem.dataset) {
+ // Check for data-sentry-label first - return in attribute format [data-sentry-label="value"]
+ if (elem.dataset['sentryLabel']) {
+ return `[data-sentry-label="${elem.dataset['sentryLabel']}"]`;
+ }
+ // If using the component name annotation plugin, this value may be available on the DOM node
if (elem.dataset['sentryComponent']) {
return elem.dataset['sentryComponent'];
}
13 changes: 13 additions & 0 deletions patches/sentry-core/details.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# `@sentry/core` patches

### [@sentry+core+10.24.0+001+data-sentry-label-support.patch](@sentry+core+10.24.0+001+data-sentry-label-support.patch)

- Reason: Enhances the `htmlTreeAsString` function to support `data-sentry-label` attributes for better element identification in Sentry spans. The patch:
- Always includes `data-sentry-label` in the list of checked attributes for each DOM element
- Searches up to 15 levels up the DOM tree to find a `data-sentry-label` attribute
- Prefixes the CSS selector with the found `data-sentry-label` value (e.g., `[data-sentry-label="MyLabel"] div.css-146c3p1.r-1udh08x.r-1udbk01.r-1iln25a > svg`)

This allows us to identify UI elements by meaningful labels rather than just CSS selectors, making Sentry spans more actionable.
- Upstream PR/issue: https://github.com/getsentry/sentry-javascript/pull/18398
- E/App issue: https://github.com/Expensify/App/issues/76128
- PR Introducing Patch: https://github.com/Expensify/App/pull/76547
10 changes: 10 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7476,6 +7476,16 @@ const CONST = {
REGULAR: 'regular',
INVERTED: 'inverted',
},

SENTRY_LABEL: {
NAVIGATION_TAB_BAR: {
EXPENSIFY_LOGO: 'NavigationTabBar-ExpensifyLogo',
INBOX: 'NavigationTabBar-Inbox',
REPORTS: 'NavigationTabBar-Reports',
WORKSPACES: 'NavigationTabBar-Workspaces',
ACCOUNT: 'NavigationTabBar-Account',
},
},
} as const;

const CONTINUATION_DETECTION_SEARCH_FILTER_KEYS = [
Expand Down
5 changes: 5 additions & 0 deletions src/components/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import {StyleSheet, View} from 'react-native';
import ActivityIndicator from '@components/ActivityIndicator';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';

Check warning on line 8 in src/components/Button/index.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'@components/Icon/Expensicons' import is restricted from being used by a pattern. Direct imports from Icon/Expensicons are deprecated. Please use lazy loading hooks instead. Use `useMemoizedLazyExpensifyIcons` from @hooks/useLazyAsset. See docs/LAZY_ICONS_AND_ILLUSTRATIONS.md for details

Check warning on line 8 in src/components/Button/index.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'@components/Icon/Expensicons' import is restricted from being used. Direct imports from @components/Icon/Expensicons are deprecated. Please use lazy loading hooks instead. Use `useMemoizedLazyExpensifyIcons` from @hooks/useLazyAsset. See docs/LAZY_ICONS_AND_ILLUSTRATIONS.md for details
import type {PressableRef} from '@components/Pressable/GenericPressable/types';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
import Text from '@components/Text';
Expand Down Expand Up @@ -176,6 +176,9 @@
* Whether the button should stay visually normal even when disabled.
*/
shouldStayNormalOnDisable?: boolean;

/** Label for Sentry tracking. On web, this will be added as data-sentry-label attribute. */
sentryLabel?: string;
};

type KeyboardShortcutComponentProps = Pick<ButtonProps, 'isDisabled' | 'isLoading' | 'onPress' | 'pressOnEnter' | 'allowBubble' | 'enterKeyEventListenerPriority' | 'isPressOnEnterActive'>;
Expand Down Expand Up @@ -282,6 +285,7 @@
secondLineText = '',
shouldBlendOpacity = false,
shouldStayNormalOnDisable = false,
sentryLabel,
ref,
...rest
}: ButtonProps) {
Expand Down Expand Up @@ -527,6 +531,7 @@
hoverDimmingValue={1}
onHoverIn={!isDisabled || !shouldStayNormalOnDisable ? () => setIsHovered(true) : undefined}
onHoverOut={!isDisabled || !shouldStayNormalOnDisable ? () => setIsHovered(false) : undefined}
sentryLabel={sentryLabel}
>
{shouldBlendOpacity && <View style={[StyleSheet.absoluteFill, buttonBlendForegroundStyle]} />}
{renderContent()}
Expand Down
5 changes: 5 additions & 0 deletions src/components/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import CONST from '@src/CONST';
import type ChildrenProps from '@src/types/utils/ChildrenProps';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';

Check warning on line 11 in src/components/Checkbox.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'./Icon/Expensicons' import is restricted from being used by a pattern. Direct imports from Icon/Expensicons are deprecated. Please use lazy loading hooks instead. Use `useMemoizedLazyExpensifyIcons` from @hooks/useLazyAsset. See docs/LAZY_ICONS_AND_ILLUSTRATIONS.md for details
import type {PressableRef} from './Pressable/GenericPressable/types';
import PressableWithFeedback from './Pressable/PressableWithFeedback';

Expand Down Expand Up @@ -63,6 +63,9 @@

/** Reference to the outer element */
ref?: ForwardedRef<View>;

/** Label for Sentry tracking. On web, this will be added as data-sentry-label attribute. */
sentryLabel?: string;
};

function Checkbox({
Expand All @@ -84,6 +87,7 @@
wrapperStyle,
testID,
ref,
sentryLabel,
}: CheckboxProps) {
const theme = useTheme();
const styles = useThemeStyles();
Expand Down Expand Up @@ -135,6 +139,7 @@
accessibilityLabel={accessibilityLabel}
pressDimmingValue={1}
wrapperStyle={wrapperStyle}
sentryLabel={sentryLabel}
>
{children ?? (
<View
Expand Down
7 changes: 7 additions & 0 deletions src/components/Navigation/NavigationTabBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ function NavigationTabBar({selectedTab, isTopLevelBar = false, shouldShowFloatin
testID="ExpensifyLogoButton"
onPress={navigateToChats}
wrapperStyle={styles.leftNavigationTabBarItem}
sentryLabel={CONST.SENTRY_LABEL.NAVIGATION_TAB_BAR.EXPENSIFY_LOGO}
>
<ImageSVG
style={StyleUtils.getAvatarStyle(CONST.AVATAR_SIZE.DEFAULT)}
Expand All @@ -272,6 +273,7 @@ function NavigationTabBar({selectedTab, isTopLevelBar = false, shouldShowFloatin
role={CONST.ROLE.BUTTON}
accessibilityLabel={translate('common.inbox')}
style={({hovered}) => [styles.leftNavigationTabBarItem, hovered && styles.navigationTabBarItemHovered]}
sentryLabel={CONST.SENTRY_LABEL.NAVIGATION_TAB_BAR.INBOX}
>
{({hovered}) => (
<>
Expand Down Expand Up @@ -312,6 +314,7 @@ function NavigationTabBar({selectedTab, isTopLevelBar = false, shouldShowFloatin
role={CONST.ROLE.BUTTON}
accessibilityLabel={translate('common.reports')}
style={({hovered}) => [styles.leftNavigationTabBarItem, hovered && styles.navigationTabBarItemHovered]}
sentryLabel={CONST.SENTRY_LABEL.NAVIGATION_TAB_BAR.REPORTS}
>
{({hovered}) => (
<>
Expand Down Expand Up @@ -343,6 +346,7 @@ function NavigationTabBar({selectedTab, isTopLevelBar = false, shouldShowFloatin
role={CONST.ROLE.BUTTON}
accessibilityLabel={translate('common.workspacesTabTitle')}
style={({hovered}) => [styles.leftNavigationTabBarItem, hovered && styles.navigationTabBarItemHovered]}
sentryLabel={CONST.SENTRY_LABEL.NAVIGATION_TAB_BAR.WORKSPACES}
>
{({hovered}) => (
<>
Expand Down Expand Up @@ -410,6 +414,7 @@ function NavigationTabBar({selectedTab, isTopLevelBar = false, shouldShowFloatin
accessibilityLabel={translate('common.inbox')}
wrapperStyle={styles.flex1}
style={styles.navigationTabBarItem}
sentryLabel={CONST.SENTRY_LABEL.NAVIGATION_TAB_BAR.INBOX}
>
<View>
<Icon
Expand Down Expand Up @@ -446,6 +451,7 @@ function NavigationTabBar({selectedTab, isTopLevelBar = false, shouldShowFloatin
accessibilityLabel={translate('common.reports')}
wrapperStyle={styles.flex1}
style={styles.navigationTabBarItem}
sentryLabel={CONST.SENTRY_LABEL.NAVIGATION_TAB_BAR.REPORTS}
>
<View>
<Icon
Expand Down Expand Up @@ -477,6 +483,7 @@ function NavigationTabBar({selectedTab, isTopLevelBar = false, shouldShowFloatin
accessibilityLabel={translate('common.workspacesTabTitle')}
wrapperStyle={styles.flex1}
style={styles.navigationTabBarItem}
sentryLabel={CONST.SENTRY_LABEL.NAVIGATION_TAB_BAR.WORKSPACES}
>
<View>
<Icon
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {Role} from 'react-native';
import type PressableProps from '@components/Pressable/GenericPressable/types';
import GenericPressable from './BaseGenericPressable';

function WebGenericPressable({focusable = true, ref, ...props}: PressableProps) {
function WebGenericPressable({focusable = true, ref, sentryLabel, ...props}: PressableProps) {
const accessible = (props.accessible ?? props.accessible === undefined) ? true : props.accessible;

return (
Expand All @@ -22,7 +22,7 @@ function WebGenericPressable({focusable = true, ref, ...props}: PressableProps)
aria-valuemin={props.accessibilityValue?.min}
aria-valuemax={props.accessibilityValue?.max}
aria-valuetext={props.accessibilityValue?.text}
dataSet={{tag: 'pressable', ...(props.noDragArea && {dragArea: false}), ...props.dataSet}}
dataSet={{tag: 'pressable', ...(props.noDragArea && {dragArea: false}), ...(sentryLabel && {sentryLabel}), ...props.dataSet}}
/>
);
}
Expand Down
5 changes: 5 additions & 0 deletions src/components/Pressable/GenericPressable/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ type PressableProps = RNPressableProps &
*/
isNested?: boolean;

/**
* Label for Sentry tracking. On web, this will be added as data-sentry-label attribute.
*/
sentryLabel?: string;

/**
* Reference to the outer element.
*/
Expand Down
1 change: 1 addition & 0 deletions src/pages/home/sidebar/NavigationTabBarAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ function NavigationTabBarAvatar({onPress, isSelected = false, style}: Navigation
accessibilityLabel={translate('sidebarScreen.buttonMySettings')}
wrapperStyle={styles.flex1}
style={({hovered}) => [style, hovered && styles.navigationTabBarItemHovered]}
sentryLabel={CONST.SENTRY_LABEL.NAVIGATION_TAB_BAR.ACCOUNT}
>
{({hovered}) => (
<>
Expand Down
Loading