Skip to content

feature: Add experimental feature opt-ins to profile settings#1779

Draft
joshsadam wants to merge 30 commits intomainfrom
feat/flipper-user-opt-ins
Draft

feature: Add experimental feature opt-ins to profile settings#1779
joshsadam wants to merge 30 commits intomainfrom
feat/flipper-user-opt-ins

Conversation

@joshsadam
Copy link
Copy Markdown
Contributor

@joshsadam joshsadam commented Apr 16, 2026

What does this PR do and why?

This branch adds a Profile > Experimental Features page so users can opt in and out of enabled experimental features themselves.

It includes:

  • profile routing, controller actions, views, and Turbo Stream updates for feature toggles;
  • a Stimulus toggle flow that keeps keyboard focus on the updated control after each change;
  • config-driven, localized feature names and descriptions;
  • service-level enforcement for restricted-feature allowlists and stricter opt-in validation;
  • persistence of user opt-ins in ApplicationSetting via user_opt_in_features;
  • expanded coverage across controller, service, model, and system tests.

How to set using the Rails Console:

app = ApplicationSetting.current || ApplicationSetting.create_from_defaults
patch = {
  "data_grid_samples_table" => {
    "name" => { "en" => "New EN Name" }
  }
}
merged = (app.user_opt_in_features || {}).deep_merge(patch)
app.update!(user_opt_in_features: merged)
app.reload.user_opt_in_features

OR a new feature flag:

bin/rails c
app_setting = ApplicationSetting.current || ApplicationSetting.create_from_defaults

default_opt_in_features = {
  "client_linelist_exports_v1" => {
    "allowlist" => ["user1@email.com"],
    "name" => {
      "en" => "Client Side Linelist Export",
      "fr" => "Tableau de données des échantillons"
    },
    "description" => {
      "en" => "Enable the new client side data export.",
      "fr" => "Activer la nouvelle grille de données pour le tableau d'échantillons."
    }
  }
}

app_setting.update!(
  user_opt_in_features: default_opt_in_features.merge(app_setting.user_opt_in_features || {})
)
app.reload.user_opt_in_features

Screenshots or screen recordings

If there are experimental features to toggle
image

If there are not experimental features to toggle
image

How to set up and validate locally

  1. Run bin/setup --reset to refresh DB + seeds.
  2. Start the app.
  3. Sign in as user1@email.com and open Profile > Experimental Features.
  4. Confirm each visible feature shows the expected localized name/description.
  5. Toggle a feature on and off, and confirm row-level Turbo updates (no full page reload).
  6. Confirm keyboard focus returns to the updated toggle after each change.
  7. Verify allowlisted users can opt in to restricted features and non-allowlisted users cannot.
  8. Run:
    PARALLEL_WORKERS=4 bundle exec rails test test/controllers/profiles/experimental_features_controller_test.rb
  9. Run:
    PARALLEL_WORKERS=4 bundle exec rails test test/services/profiles/experimental_features/opt_in_service_test.rb
  10. Run:
    PARALLEL_WORKERS=4 bundle exec rails test test/models/application_setting_test.rb
  11. Run:
    PARALLEL_WORKERS=4 bundle exec rails test test/system/profile_test.rb -n "/experimental features with keyboard and keep focus/"

PR acceptance checklist

@joshsadam joshsadam self-assigned this Apr 16, 2026
Comment thread test/controllers/profiles/experimental_features_controller_test.rb Fixed
Comment thread test/services/profiles/experimental_features/opt_in_service_test.rb Fixed
@ericenns ericenns requested review from ksierks April 21, 2026 12:28
Comment thread test/controllers/profiles/experimental_features_controller_test.rb Outdated
Comment thread test/services/profiles/experimental_features/opt_in_service_test.rb Outdated
@joshsadam joshsadam force-pushed the feat/flipper-user-opt-ins branch from 627f0e0 to 9112e1c Compare April 21, 2026 16:11
Comment thread db/seeds.rb
Copy link
Copy Markdown
Contributor

@deepsidhu85 deepsidhu85 left a comment

Choose a reason for hiding this comment

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

Just a quick thing after a quick glance below. Also I think we should split this PR up into smaller pieces

# frozen_string_literal: true

# Purpose: To handle user opt-in to experimental features via Flipper actor gates
module Profiles
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.

I think we should refactor most of the code in the controller into using a service such Profiles::ExperimentalFeatures::UpdateService. Controller should just be making a call to the service and returning a result, just to keep the controller light

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.

Updated in 0fb966b.

joshsadam and others added 20 commits April 24, 2026 06:31
- Create config/user_opt_in_features.yml with empty allowlist (separate from features.yml per D-05)
- Load USER_OPT_IN_FEATURE_CONFIG constant at boot in flipper.rb initializer
- Add resource :experimental_features route (GET show + PATCH update) under profiles scope
- Implement Profiles::ExperimentalFeaturesController with authorization, Flipper actor
  enable/disable, allowlist security guard, and actors_value-based display state
- Insert Experimental Features sidebar nav item (flask_conical icon) between Preferences and Account
- Add complete en.yml translations: page title, description, empty state, update feedback, toggle aria-label
- Add fr.yml stub translations marked with TODO: translator review
…tal features

- show.html.erb: PageHeaderComponent + viral_card with flat feature list and empty state
- _feature_row.html.erb: name/description + immediate-save toggle form with sr-only checkbox, toggle pill, accessible status div (role=alert, aria-live=polite)
- update.turbo_stream.erb: silent success, inline error targeting per-row status div
- experimental_feature_toggle_controller.js: requestSubmit() on checkbox change (Turbo-compatible)
- config/user_opt_in_features.yml: add data_grid_samples_table with allowlist: all
- config/locales/en.yml + fr.yml: add feature translations for data_grid_samples_table
- experimental_features_controller_test.rb: 8 tests covering show, empty state, toggle enable/disable, security guard, unauthenticated redirect, HTML fallback redirect
- _feature_row.html.erb: fix W3C violations (aria-label moved to checkbox, div-in-label replaced with span)
- All 29 profile controller tests pass with 0 failures
…tions and improve French translation

Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
…al variable'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
@joshsadam joshsadam marked this pull request as draft April 24, 2026 11:35
@joshsadam joshsadam force-pushed the feat/flipper-user-opt-ins branch from 28edda0 to 5ab6012 Compare April 24, 2026 11:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants