Skip to content

feat(multivariate): add per-feature unique key to multivariate options#7698

Open
gagantrivedi wants to merge 1 commit into
mainfrom
feat/multivariate-option-key
Open

feat(multivariate): add per-feature unique key to multivariate options#7698
gagantrivedi wants to merge 1 commit into
mainfrom
feat/multivariate-option-key

Conversation

@gagantrivedi
Copy link
Copy Markdown
Member

@gagantrivedi gagantrivedi commented Jun 3, 2026

WIP / draft. First backend slice of "give every multivariate value a stable key so we know which variant a user was exposed to".

  • I have read the Contributing Guide.
  • I have added information to docs/ if required so people know about the feature. (deferred — the key isn't yet surfaced to SDKs/UI; docs will land with the SDK/experimentation slices.)
  • I have filled in the "Changes" section below.
  • I have filled in the "How did you test this code" section below.

Changes

Contributes to

Adds a nullable, per-feature-unique key to MultivariateFeatureOption, giving each variant a stable, human-readable identifier (e.g. control, variant_a). This is the foundation for attributing experimentation exposure/metrics to the specific variant a user was shown.

  • Model (features/multivariate/models.py): nullable key CharField (max_length=2000, mirroring Feature.name) with a ("feature", "key") unique_together. Postgres treats NULL as distinct, so the key stays optional now while non-null keys are unique per feature — no not-null enforcement, no backfill.
  • Migration (0009): single additive migration (field + constraint), zero data migration.
  • Serializer (features/multivariate/serializers.py): exposes key. The (feature, key) unique_together makes DRF both attach a UniqueTogetherValidator and mark the field required=True — either of which would break the existing no-key create flow — so the auto-validator and required are overridden, and uniqueness is enforced manually to return a clean 400 (the DB constraint remains the final guard).

Out of scope (follow-up slices): threading key through the flag engine + environment document, the SDK flag-response variant_key, experimentation consuming key for per-variant attribution, and the frontend variation-key input/display.

How did you test this code?

Automated tests, written TDD-first (Given/When/Then, both auth types via admin_client_new):

  • Model (tests/unit/.../test_unit_multivariate_models.py): key defaults to None; duplicate non-null key on the same feature → IntegrityError; multiple null keys on the same feature allowed; same key across different features allowed.
  • API (tests/integration/.../test_integration_multivariate.py): create with key round-trips the value; duplicate key → 400 with a clear message.

Verification run locally:

  • New multivariate suite: 44 passed.
  • Regression sweep (feature serializers, SDK environment document, engine/sdk/dynamo mappers, edge identities, import/export): 412 passed, 2 skipped.
  • ruff check, ruff format --check, and mypy (strict, incl. tests): clean.

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

3 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs Ignored Ignored Preview Jun 3, 2026 10:45am
flagsmith-frontend-preview Ignored Ignored Preview Jun 3, 2026 10:45am
flagsmith-frontend-staging Ignored Ignored Preview Jun 3, 2026 10:45am

Request Review

@github-actions github-actions Bot added api Issue related to the REST API feature New feature or request labels Jun 3, 2026
@gagantrivedi gagantrivedi force-pushed the feat/multivariate-option-key branch from d7fb119 to c2adfb4 Compare June 3, 2026 10:38
@gagantrivedi gagantrivedi changed the base branch from feat/experiment-metrics to main June 3, 2026 10:38
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request labels Jun 3, 2026
Add a nullable, per-feature-unique `key` to MultivariateFeatureOption so
each variant carries a stable, human-readable identifier. This is the first
slice towards attributing experimentation exposure data to the specific
variant a user was shown.

- Model: nullable `key` CharField with a (feature, key) unique_together
  constraint. NULLs remain distinct in Postgres, so the key stays optional
  while non-null keys are unique per feature.
- Serializer: expose `key`, and enforce uniqueness manually to return a clean
  400 (the auto-generated UniqueTogetherValidator would otherwise force `key`
  to be required on create).
@gagantrivedi gagantrivedi force-pushed the feat/multivariate-option-key branch from c2adfb4 to df85009 Compare June 3, 2026 10:44
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request labels Jun 3, 2026
@gagantrivedi gagantrivedi marked this pull request as ready for review June 3, 2026 10:46
@gagantrivedi gagantrivedi requested a review from a team as a code owner June 3, 2026 10:46
@gagantrivedi gagantrivedi requested review from khvn26 and removed request for a team June 3, 2026 10:46
@gagantrivedi gagantrivedi changed the title feat(multivariate): add per-feature unique key to multivariate options [WIP] feat(multivariate): add per-feature unique key to multivariate options Jun 3, 2026
@gagantrivedi gagantrivedi requested a review from Zaimwa9 June 3, 2026 10:46
@gagantrivedi gagantrivedi removed the request for review from khvn26 June 3, 2026 10:46
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request labels Jun 3, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 3, 2026

Docker builds report

Image Build Status Security report
ghcr.io/flagsmith/flagsmith-e2e:pr-7698 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-api-test:pr-7698 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-frontend:pr-7698 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-api:pr-7698 Finished ✅ Results
ghcr.io/flagsmith/flagsmith:pr-7698 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-private-cloud:pr-7698 Finished ✅ Results

@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 3, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.52%. Comparing base (1946ec5) to head (df85009).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #7698   +/-   ##
=======================================
  Coverage   98.52%   98.52%           
=======================================
  Files        1444     1445    +1     
  Lines       54971    55021   +50     
=======================================
+ Hits        54161    54211   +50     
  Misses        810      810           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 3, 2026

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  34.2 seconds
commit  df85009
info  🔄 Run: #17225 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  42.7 seconds
commit  df85009
info  🔄 Run: #17225 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  1 minute, 4 seconds
commit  df85009
info  🔄 Run: #17225 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

failed  1 failed
passed  1 passed

Details

stats  2 tests across 2 suites
duration  54.2 seconds
commit  df85009
info  📦 Artifacts: View test results and HTML report
🔄 Run: #17225 (attempt 1)

Failed tests

firefox › tests/environment-permission-test.pw.ts › Environment Permission Tests › Environment-level permissions control access to features, identities, and segments @enterprise

### Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  3 passed

Details

stats  3 tests across 3 suites
duration  34 seconds
commit  df85009
info  🔄 Run: #17225 (attempt 2)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 3, 2026

Visual Regression

19 screenshots compared. See report for details.
View full report

Copy link
Copy Markdown
Contributor

@Zaimwa9 Zaimwa9 left a comment

Choose a reason for hiding this comment

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

Flagged 2 possible loopholes but otherwise I haven't identified other leaks into existing MV features.
It's mostly about reviewing serializers using the NestedMultivariateFeatureOptionSerializer and an alignment on empty string values.
It would be nice to also add tests on updating keys in MV options and then maybe add some coverage around CreateFeatureSerializer to sleep at peace?

)

# A stable, human-readable identifier for the variant.
key = models.CharField(max_length=2000, null=True, blank=True)
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.

NIT: 2000 seems like a lot

# Then
assert (
MultivariateFeatureOption.objects.filter(
feature=feature, key__isnull=True
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.

This one is only testing null and not "" which would trigger the constraint. Do we want to allow users to use "" and make them unique or normalize them into null ?

"string_value",
"boolean_value",
"default_percentage_allocation",
"key",
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.

So, this serializer is also used in CreateFeatureSerializer, that would make key writable there too right.

Just for context, the frontend is only creating MV options via the mv-options dedicated endpoint but I believe (not tested it though) that it would make it writable in the feature endpoint too without validation.

We should make it read_only here and writable only in the MultivariateFeatureOptionSerializer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Issue related to the REST API feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants