diff --git a/src/analytics/index.tsx b/src/analytics/index.tsx index ac1c96e629..7afc7ac763 100644 --- a/src/analytics/index.tsx +++ b/src/analytics/index.tsx @@ -9,6 +9,7 @@ import {Platform} from 'react-native' import {type Result, type WidenPrimitives} from '@growthbook/growthbook-react' import {Logger} from '#/logger' +import {recordFeatureFlagEvaluation} from '#/logger/sentry/featureFlags' import { Features, features as feats, @@ -313,6 +314,7 @@ export function AnalyticsFeaturesContext({ ? sessionMetadataForResult(parentContext, result.experimentResult) : undefined, ) + recordFeatureFlagEvaluation(feature, result.value) }) setAttributes(parentContext.metadata) diff --git a/src/logger/sentry/featureFlags.test.ts b/src/logger/sentry/featureFlags.test.ts new file mode 100644 index 0000000000..30d5b7b226 --- /dev/null +++ b/src/logger/sentry/featureFlags.test.ts @@ -0,0 +1,45 @@ +import {beforeEach, describe, expect, it, jest} from '@jest/globals' + +import { + featureFlagsIntegration, + recordFeatureFlagEvaluation, +} from '#/logger/sentry/featureFlags' + +jest.mock('#/logger/sentry/lib', () => ({ + Sentry: { + featureFlagsIntegration: jest.fn(() => ({ + addFeatureFlag: jest.fn(), + })), + }, +})) + +describe('recordFeatureFlagEvaluation', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + it('records boolean feature flags', () => { + recordFeatureFlagEvaluation('enabled-flag', true) + recordFeatureFlagEvaluation('disabled-flag', false) + + expect(featureFlagsIntegration.addFeatureFlag).toHaveBeenNthCalledWith( + 1, + 'enabled-flag', + true, + ) + expect(featureFlagsIntegration.addFeatureFlag).toHaveBeenNthCalledWith( + 2, + 'disabled-flag', + false, + ) + }) + + it.each([['value'], [1], [['value']], [{value: true}], [null], [undefined]])( + 'ignores unsupported value %p', + value => { + recordFeatureFlagEvaluation('non-boolean-flag', value) + + expect(featureFlagsIntegration.addFeatureFlag).not.toHaveBeenCalled() + }, + ) +}) diff --git a/src/logger/sentry/featureFlags.ts b/src/logger/sentry/featureFlags.ts new file mode 100644 index 0000000000..f48f04a1cf --- /dev/null +++ b/src/logger/sentry/featureFlags.ts @@ -0,0 +1,13 @@ +import {Sentry} from '#/logger/sentry/lib' + +export const featureFlagsIntegration = Sentry.featureFlagsIntegration() + +/** + * Records a feature flag evaluation on Sentry error events and active spans. + * Sentry currently only supports boolean feature flag values. + */ +export function recordFeatureFlagEvaluation(name: string, value: unknown) { + if (typeof value === 'boolean') { + featureFlagsIntegration.addFeatureFlag(name, value) + } +} diff --git a/src/logger/sentry/setup/index.ts b/src/logger/sentry/setup/index.ts index d4607e08a1..aff44d5839 100644 --- a/src/logger/sentry/setup/index.ts +++ b/src/logger/sentry/setup/index.ts @@ -1,5 +1,6 @@ import {getGlobalScope, init} from '@sentry/react-native' +import {featureFlagsIntegration} from '#/logger/sentry/featureFlags' import * as env from '#/env' init({ @@ -10,6 +11,7 @@ init({ environment: env.ENV, dist: env.BUNDLE_IDENTIFIER, release: env.RELEASE_VERSION, + integrations: [featureFlagsIntegration], ignoreErrors: [ /* * Unknown internals errors