From 62cdc5ef110183b9064967d6a819de5c51d0b47b Mon Sep 17 00:00:00 2001 From: Matthew Bernhardt Date: Mon, 28 Aug 2023 15:13:20 -0400 Subject: [PATCH 1/5] Adds split gem with dashboard and redis A/B testing will be handled by the Split gem. Because this PR will include two different experiments as a demonstration, we also add an initializer to enable that feature. Along the way, we add a line to gitignore because local Redis servers will create a file at project root. We also update the project readme with a bit of information about running Redis locally. --- .gitignore | 3 + Gemfile | 1 + Gemfile.lock | 182 +++++++++++++++++++++-------------- README.md | 4 + config/initializers/split.rb | 3 + config/routes.rb | 2 + 6 files changed, 125 insertions(+), 70 deletions(-) create mode 100644 config/initializers/split.rb diff --git a/.gitignore b/.gitignore index 2b4e6d75..14fef606 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,9 @@ yarn-debug.log* # Ignore master key for decrypting credentials and more. /config/master.key +# Ignore local Redis databases +dump.rdb + .DS_Store .vscode/ .yardoc diff --git a/Gemfile b/Gemfile index 07b42619..9cde0424 100644 --- a/Gemfile +++ b/Gemfile @@ -23,6 +23,7 @@ gem 'redis' gem 'scout_apm' gem 'sentry-rails' gem 'sentry-ruby' +gem 'split', require: 'split/dashboard' gem 'sprockets-rails' gem 'stimulus-rails' gem 'turbo-rails' diff --git a/Gemfile.lock b/Gemfile.lock index 6e964328..d1e26713 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -12,29 +12,29 @@ GEM specs: action_text-trix (2.1.19) railties - actioncable (8.1.3) - actionpack (= 8.1.3) - activesupport (= 8.1.3) + actioncable (8.1.3.1) + actionpack (= 8.1.3.1) + activesupport (= 8.1.3.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (8.1.3) - actionpack (= 8.1.3) - activejob (= 8.1.3) - activerecord (= 8.1.3) - activestorage (= 8.1.3) - activesupport (= 8.1.3) + actionmailbox (8.1.3.1) + actionpack (= 8.1.3.1) + activejob (= 8.1.3.1) + activerecord (= 8.1.3.1) + activestorage (= 8.1.3.1) + activesupport (= 8.1.3.1) mail (>= 2.8.0) - actionmailer (8.1.3) - actionpack (= 8.1.3) - actionview (= 8.1.3) - activejob (= 8.1.3) - activesupport (= 8.1.3) + actionmailer (8.1.3.1) + actionpack (= 8.1.3.1) + actionview (= 8.1.3.1) + activejob (= 8.1.3.1) + activesupport (= 8.1.3.1) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (8.1.3) - actionview (= 8.1.3) - activesupport (= 8.1.3) + actionpack (8.1.3.1) + actionview (= 8.1.3.1) + activesupport (= 8.1.3.1) nokogiri (>= 1.8.5) rack (>= 2.2.4) rack-session (>= 1.0.1) @@ -42,36 +42,36 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (8.1.3) + actiontext (8.1.3.1) action_text-trix (~> 2.1.15) - actionpack (= 8.1.3) - activerecord (= 8.1.3) - activestorage (= 8.1.3) - activesupport (= 8.1.3) + actionpack (= 8.1.3.1) + activerecord (= 8.1.3.1) + activestorage (= 8.1.3.1) + activesupport (= 8.1.3.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (8.1.3) - activesupport (= 8.1.3) + actionview (8.1.3.1) + activesupport (= 8.1.3.1) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (8.1.3) - activesupport (= 8.1.3) + activejob (8.1.3.1) + activesupport (= 8.1.3.1) globalid (>= 0.3.6) - activemodel (8.1.3) - activesupport (= 8.1.3) - activerecord (8.1.3) - activemodel (= 8.1.3) - activesupport (= 8.1.3) + activemodel (8.1.3.1) + activesupport (= 8.1.3.1) + activerecord (8.1.3.1) + activemodel (= 8.1.3.1) + activesupport (= 8.1.3.1) timeout (>= 0.4.0) - activestorage (8.1.3) - actionpack (= 8.1.3) - activejob (= 8.1.3) - activerecord (= 8.1.3) - activesupport (= 8.1.3) + activestorage (8.1.3.1) + actionpack (= 8.1.3.1) + activejob (= 8.1.3.1) + activerecord (= 8.1.3.1) + activesupport (= 8.1.3.1) marcel (~> 1.0) - activesupport (8.1.3) + activesupport (8.1.3.1) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) @@ -113,6 +113,7 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) + cgi (0.5.2) climate_control (1.2.0) concurrent-ruby (1.3.8) connection_pool (3.0.2) @@ -142,13 +143,17 @@ GEM faraday-net_http (3.4.4) net-http (~> 0.5) ffi (1.17.4-aarch64-linux-gnu) + ffi (1.17.4-aarch64-linux-musl) + ffi (1.17.4-arm-linux-gnu) + ffi (1.17.4-arm-linux-musl) ffi (1.17.4-arm64-darwin) ffi (1.17.4-x86_64-darwin) ffi (1.17.4-x86_64-linux-gnu) + ffi (1.17.4-x86_64-linux-musl) fiber-storage (1.0.1) globalid (1.4.0) activesupport (>= 6.1) - graphql (2.6.6) + graphql (2.6.7) base64 fiber-storage logger @@ -176,7 +181,7 @@ GEM jbuilder (2.15.1) actionview (>= 7.0.0) activesupport (>= 7.0.0) - json (2.21.1) + json (2.21.2) language_server-protocol (3.17.0.6) lint_roller (1.1.0) llhttp (0.6.2) @@ -203,10 +208,11 @@ GEM ruby-progressbar mocha (3.1.0) ruby2_keywords (>= 0.0.5) - msgpack (1.8.3) + msgpack (1.8.4) + mustermann (3.1.1) net-http (0.9.1) uri (>= 0.11.1) - net-imap (0.6.4.1) + net-imap (0.6.6) date net-protocol net-pop (0.1.2) @@ -218,21 +224,32 @@ GEM nio4r (2.7.5) nokogiri (1.19.4-aarch64-linux-gnu) racc (~> 1.4) + nokogiri (1.19.4-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.19.4-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.4-arm-linux-musl) + racc (~> 1.4) nokogiri (1.19.4-arm64-darwin) racc (~> 1.4) nokogiri (1.19.4-x86_64-darwin) racc (~> 1.4) nokogiri (1.19.4-x86_64-linux-gnu) racc (~> 1.4) + nokogiri (1.19.4-x86_64-linux-musl) + racc (~> 1.4) openssl (4.0.2) parallel (2.1.0) parser (3.3.12.0) ast (~> 2.4.1) racc + pg (1.6.3) pg (1.6.3-aarch64-linux) + pg (1.6.3-aarch64-linux-musl) pg (1.6.3-arm64-darwin) pg (1.6.3-x86_64-darwin) pg (1.6.3-x86_64-linux) + pg (1.6.3-x86_64-linux-musl) pp (0.6.4) prettyprint prettyprint (0.2.0) @@ -246,6 +263,10 @@ GEM rack (3.2.6) rack-attack (6.8.0) rack (>= 1.0, < 4) + rack-protection (4.2.1) + base64 (>= 0.1.0) + logger (>= 1.6.0) + rack (>= 3.0.0, < 4) rack-session (2.1.2) base64 (>= 0.1.0) rack (>= 3.0.0) @@ -254,20 +275,20 @@ GEM rack-timeout (0.7.0) rackup (2.3.1) rack (>= 3) - rails (8.1.3) - actioncable (= 8.1.3) - actionmailbox (= 8.1.3) - actionmailer (= 8.1.3) - actionpack (= 8.1.3) - actiontext (= 8.1.3) - actionview (= 8.1.3) - activejob (= 8.1.3) - activemodel (= 8.1.3) - activerecord (= 8.1.3) - activestorage (= 8.1.3) - activesupport (= 8.1.3) + rails (8.1.3.1) + actioncable (= 8.1.3.1) + actionmailbox (= 8.1.3.1) + actionmailer (= 8.1.3.1) + actionpack (= 8.1.3.1) + actiontext (= 8.1.3.1) + actionview (= 8.1.3.1) + activejob (= 8.1.3.1) + activemodel (= 8.1.3.1) + activerecord (= 8.1.3.1) + activestorage (= 8.1.3.1) + activesupport (= 8.1.3.1) bundler (>= 1.15.0) - railties (= 8.1.3) + railties (= 8.1.3.1) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest @@ -282,9 +303,9 @@ GEM rack railties (>= 7.2) semantic_logger (>= 5.1) - railties (8.1.3) - actionpack (= 8.1.3) - activesupport (= 8.1.3) + railties (8.1.3.1) + actionpack (= 8.1.3.1) + activesupport (= 8.1.3.1) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) @@ -293,7 +314,7 @@ GEM zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.4.2) - rbs (4.0.3) + rbs (4.1.1) logger prism (>= 1.6.0) tsort @@ -302,8 +323,8 @@ GEM prism (>= 1.6.0) rbs (>= 4.0.0) tsort - redis (5.4.1) - redis-client (>= 0.22.0) + redis (6.0.0) + redis-client (= 0.30.1) redis-client (0.30.1) connection_pool regexp_parser (2.12.0) @@ -334,6 +355,8 @@ GEM rubocop-ast (>= 1.44.0, < 2.0) ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) + rubystats (0.4.1) + matrix rubyzip (3.4.1) sassc (2.4.0) ffi (~> 1.9) @@ -361,8 +384,22 @@ GEM bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) logger - simplecov (1.0.2) + simplecov (1.0.3) simplecov-lcov (0.9.0) + sinatra (4.2.1) + logger (>= 1.6.0) + mustermann (~> 3.0) + rack (>= 3.0.0, < 4) + rack-protection (= 4.2.1) + rack-session (>= 2.0.0, < 3) + tilt (~> 2.0) + split (4.0.5) + bigdecimal + cgi + matrix + redis (>= 4.2) + rubystats (>= 0.3.0) + sinatra (>= 1.2.6) sprockets (4.2.2) concurrent-ruby (~> 1.0) logger @@ -372,9 +409,13 @@ GEM activesupport (>= 6.1) sprockets (>= 3.0.0) sqlite3 (2.9.5-aarch64-linux-gnu) + sqlite3 (2.9.5-aarch64-linux-musl) + sqlite3 (2.9.5-arm-linux-gnu) + sqlite3 (2.9.5-arm-linux-musl) sqlite3 (2.9.5-arm64-darwin) sqlite3 (2.9.5-x86_64-darwin) sqlite3 (2.9.5-x86_64-linux-gnu) + sqlite3 (2.9.5-x86_64-linux-musl) stimulus-rails (1.3.4) railties (>= 6.0.0) strscan (3.1.8) @@ -413,15 +454,15 @@ GEM PLATFORMS aarch64-linux - arm64-darwin-22 - arm64-darwin-23 - arm64-darwin-24 - arm64-darwin-25 - x86_64-darwin-19 - x86_64-darwin-20 - x86_64-darwin-21 - x86_64-darwin-22 + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin x86_64-linux + x86_64-linux-gnu + x86_64-linux-musl DEPENDENCIES addressable @@ -460,6 +501,7 @@ DEPENDENCIES sentry-ruby simplecov simplecov-lcov + split sprockets-rails sqlite3 stimulus-rails @@ -474,4 +516,4 @@ RUBY VERSION ruby 3.4.10p104 BUNDLED WITH - 2.6.9 + 2.7.2 diff --git a/README.md b/README.md index 8c66daf2..59768045 100644 --- a/README.md +++ b/README.md @@ -210,3 +210,7 @@ If you prefer an editor other than VSCode, you can manage [Dev Containers from t [DevPod](https://github.com/loft-sh/devpod) is also something to consider. It provides a VScode-via-web-browser-in-a-box as well as allowing you to use whatever editor you want and only using DevPod to start/stop the containers and run your terminals. Local for editor, DevPod managed Dev Container for everything else. + +### A/B testing, the Split gem, and Redis + +This application has the capability of supporting A/B testing of new features using the [Split](https://github.com/splitrb/split) gem. When running locally, this means you'll need access to a Redis instance - which can be achieved on a Mac by Homebrew. Full details can be found in the Split documentation. diff --git a/config/initializers/split.rb b/config/initializers/split.rb new file mode 100644 index 00000000..bc1f7cab --- /dev/null +++ b/config/initializers/split.rb @@ -0,0 +1,3 @@ +Split.configure do |config| + config.allow_multiple_experiments = true +end diff --git a/config/routes.rb b/config/routes.rb index 9a7ee757..e3bff404 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -26,5 +26,7 @@ get 'boolpref', to: 'static#boolpref' get 'natural_language_search_optin', to: 'static#natural_language_search_optin' + mount Split::Dashboard, at: 'split' + get 'robots.txt', to: 'robots#robots' end From 3def803adf746ef1f0b7acba5e5b5eb9e1881d72 Mon Sep 17 00:00:00 2001 From: Matthew Bernhardt Date: Mon, 28 Aug 2023 15:20:06 -0400 Subject: [PATCH 2/5] Implement a first experiment This sets up a simple UI experiment, defining an alternative set of styles for the search form. The search form starts in the _form.html.erb partial, with most users getting a "standard" class - the experimental group would get the "alt" class, which is rendered differently. The experiment ends when a user reaches a search results page (i.e, they submitted the form and thus got results). Curious folks can force themselves into one or the other pool by using a querystring such as ?ab_test[ui_colors]=alt or ?ab_test[ui_colors]=standard Refactor UI experiment based on recent changes Fixup search controller Updated first experiment --- app/assets/stylesheets/partials/_search.scss | 14 +++++++++++++- app/controllers/search_controller.rb | 3 +++ app/views/search/_form.html.erb | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/partials/_search.scss b/app/assets/stylesheets/partials/_search.scss index 78ba3509..6fe0d27a 100644 --- a/app/assets/stylesheets/partials/_search.scss +++ b/app/assets/stylesheets/partials/_search.scss @@ -110,9 +110,21 @@ } +/* hypothetical UI experiment */ +#search-form { + + &.alt { + margin: -1rem; + padding: 1rem; + padding-bottom: 1.6rem; + border: 2px solid black; + border-color: yellow; + background-color: rgba(255, 255, 0, 0.25); + } +} + /* basic search bar */ .search-form { - background-color: #989898; margin-bottom: 0rem; padding: 2.4rem 2rem 1.6rem 2rem; diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 58e02d8e..737db58e 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -13,6 +13,9 @@ class SearchController < ApplicationController before_action :validate_geodistance_units!, only: %i[results] def results + # if we are loading results, the user submitted the form - so this experiment is finished + ab_finished(:ui_colors) + # inject session preference for boolean type if it is present params[:booleanType] = cookies[:boolean_type] || 'AND' diff --git a/app/views/search/_form.html.erb b/app/views/search/_form.html.erb index 3c7115af..a805211d 100644 --- a/app/views/search/_form.html.erb +++ b/app/views/search/_form.html.erb @@ -1,4 +1,4 @@ -