Skip to content

fix: record analytics against the canonical feature name - #428

Open
matthewelwell wants to merge 1 commit into
feat/expose-feature-namefrom
fix/analytics-feature-name-casing
Open

matthewelwell wants to merge 1 commit into
feat/expose-feature-namefrom
fix/analytics-feature-name-casing

Conversation

@matthewelwell

@matthewelwell matthewelwell commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Currently, flag analytics are recorded under the caller's casing rather than the feature name, so getValue('MyFlag') and getValue('myflag') both evaluate correctly in the client, but the analytics are reported as two separate features (because the API matches case sensitively).

Stacked on #427, which adds the name this relies on.

🤖 Generated with Claude Code

@matthewelwell
matthewelwell requested a review from a team as a code owner October 6, 2026 07:32
@matthewelwell
matthewelwell requested review from talissoncosta and removed request for a team October 6, 2026 07:32
},
];

const instances: any[] = [];

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const instances: any[] = [];
const flagsmithInstances: any[] = [];

Can we not be more specific with the typing here too? Something like:

Suggested change
const instances: any[] = [];
const flagsmithInstances: Flagsmith[] = [];

Comment on lines +94 to +104
test('buckets unknown flags under the normalised key rather than the caller casing', async () => {
const { flagsmith, mockFetch } = await initWithMixedCaseFlags({ enableAnalytics: true });
await delay(1);

flagsmith.getValue('Does Not Exist');
flagsmith.getValue('does_not_exist');

// @ts-ignore internal, normally driven by an interval
await flagsmith.analyticsFlags();
expect(postedAnalytics(mockFetch)).toEqual({ does_not_exist: 2 });
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test('buckets unknown flags under the normalised key rather than the caller casing', async () => {
const { flagsmith, mockFetch } = await initWithMixedCaseFlags({ enableAnalytics: true });
await delay(1);
flagsmith.getValue('Does Not Exist');
flagsmith.getValue('does_not_exist');
// @ts-ignore internal, normally driven by an interval
await flagsmith.analyticsFlags();
expect(postedAnalytics(mockFetch)).toEqual({ does_not_exist: 2 });
});

Comment thread flagsmith-core.ts
this.sentryClient.getIntegrationByName(
"FeatureFlags",
)?.addFeatureFlag?.(key, res);
)?.addFeatureFlag?.(flag?.name || normalizeFlagKey(key), res);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description of the PR mentions nothing about Sentry... why are we changing this too?

Comment thread flagsmith-core.ts
Comment on lines +1020 to +1022
// As with evaluateFlag, report the feature's own name so that exposures for the
// same flag aggregate regardless of the casing the caller used.
const key = normalizeFlagKey(featureName);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Zaimwa9 thoughts on this?

@matthewelwell
matthewelwell force-pushed the feat/expose-feature-name branch 2 times, most recently from 44ce0be to fe8068b Compare October 6, 2026 09:42
evaluateFlag bucketed by the caller's raw string rather than the resolved
flag, so getValue('MyFlag') and getValue('myflag') hit the same flag but
reported as two separate features.

The API resolves analytics with a case-sensitive name__in against
Feature.name, so neither spelling resolves unless it matches the feature
name exactly - meaning flags in projects that don't use lower-case names
never register as in use, which drives stale flag detection.

evaluateFlag, the Sentry addFeatureFlag call and trackExposureEvent now
all report the resolved flag's name, falling back to the normalised key
for flags that don't exist so two spellings of a missing flag collapse to
one bucket.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@matthewelwell
matthewelwell force-pushed the fix/analytics-feature-name-casing branch from b8bd879 to a9fc718 Compare October 6, 2026 09:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant