Skip to content

feat(flags): send the split serial ID on exposure events (EX-3422) - #1409

Open
danyal002 wants to merge 1 commit into
developfrom
danyal.khan/EX-3422
Open

feat(flags): send the split serial ID on exposure events (EX-3422)#1409
danyal002 wants to merge 1 commit into
developfrom
danyal.khan/EX-3422

Conversation

@danyal002

Copy link
Copy Markdown

What does this PR do?

The SDK now sends the split serial ID on feature flag exposure events.

The serial ID crosses the bridge as a string, because React Native coerces integers to Double. Native parses the string back to an integer and puts it on the exposure event.

File Change
packages/core/src/flags/configuration/precomputed.ts Propagates serialId from a precomputed configuration into the flag cache.
packages/core/src/flags/internal.ts Adds the serialId field to FlagCacheEntry.
packages/core/android/.../DdFlagsImplementation.kt Omits the serialId key when a flag has no serial ID.
packages/core/package.json, yarn.lock Moves @datadog/flagging-core to ~2.1.0.

The decoder gates on type alone. It accepts a finite number. It rejects every other value, including the explicit null that a precomputed assignment sends when a subject has no serial ID. A rejected value omits the key and keeps the flag, so one malformed serial ID never costs a whole flag or its siblings.

Serial IDs are zero-based for each org. 0 is therefore a real and common value, not an absent one. A test pins this.

The Kotlin change removes a shape mismatch. The Android bridge sent an explicit null when a flag had no serial ID, and the JavaScript decoder omitted the key. Both paths now omit the key.

The @datadog/flagging-core bump supplies serialId on the precomputed flag type. The previous ~2.0.1 pin could not resolve 2.1.0.

Motivation

The UFC compiler rewrites a holdout into an ordinary allocation before any SDK reads the flag configuration. An exposure event therefore records no holdout. The split serial ID is the only link back to it. An exposure event without the serial ID cannot be resolved to a holdout.

The native SDKs already carry the value, so this completes the Android path. iOS needs a separate change after the iOS SDK bump to 3.17.0.

Additional Notes

Testing. 89 tests pass in src/flags, of which 14 are new. The new tests cover the string form, serial ID 0, the six rejected input shapes, sibling isolation, and the value arriving at trackEvaluation on both the offline and the online path. One test asserts the absence of the key on the encoded JSON rather than on the object, because a property set to undefined is absent from JSON but present on the object.

Two mutations confirm the tests fail without the change. Deleting the propagation fails 5 tests. Replacing the type gate with a truthiness check fails 6 tests, including the test for serial ID 0. tsc, ESLint, testDebugUnitTest, and detekt all pass.

Risks. The Android online path already sent the serial ID before this PR, because the JavaScript cache stores the native snapshot unchanged. That path now omits the serialId key instead of sending null for a flag with no serial ID. Native reads the key with a null-safe cast, so the exposure event does not change.

The @datadog/flagging-core bump is a minor version of a runtime dependency. It carries three other changes: canonical FFE validation, extended SemVer parsing, and the rules evaluator move. It also removes the transitive @datadog/js-core dependency.

The UFC compiler rewrites a holdout into an ordinary allocation before any
SDK reads the flag configuration, so an exposure event records no holdout.
The split serial ID is the only link back to it.

The native SDKs already carry the value. Android SDK 3.13.1 exposes it on
UnparsedFlag and the bridge forwards it, so the online path already works.
The offline path did not: the precomputed decoder discarded the serial ID
because FlagCacheEntry had no field for it.

Propagate the serial ID through the precomputed decoder, gated on type
alone. A serial ID that is absent or malformed omits the key and keeps the
flag, so a bad value never costs a whole flag or its siblings. Serial IDs
are zero-based per org, so 0 is a real value and is pinned by a test.

The value crosses the bridge as a string because React Native coerces
integers to Double. The Android bridge now omits the key instead of sending
null when a flag has no serial ID, so both paths produce one shape.

@datadog/flagging-core moves to ~2.1.0, which declares serialId on the
precomputed flag type. The previous ~2.0.1 pin could not resolve it.
@danyal002
danyal002 marked this pull request as ready for review September 9, 2026 18:47
@danyal002
danyal002 requested a review from a team as a code owner September 9, 2026 18:47
Copilot AI lite review requested due to automatic review settings September 9, 2026 18:47
@danyal002
danyal002 requested review from a team as code owners September 9, 2026 18:47
@danyal002
danyal002 requested review from btthomas and vjfridge and removed request for a team September 9, 2026 18:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The current serialId validation/stringification can still forward values that won’t safely round-trip to native integer parsing, risking silent loss or mis-encoding of the identifier.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR extends the React Native Flags SDK to propagate the feature-flag split serial ID through the JS flag cache and across the React Native bridge so native exposure tracking can attach it to exposure events.

Changes:

  • Adds serialId?: string to FlagCacheEntry and propagates it from precomputed configurations when valid.
  • Updates Android flag snapshot serialization to omit the serialId key entirely when absent (instead of sending null).
  • Bumps @datadog/flagging-core to ~2.1.0 to consume the precomputed flag type that includes serialId.
File summaries
File Description
yarn.lock Updates lockfile for @datadog/flagging-core@~2.1.0 and removes the prior transitive @datadog/js-core entry.
packages/core/package.json Bumps @datadog/flagging-core dependency to ~2.1.0.
packages/core/src/flags/internal.ts Adds optional serialId field to the cached flag entry shape.
packages/core/src/flags/configuration/precomputed.ts Propagates serialId into the decoded offline cache (stringified for bridge safety).
packages/core/src/flags/configuration/tests/precomputed.test.ts Adds extensive tests for serialId acceptance/rejection and key omission behavior.
packages/core/src/flags/tests/FlagsClient.test.ts Verifies serialId is passed to native tracking (offline + online) and omitted when absent.
packages/core/android/src/main/kotlin/com/datadog/reactnative/DdFlagsImplementation.kt Omits serialId key when absent by conditionally adding it to the map.
Review details
  • Files reviewed: 6/7 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/core/src/flags/configuration/precomputed.ts
Comment thread packages/core/src/flags/configuration/__tests__/precomputed.test.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants