diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e4e838615..d5b7a8e2f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ ## Unreleased +### Fixes +- Fix for missing `replay_id` from metrics ([#5483](https://github.com/getsentry/sentry-react-native/pull/5483)) + ### Dependencies - Bump JavaScript SDK from v10.30.0 to v10.31.0 ([#5480](https://github.com/getsentry/sentry-react-native/pull/5480)) diff --git a/packages/core/src/js/replay/mobilereplay.ts b/packages/core/src/js/replay/mobilereplay.ts index d65d794add..293a14ea5d 100644 --- a/packages/core/src/js/replay/mobilereplay.ts +++ b/packages/core/src/js/replay/mobilereplay.ts @@ -1,4 +1,4 @@ -import type { Client, DynamicSamplingContext, Event, EventHint, Integration } from '@sentry/core'; +import type { Client, DynamicSamplingContext, Event, EventHint, Integration, Metric } from '@sentry/core'; import { debug } from '@sentry/core'; import { isHardCrash } from '../misc'; import { hasHooks } from '../utils/clientutils'; @@ -253,6 +253,15 @@ export const mobileReplayIntegration = (initOptions: MobileReplayOptions = defau } }); + client.on('processMetric', (metric: Metric) => { + // Add replay_id to metric attributes to link metrics to replays + const currentReplayId = getCachedReplayId(); + if (currentReplayId) { + metric.attributes = metric.attributes || {}; + metric.attributes.replay_id = currentReplayId; + } + }); + client.on('beforeAddBreadcrumb', enrichXhrBreadcrumbsForMobileReplay); } diff --git a/packages/core/test/metrics.test.ts b/packages/core/test/metrics.test.ts index e1e7bc1de1..bfa17aa624 100644 --- a/packages/core/test/metrics.test.ts +++ b/packages/core/test/metrics.test.ts @@ -1,5 +1,6 @@ import { getClient, metrics, setCurrentClient } from '@sentry/core'; import { ReactNativeClient } from '../src/js'; +import { mobileReplayIntegration } from '../src/js/replay/mobilereplay'; import { getDefaultTestClientOptions } from './mocks/client'; import { NATIVE } from './mockWrapper'; @@ -176,5 +177,61 @@ describe('Metrics', () => { const sentMetric = beforeSendMetric.mock.calls[0]?.[0]; expect(sentMetric).toBeDefined(); }); + + it('metrics include replay_id when mobile replay integration is active', async () => { + const mockReplayId = 'test-replay-id-123'; + (NATIVE.getCurrentReplayId as jest.Mock).mockReturnValue(mockReplayId); + + const beforeSendMetric = jest.fn(metric => metric); + + const client = new ReactNativeClient({ + ...getDefaultTestClientOptions({ + dsn: EXAMPLE_DSN, + enableMetrics: true, + beforeSendMetric, + integrations: [mobileReplayIntegration()], + replaysSessionSampleRate: 1.0, + }), + }); + + setCurrentClient(client); + client.init(); + + // Send a metric + metrics.count('test_metric', 1); + + jest.advanceTimersByTime(10000); + expect(beforeSendMetric).toHaveBeenCalled(); + const sentMetric = beforeSendMetric.mock.calls[0]?.[0]; + expect(sentMetric).toBeDefined(); + expect(sentMetric.attributes).toBeDefined(); + expect(sentMetric.attributes?.replay_id).toBe(mockReplayId); + }); + + it('metrics do not include replay_id when replay integration is not active', async () => { + (NATIVE.getCurrentReplayId as jest.Mock).mockReturnValue(null); + + const beforeSendMetric = jest.fn(metric => metric); + + const client = new ReactNativeClient({ + ...getDefaultTestClientOptions({ + dsn: EXAMPLE_DSN, + enableMetrics: true, + beforeSendMetric, + }), + }); + + setCurrentClient(client); + client.init(); + + // Send a metric + metrics.count('test_metric', 1); + + jest.advanceTimersByTime(10000); + expect(beforeSendMetric).toHaveBeenCalled(); + const sentMetric = beforeSendMetric.mock.calls[0]?.[0]; + expect(sentMetric).toBeDefined(); + expect(sentMetric.attributes?.replay_id).toBeUndefined(); + }); }); });