From 4d4d9e8126917a3b83aa809ff05ecb05177234b2 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Wed, 5 Feb 2025 20:37:04 -0800 Subject: [PATCH 1/8] Init src code --- .../featureFlags/statsig/index.ts | 1 + .../featureFlags/statsig/integration.ts | 57 +++++++++++++++++++ .../featureFlags/statsig/types.ts | 18 ++++++ 3 files changed, 76 insertions(+) create mode 100644 packages/browser/src/integrations/featureFlags/statsig/index.ts create mode 100644 packages/browser/src/integrations/featureFlags/statsig/integration.ts create mode 100644 packages/browser/src/integrations/featureFlags/statsig/types.ts diff --git a/packages/browser/src/integrations/featureFlags/statsig/index.ts b/packages/browser/src/integrations/featureFlags/statsig/index.ts new file mode 100644 index 000000000000..a208e4c7dc0e --- /dev/null +++ b/packages/browser/src/integrations/featureFlags/statsig/index.ts @@ -0,0 +1 @@ +export { statsigIntegration } from './integration'; diff --git a/packages/browser/src/integrations/featureFlags/statsig/integration.ts b/packages/browser/src/integrations/featureFlags/statsig/integration.ts new file mode 100644 index 000000000000..ac61ae6883c1 --- /dev/null +++ b/packages/browser/src/integrations/featureFlags/statsig/integration.ts @@ -0,0 +1,57 @@ +import type { Client, Event, EventHint, IntegrationFn } from '@sentry/core'; + +import { defineIntegration, logger } from '@sentry/core'; +import { DEBUG_BUILD } from '../../../debug-build'; +import { copyFlagsFromScopeToEvent, insertFlagToScope } from '../../../utils/featureFlags'; +import type { StatsigClient, FeatureGate } from './types'; + +/** + * Sentry integration for capturing feature flag evaluations from the Statsig js-client SDK. + * + * See the [feature flag documentation](https://develop.sentry.dev/sdk/expected-features/#feature-flags) for more information. + * + * @example + * ``` + * import { StatsigClient } from ''; + * import * as Sentry from '@sentry/browser'; + * + * const statsigClient = new StatsigClient(); + * + * Sentry.init({ + * dsn: '___PUBLIC_DSN___', + * integrations: [Sentry.statsigIntegration({statsigClient})], + * }); + * + * await statsigClient.initializeAsync(); // or statsigClient.initializeSync(); + * + * const result = statsigClient.checkGate('my-feature-gate'); + * Sentry.captureException(new Error('something went wrong')); + * ``` + */ +export const statsigIntegration = defineIntegration( + ({ statsigClient }: { statsigClient: StatsigClient }) => { + return { + name: 'Statsig', + + processEvent(event: Event, _hint: EventHint, _client: Client): Event { + return copyFlagsFromScopeToEvent(event); + }, + + setupOnce() { + statsigClient.on('gate_evaluation', (event: { gate: FeatureGate }) => { + try { + insertFlagToScope(event.gate.name, event.gate.value); + } catch (error) { + if (!(error instanceof TypeError)) { + throw error; + } + + if (DEBUG_BUILD) { + logger.error(`[Feature Flags] Error reading Statsig gate evaluation: ${error.message}`); + } + } + }); + }, + }; + }, +) satisfies IntegrationFn; diff --git a/packages/browser/src/integrations/featureFlags/statsig/types.ts b/packages/browser/src/integrations/featureFlags/statsig/types.ts new file mode 100644 index 000000000000..2ffe671f818b --- /dev/null +++ b/packages/browser/src/integrations/featureFlags/statsig/types.ts @@ -0,0 +1,18 @@ +export type FeatureGate = { + readonly name: string; + readonly value: boolean; + // readonly ruleID: string; + // readonly details: EvaluationDetails; + // readonly __evaluation: GateEvaluation | null; +}; + +type EventNameToEventDataMap = { + gate_evaluation: { gate: FeatureGate }; +}; + +export interface StatsigClient { + on( + event: keyof EventNameToEventDataMap, + callback: (data: EventNameToEventDataMap[keyof EventNameToEventDataMap]) => void + ): void; +} From dde56fbdfd67a981bc8fa49c29b527a3a9acee7a Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Wed, 5 Feb 2025 21:15:31 -0800 Subject: [PATCH 2/8] Add tests and remove type error case --- .../featureFlags/statsig/basic/test.ts | 47 ++++++++++++++ .../integrations/featureFlags/statsig/init.js | 30 +++++++++ .../featureFlags/statsig/subject.js | 3 + .../featureFlags/statsig/template.html | 9 +++ .../featureFlags/statsig/withScope/test.ts | 65 +++++++++++++++++++ packages/browser/src/index.ts | 1 + .../featureFlags/statsig/integration.ts | 17 +---- 7 files changed, 158 insertions(+), 14 deletions(-) create mode 100644 dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/basic/test.ts create mode 100644 dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/init.js create mode 100644 dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/template.html create mode 100644 dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/withScope/test.ts diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/basic/test.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/basic/test.ts new file mode 100644 index 000000000000..f58db487b111 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/basic/test.ts @@ -0,0 +1,47 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../../../utils/fixtures'; + +import { envelopeRequestParser, shouldSkipFeatureFlagsTest, waitForErrorRequest } from '../../../../../utils/helpers'; + +const FLAG_BUFFER_SIZE = 100; // Corresponds to constant in featureFlags.ts, in browser utils. + +sentryTest('Basic test with eviction, update, and no async tasks', async ({ getLocalTestUrl, page }) => { + if (shouldSkipFeatureFlagsTest()) { + sentryTest.skip(); + } + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestUrl({ testDir: __dirname, skipDsnRouteHandler: true }); + await page.goto(url); + + await page.evaluate(bufferSize => { + const client = (window as any).statsigClient; + for (let i = 1; i <= bufferSize; i++) { + client.checkGate(`feat${i}`, false); + } + client.checkGate(`feat${bufferSize + 1}`, true); // eviction + client.checkGate('feat3', true); // update + }, FLAG_BUFFER_SIZE); + + const reqPromise = waitForErrorRequest(page); + await page.locator('#error').click(); + const req = await reqPromise; + const event = envelopeRequestParser(req); + + const expectedFlags = [{ flag: 'feat2', result: false }]; + for (let i = 4; i <= FLAG_BUFFER_SIZE; i++) { + expectedFlags.push({ flag: `feat${i}`, result: false }); + } + expectedFlags.push({ flag: `feat${FLAG_BUFFER_SIZE + 1}`, result: true }); + expectedFlags.push({ flag: 'feat3', result: true }); + + expect(event.contexts?.flags?.values).toEqual(expectedFlags); +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/init.js b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/init.js new file mode 100644 index 000000000000..4a19173e9bb6 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/init.js @@ -0,0 +1,30 @@ +import * as Sentry from '@sentry/browser'; + +class MockStatsigClient { + constructor() { + this._gateEvaluationListeners = []; + } + + on(event, listener) { + this._gateEvaluationListeners.push(listener); + } + + checkGate(name, defaultVal) { + // Note the actual StatsigClient.checkGate does not take a defaultVal. + this._gateEvaluationListeners.forEach(listener => { + listener({ gate: { name, value: defaultVal } }); + }); + return defaultVal; + } +} + +window.statsigClient = new MockStatsigClient(); + +window.Sentry = Sentry; +window.sentryStatsigIntegration = Sentry.statsigIntegration({ statsigClient: window.statsigClient }); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + sampleRate: 1.0, + integrations: [window.sentryStatsigIntegration], +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/subject.js b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/subject.js new file mode 100644 index 000000000000..e6697408128c --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/subject.js @@ -0,0 +1,3 @@ +document.getElementById('error').addEventListener('click', () => { + throw new Error('Button triggered error'); +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/template.html b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/template.html new file mode 100644 index 000000000000..9330c6c679f4 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/template.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/withScope/test.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/withScope/test.ts new file mode 100644 index 000000000000..1131b03ce92b --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/withScope/test.ts @@ -0,0 +1,65 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../../../utils/fixtures'; + +import { envelopeRequestParser, shouldSkipFeatureFlagsTest, waitForErrorRequest } from '../../../../../utils/helpers'; + +import type { Scope } from '@sentry/browser'; + +sentryTest('Flag evaluations in forked scopes are stored separately.', async ({ getLocalTestUrl, page }) => { + if (shouldSkipFeatureFlagsTest()) { + sentryTest.skip(); + } + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestUrl({ testDir: __dirname, skipDsnRouteHandler: true }); + await page.goto(url); + + const forkedReqPromise = waitForErrorRequest(page, event => !!event.tags?.isForked === true); + const mainReqPromise = waitForErrorRequest(page, event => !!event.tags?.isForked === false); + + await page.evaluate(() => { + const Sentry = (window as any).Sentry; + const errorButton = document.querySelector('#error') as HTMLButtonElement; + const client = (window as any).statsigClient; + + client.checkGate('shared', true); + + Sentry.withScope((scope: Scope) => { + client.checkGate('forked', true); + client.checkGate('shared', false); + scope.setTag('isForked', true); + if (errorButton) { + errorButton.click(); + } + }); + + client.checkGate('main', true); + Sentry.getCurrentScope().setTag('isForked', false); + errorButton.click(); + return true; + }); + + const forkedReq = await forkedReqPromise; + const forkedEvent = envelopeRequestParser(forkedReq); + + const mainReq = await mainReqPromise; + const mainEvent = envelopeRequestParser(mainReq); + + expect(forkedEvent.contexts?.flags?.values).toEqual([ + { flag: 'forked', result: true }, + { flag: 'shared', result: false }, + ]); + + expect(mainEvent.contexts?.flags?.values).toEqual([ + { flag: 'shared', result: true }, + { flag: 'main', result: true }, + ]); +}); diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 42c388d73547..2abe7beb55fb 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -68,3 +68,4 @@ export { export { launchDarklyIntegration, buildLaunchDarklyFlagUsedHandler } from './integrations/featureFlags/launchdarkly'; export { openFeatureIntegration, OpenFeatureIntegrationHook } from './integrations/featureFlags/openfeature'; export { unleashIntegration } from './integrations/featureFlags/unleash'; +export { statsigIntegration } from './integrations/featureFlags/statsig'; diff --git a/packages/browser/src/integrations/featureFlags/statsig/integration.ts b/packages/browser/src/integrations/featureFlags/statsig/integration.ts index ac61ae6883c1..f106e2bf32ef 100644 --- a/packages/browser/src/integrations/featureFlags/statsig/integration.ts +++ b/packages/browser/src/integrations/featureFlags/statsig/integration.ts @@ -1,7 +1,6 @@ import type { Client, Event, EventHint, IntegrationFn } from '@sentry/core'; -import { defineIntegration, logger } from '@sentry/core'; -import { DEBUG_BUILD } from '../../../debug-build'; +import { defineIntegration } from '@sentry/core'; import { copyFlagsFromScopeToEvent, insertFlagToScope } from '../../../utils/featureFlags'; import type { StatsigClient, FeatureGate } from './types'; @@ -37,19 +36,9 @@ export const statsigIntegration = defineIntegration( return copyFlagsFromScopeToEvent(event); }, - setupOnce() { + setup() { statsigClient.on('gate_evaluation', (event: { gate: FeatureGate }) => { - try { - insertFlagToScope(event.gate.name, event.gate.value); - } catch (error) { - if (!(error instanceof TypeError)) { - throw error; - } - - if (DEBUG_BUILD) { - logger.error(`[Feature Flags] Error reading Statsig gate evaluation: ${error.message}`); - } - } + insertFlagToScope(event.gate.name, event.gate.value); }); }, }; From 694acfe02ce9c3efdfc1110d5714f6520e3ff033 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Wed, 5 Feb 2025 21:18:24 -0800 Subject: [PATCH 3/8] Biome check --- .../featureFlags/statsig/integration.ts | 30 +++++++++---------- .../featureFlags/statsig/types.ts | 2 +- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/packages/browser/src/integrations/featureFlags/statsig/integration.ts b/packages/browser/src/integrations/featureFlags/statsig/integration.ts index f106e2bf32ef..eaa79b09d646 100644 --- a/packages/browser/src/integrations/featureFlags/statsig/integration.ts +++ b/packages/browser/src/integrations/featureFlags/statsig/integration.ts @@ -2,7 +2,7 @@ import type { Client, Event, EventHint, IntegrationFn } from '@sentry/core'; import { defineIntegration } from '@sentry/core'; import { copyFlagsFromScopeToEvent, insertFlagToScope } from '../../../utils/featureFlags'; -import type { StatsigClient, FeatureGate } from './types'; +import type { FeatureGate, StatsigClient } from './types'; /** * Sentry integration for capturing feature flag evaluations from the Statsig js-client SDK. @@ -27,20 +27,18 @@ import type { StatsigClient, FeatureGate } from './types'; * Sentry.captureException(new Error('something went wrong')); * ``` */ -export const statsigIntegration = defineIntegration( - ({ statsigClient }: { statsigClient: StatsigClient }) => { - return { - name: 'Statsig', +export const statsigIntegration = defineIntegration(({ statsigClient }: { statsigClient: StatsigClient }) => { + return { + name: 'Statsig', - processEvent(event: Event, _hint: EventHint, _client: Client): Event { - return copyFlagsFromScopeToEvent(event); - }, + processEvent(event: Event, _hint: EventHint, _client: Client): Event { + return copyFlagsFromScopeToEvent(event); + }, - setup() { - statsigClient.on('gate_evaluation', (event: { gate: FeatureGate }) => { - insertFlagToScope(event.gate.name, event.gate.value); - }); - }, - }; - }, -) satisfies IntegrationFn; + setup() { + statsigClient.on('gate_evaluation', (event: { gate: FeatureGate }) => { + insertFlagToScope(event.gate.name, event.gate.value); + }); + }, + }; +}) satisfies IntegrationFn; diff --git a/packages/browser/src/integrations/featureFlags/statsig/types.ts b/packages/browser/src/integrations/featureFlags/statsig/types.ts index 2ffe671f818b..397df4457101 100644 --- a/packages/browser/src/integrations/featureFlags/statsig/types.ts +++ b/packages/browser/src/integrations/featureFlags/statsig/types.ts @@ -13,6 +13,6 @@ type EventNameToEventDataMap = { export interface StatsigClient { on( event: keyof EventNameToEventDataMap, - callback: (data: EventNameToEventDataMap[keyof EventNameToEventDataMap]) => void + callback: (data: EventNameToEventDataMap[keyof EventNameToEventDataMap]) => void, ): void; } From 300f2d4204903fdd0fdc5a1c4bc967091958f84a Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Wed, 5 Feb 2025 21:32:22 -0800 Subject: [PATCH 4/8] Rm commented FeatureGate props --- .../browser/src/integrations/featureFlags/statsig/types.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/browser/src/integrations/featureFlags/statsig/types.ts b/packages/browser/src/integrations/featureFlags/statsig/types.ts index 397df4457101..ff0ea820d31f 100644 --- a/packages/browser/src/integrations/featureFlags/statsig/types.ts +++ b/packages/browser/src/integrations/featureFlags/statsig/types.ts @@ -1,9 +1,6 @@ export type FeatureGate = { readonly name: string; readonly value: boolean; - // readonly ruleID: string; - // readonly details: EvaluationDetails; - // readonly __evaluation: GateEvaluation | null; }; type EventNameToEventDataMap = { From 9588fec0e880da642249449df09f76033d30afe0 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Thu, 6 Feb 2025 13:43:48 -0800 Subject: [PATCH 5/8] review comments 1 --- .../integrations/featureFlags/constants.ts | 1 + .../featureFlags/featureFlags/basic/test.ts | 2 +- .../featureFlags/launchdarkly/basic/test.ts | 2 +- .../featureFlags/openfeature/basic/test.ts | 2 +- .../featureFlags/openfeature/errorHook/test.ts | 2 +- .../featureFlags/statsig/basic/test.ts | 12 ++++++++---- .../integrations/featureFlags/statsig/init.js | 13 +++++++++---- .../featureFlags/statsig/withScope/test.ts | 18 +++++++++++------- .../featureFlags/unleash/basic/test.ts | 2 +- .../featureFlags/statsig/integration.ts | 2 +- 10 files changed, 35 insertions(+), 21 deletions(-) create mode 100644 dev-packages/browser-integration-tests/suites/integrations/featureFlags/constants.ts diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/constants.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/constants.ts new file mode 100644 index 000000000000..680105d242e5 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/constants.ts @@ -0,0 +1 @@ +export const FLAG_BUFFER_SIZE = 100; // Corresponds to constant in featureFlags.ts, in browser utils. diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/featureFlags/basic/test.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/featureFlags/basic/test.ts index ed909b19d1fa..fb0eef09b79e 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/featureFlags/basic/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/featureFlags/basic/test.ts @@ -4,7 +4,7 @@ import { sentryTest } from '../../../../../utils/fixtures'; import { envelopeRequestParser, shouldSkipFeatureFlagsTest, waitForErrorRequest } from '../../../../../utils/helpers'; -const FLAG_BUFFER_SIZE = 100; // Corresponds to constant in featureFlags.ts, in browser utils. +import { FLAG_BUFFER_SIZE } from '../../constants' sentryTest('Basic test with eviction, update, and no async tasks', async ({ getLocalTestUrl, page }) => { if (shouldSkipFeatureFlagsTest()) { diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/launchdarkly/basic/test.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/launchdarkly/basic/test.ts index e97cb70761ba..ad610854e5c2 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/launchdarkly/basic/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/launchdarkly/basic/test.ts @@ -4,7 +4,7 @@ import { sentryTest } from '../../../../../utils/fixtures'; import { envelopeRequestParser, shouldSkipFeatureFlagsTest, waitForErrorRequest } from '../../../../../utils/helpers'; -const FLAG_BUFFER_SIZE = 100; // Corresponds to constant in featureFlags.ts, in browser utils. +import { FLAG_BUFFER_SIZE } from '../../constants' sentryTest('Basic test with eviction, update, and no async tasks', async ({ getLocalTestUrl, page }) => { if (shouldSkipFeatureFlagsTest()) { diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/basic/test.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/basic/test.ts index a3de589677ea..cb163c418b44 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/basic/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/basic/test.ts @@ -4,7 +4,7 @@ import { sentryTest } from '../../../../../utils/fixtures'; import { envelopeRequestParser, shouldSkipFeatureFlagsTest, waitForErrorRequest } from '../../../../../utils/helpers'; -const FLAG_BUFFER_SIZE = 100; // Corresponds to constant in featureFlags.ts, in browser utils. +import { FLAG_BUFFER_SIZE } from '../../constants' sentryTest('Basic test with eviction, update, and no async tasks', async ({ getLocalTestUrl, page }) => { if (shouldSkipFeatureFlagsTest()) { diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/errorHook/test.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/errorHook/test.ts index 719782d0b0ab..e9660faec8ca 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/errorHook/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/errorHook/test.ts @@ -4,7 +4,7 @@ import { sentryTest } from '../../../../../utils/fixtures'; import { envelopeRequestParser, shouldSkipFeatureFlagsTest, waitForErrorRequest } from '../../../../../utils/helpers'; -const FLAG_BUFFER_SIZE = 100; // Corresponds to constant in featureFlags.ts, in browser utils. +import { FLAG_BUFFER_SIZE } from '../../constants' sentryTest('Flag evaluation error hook', async ({ getLocalTestUrl, page }) => { if (shouldSkipFeatureFlagsTest()) { diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/basic/test.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/basic/test.ts index f58db487b111..686636b6e300 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/basic/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/basic/test.ts @@ -4,7 +4,7 @@ import { sentryTest } from '../../../../../utils/fixtures'; import { envelopeRequestParser, shouldSkipFeatureFlagsTest, waitForErrorRequest } from '../../../../../utils/helpers'; -const FLAG_BUFFER_SIZE = 100; // Corresponds to constant in featureFlags.ts, in browser utils. +import { FLAG_BUFFER_SIZE } from '../../constants' sentryTest('Basic test with eviction, update, and no async tasks', async ({ getLocalTestUrl, page }) => { if (shouldSkipFeatureFlagsTest()) { @@ -25,10 +25,14 @@ sentryTest('Basic test with eviction, update, and no async tasks', async ({ getL await page.evaluate(bufferSize => { const client = (window as any).statsigClient; for (let i = 1; i <= bufferSize; i++) { - client.checkGate(`feat${i}`, false); + client.checkGate(`feat${i}`); // values default to false } - client.checkGate(`feat${bufferSize + 1}`, true); // eviction - client.checkGate('feat3', true); // update + + client.setMockGateValue(`feat${bufferSize + 1}`, true); + client.checkGate(`feat${bufferSize + 1}`); // eviction + + client.setMockGateValue('feat3', true); + client.checkGate('feat3'); // update }, FLAG_BUFFER_SIZE); const reqPromise = waitForErrorRequest(page); diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/init.js b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/init.js index 4a19173e9bb6..40e1593fc2aa 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/init.js @@ -3,18 +3,23 @@ import * as Sentry from '@sentry/browser'; class MockStatsigClient { constructor() { this._gateEvaluationListeners = []; + this._mockGateValues = {}; } on(event, listener) { this._gateEvaluationListeners.push(listener); } - checkGate(name, defaultVal) { - // Note the actual StatsigClient.checkGate does not take a defaultVal. + checkGate(name) { + const value = this._mockGateValues[name] || false; // unknown features default to false. this._gateEvaluationListeners.forEach(listener => { - listener({ gate: { name, value: defaultVal } }); + listener({ gate: { name, value } }); }); - return defaultVal; + return value; + } + + setMockGateValue(name, value) { + this._mockGateValues[name] = value; } } diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/withScope/test.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/withScope/test.ts index 1131b03ce92b..cf41767036b6 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/withScope/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/withScope/test.ts @@ -30,18 +30,22 @@ sentryTest('Flag evaluations in forked scopes are stored separately.', async ({ const errorButton = document.querySelector('#error') as HTMLButtonElement; const client = (window as any).statsigClient; - client.checkGate('shared', true); + client.setMockGateValue('shared', true); + client.setMockGateValue('main', true); + + client.checkGate('shared'); Sentry.withScope((scope: Scope) => { - client.checkGate('forked', true); - client.checkGate('shared', false); + client.setMockGateValue('forked', true); + client.setMockGateValue('shared', false); // override the value in the parent scope. + + client.checkGate('forked'); + client.checkGate('shared'); scope.setTag('isForked', true); - if (errorButton) { - errorButton.click(); - } + errorButton.click(); }); - client.checkGate('main', true); + client.checkGate('main'); Sentry.getCurrentScope().setTag('isForked', false); errorButton.click(); return true; diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/basic/test.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/basic/test.ts index 5bb72caddd24..26793e8851ab 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/basic/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/basic/test.ts @@ -4,7 +4,7 @@ import { sentryTest } from '../../../../../utils/fixtures'; import { envelopeRequestParser, shouldSkipFeatureFlagsTest, waitForErrorRequest } from '../../../../../utils/helpers'; -const FLAG_BUFFER_SIZE = 100; // Corresponds to constant in featureFlags.ts, in browser utils. +import { FLAG_BUFFER_SIZE } from '../../constants' sentryTest('Basic test with eviction, update, and no async tasks', async ({ getLocalTestUrl, page }) => { if (shouldSkipFeatureFlagsTest()) { diff --git a/packages/browser/src/integrations/featureFlags/statsig/integration.ts b/packages/browser/src/integrations/featureFlags/statsig/integration.ts index eaa79b09d646..3b2f9be2889c 100644 --- a/packages/browser/src/integrations/featureFlags/statsig/integration.ts +++ b/packages/browser/src/integrations/featureFlags/statsig/integration.ts @@ -11,7 +11,7 @@ import type { FeatureGate, StatsigClient } from './types'; * * @example * ``` - * import { StatsigClient } from ''; + * import { StatsigClient } from '@statsig/js-client'; * import * as Sentry from '@sentry/browser'; * * const statsigClient = new StatsigClient(); From fa773bbe01df7b6203ee73e9827ed51bdfec286b Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Thu, 6 Feb 2025 13:50:37 -0800 Subject: [PATCH 6/8] Rename client param --- .../integrations/featureFlags/statsig/init.js | 2 +- .../featureFlags/statsig/integration.ts | 30 ++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/init.js b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/init.js index 40e1593fc2aa..d0125be26cb2 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/init.js @@ -26,7 +26,7 @@ class MockStatsigClient { window.statsigClient = new MockStatsigClient(); window.Sentry = Sentry; -window.sentryStatsigIntegration = Sentry.statsigIntegration({ statsigClient: window.statsigClient }); +window.sentryStatsigIntegration = Sentry.statsigIntegration({ featureFlagClient: window.statsigClient }); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', diff --git a/packages/browser/src/integrations/featureFlags/statsig/integration.ts b/packages/browser/src/integrations/featureFlags/statsig/integration.ts index 3b2f9be2889c..7870f1250032 100644 --- a/packages/browser/src/integrations/featureFlags/statsig/integration.ts +++ b/packages/browser/src/integrations/featureFlags/statsig/integration.ts @@ -18,7 +18,7 @@ import type { FeatureGate, StatsigClient } from './types'; * * Sentry.init({ * dsn: '___PUBLIC_DSN___', - * integrations: [Sentry.statsigIntegration({statsigClient})], + * integrations: [Sentry.statsigIntegration({featureFlagClient: statsigClient})], * }); * * await statsigClient.initializeAsync(); // or statsigClient.initializeSync(); @@ -27,18 +27,20 @@ import type { FeatureGate, StatsigClient } from './types'; * Sentry.captureException(new Error('something went wrong')); * ``` */ -export const statsigIntegration = defineIntegration(({ statsigClient }: { statsigClient: StatsigClient }) => { - return { - name: 'Statsig', +export const statsigIntegration = defineIntegration( + ({ featureFlagClient: statsigClient }: { featureFlagClient: StatsigClient }) => { + return { + name: 'Statsig', - processEvent(event: Event, _hint: EventHint, _client: Client): Event { - return copyFlagsFromScopeToEvent(event); - }, + processEvent(event: Event, _hint: EventHint, _client: Client): Event { + return copyFlagsFromScopeToEvent(event); + }, - setup() { - statsigClient.on('gate_evaluation', (event: { gate: FeatureGate }) => { - insertFlagToScope(event.gate.name, event.gate.value); - }); - }, - }; -}) satisfies IntegrationFn; + setup() { + statsigClient.on('gate_evaluation', (event: { gate: FeatureGate }) => { + insertFlagToScope(event.gate.name, event.gate.value); + }); + }, + }; + }, +) satisfies IntegrationFn; From 0b61d86c280e95dadfa314d94e238e6001cf18b1 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Thu, 6 Feb 2025 13:55:17 -0800 Subject: [PATCH 7/8] Test formatting --- .../suites/integrations/featureFlags/statsig/basic/test.ts | 2 +- .../suites/integrations/featureFlags/statsig/init.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/basic/test.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/basic/test.ts index 686636b6e300..5b484b91ba22 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/basic/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/basic/test.ts @@ -4,7 +4,7 @@ import { sentryTest } from '../../../../../utils/fixtures'; import { envelopeRequestParser, shouldSkipFeatureFlagsTest, waitForErrorRequest } from '../../../../../utils/helpers'; -import { FLAG_BUFFER_SIZE } from '../../constants' +import { FLAG_BUFFER_SIZE } from '../../constants'; sentryTest('Basic test with eviction, update, and no async tasks', async ({ getLocalTestUrl, page }) => { if (shouldSkipFeatureFlagsTest()) { diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/init.js b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/init.js index d0125be26cb2..61d00587f4c6 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/statsig/init.js @@ -11,7 +11,7 @@ class MockStatsigClient { } checkGate(name) { - const value = this._mockGateValues[name] || false; // unknown features default to false. + const value = this._mockGateValues[name] || false; // unknown features default to false. this._gateEvaluationListeners.forEach(listener => { listener({ gate: { name, value } }); }); From 52da69623525fc8585fd367d652c9e7aa519d6df Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:19:40 -0800 Subject: [PATCH 8/8] formatting: semicolon after import --- .../suites/integrations/featureFlags/featureFlags/basic/test.ts | 2 +- .../suites/integrations/featureFlags/launchdarkly/basic/test.ts | 2 +- .../suites/integrations/featureFlags/openfeature/basic/test.ts | 2 +- .../integrations/featureFlags/openfeature/errorHook/test.ts | 2 +- .../suites/integrations/featureFlags/unleash/basic/test.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/featureFlags/basic/test.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/featureFlags/basic/test.ts index fb0eef09b79e..5cf63a4914ed 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/featureFlags/basic/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/featureFlags/basic/test.ts @@ -4,7 +4,7 @@ import { sentryTest } from '../../../../../utils/fixtures'; import { envelopeRequestParser, shouldSkipFeatureFlagsTest, waitForErrorRequest } from '../../../../../utils/helpers'; -import { FLAG_BUFFER_SIZE } from '../../constants' +import { FLAG_BUFFER_SIZE } from '../../constants'; sentryTest('Basic test with eviction, update, and no async tasks', async ({ getLocalTestUrl, page }) => { if (shouldSkipFeatureFlagsTest()) { diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/launchdarkly/basic/test.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/launchdarkly/basic/test.ts index ad610854e5c2..d0adafc3d101 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/launchdarkly/basic/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/launchdarkly/basic/test.ts @@ -4,7 +4,7 @@ import { sentryTest } from '../../../../../utils/fixtures'; import { envelopeRequestParser, shouldSkipFeatureFlagsTest, waitForErrorRequest } from '../../../../../utils/helpers'; -import { FLAG_BUFFER_SIZE } from '../../constants' +import { FLAG_BUFFER_SIZE } from '../../constants'; sentryTest('Basic test with eviction, update, and no async tasks', async ({ getLocalTestUrl, page }) => { if (shouldSkipFeatureFlagsTest()) { diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/basic/test.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/basic/test.ts index cb163c418b44..21db2c0b7d85 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/basic/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/basic/test.ts @@ -4,7 +4,7 @@ import { sentryTest } from '../../../../../utils/fixtures'; import { envelopeRequestParser, shouldSkipFeatureFlagsTest, waitForErrorRequest } from '../../../../../utils/helpers'; -import { FLAG_BUFFER_SIZE } from '../../constants' +import { FLAG_BUFFER_SIZE } from '../../constants'; sentryTest('Basic test with eviction, update, and no async tasks', async ({ getLocalTestUrl, page }) => { if (shouldSkipFeatureFlagsTest()) { diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/errorHook/test.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/errorHook/test.ts index e9660faec8ca..bae111a5e6f5 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/errorHook/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/errorHook/test.ts @@ -4,7 +4,7 @@ import { sentryTest } from '../../../../../utils/fixtures'; import { envelopeRequestParser, shouldSkipFeatureFlagsTest, waitForErrorRequest } from '../../../../../utils/helpers'; -import { FLAG_BUFFER_SIZE } from '../../constants' +import { FLAG_BUFFER_SIZE } from '../../constants'; sentryTest('Flag evaluation error hook', async ({ getLocalTestUrl, page }) => { if (shouldSkipFeatureFlagsTest()) { diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/basic/test.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/basic/test.ts index 26793e8851ab..7e8065dc6172 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/basic/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/basic/test.ts @@ -4,7 +4,7 @@ import { sentryTest } from '../../../../../utils/fixtures'; import { envelopeRequestParser, shouldSkipFeatureFlagsTest, waitForErrorRequest } from '../../../../../utils/helpers'; -import { FLAG_BUFFER_SIZE } from '../../constants' +import { FLAG_BUFFER_SIZE } from '../../constants'; sentryTest('Basic test with eviction, update, and no async tasks', async ({ getLocalTestUrl, page }) => { if (shouldSkipFeatureFlagsTest()) {