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..706187e0 100644 --- a/Gemfile +++ b/Gemfile @@ -5,6 +5,7 @@ ruby '3.4.10' gem 'addressable' gem 'bootsnap', require: false +gem 'cancancan' gem 'crawler_detect' gem 'graphql' gem 'graphql-client' @@ -23,6 +24,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..44d1a266 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) @@ -104,6 +104,7 @@ GEM bootsnap (1.24.6) msgpack (~> 1.2) builder (3.3.0) + cancancan (3.6.1) capybara (3.40.0) addressable matrix @@ -113,6 +114,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 +144,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 +182,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 +209,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 +225,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 +264,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 +276,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 +304,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 +315,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 +324,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 +356,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 +385,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 +410,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 +455,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 @@ -430,6 +472,7 @@ DEPENDENCIES better_errors binding_of_caller bootsnap + cancancan capybara climate_control crawler_detect @@ -460,6 +503,7 @@ DEPENDENCIES sentry-ruby simplecov simplecov-lcov + split sprockets-rails sqlite3 stimulus-rails @@ -474,4 +518,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/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/beacon_controller.rb b/app/controllers/beacon_controller.rb new file mode 100644 index 00000000..b5f6f770 --- /dev/null +++ b/app/controllers/beacon_controller.rb @@ -0,0 +1,6 @@ +class BeaconController < ApplicationController + def outbound + ab_finished(:result_format) + head :ok + end +end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 58e02d8e..c8dcbc8b 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -13,6 +13,12 @@ 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) + + # Start experimental result format + @fulfillment = ab_test(:result_format, 'link', 'button') + # inject session preference for boolean type if it is present params[:booleanType] = cookies[:boolean_type] || 'AND' diff --git a/app/javascript/application.js b/app/javascript/application.js index 5eef2669..3aa917bf 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -3,6 +3,7 @@ import "@hotwired/turbo-rails" import "controllers" import "loading_spinner" import "matomo_tracking" +import "outbound_beacon" // Show the progress bar after 200 milliseconds, not the default 500 Turbo.config.drive.progressBarDelay = 200; \ No newline at end of file diff --git a/app/javascript/outbound_beacon.js b/app/javascript/outbound_beacon.js new file mode 100644 index 00000000..41de9406 --- /dev/null +++ b/app/javascript/outbound_beacon.js @@ -0,0 +1,15 @@ +const resultsContainer = document.querySelector('.results-list'); + +if (resultsContainer) { + resultsContainer.addEventListener('click', (event) => { + const link = event.target.closest('a'); + + // Discard clicks that aren't on links + if (!link || !resultsContainer.contains(link)) return; + + const data = new FormData(); + data.append('experiment', 'result_format'); + console.log(data); + navigator.sendBeacon('/beacon', data); + }); +} diff --git a/app/models/ability.rb b/app/models/ability.rb new file mode 100644 index 00000000..08b2ba17 --- /dev/null +++ b/app/models/ability.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class Ability + include CanCan::Ability + + def initialize(user) + # Define abilities for the user here. For example: + # + # return unless user.present? + # can :read, :all + # return unless user.admin? + # can :manage, :all + # + # The first argument to `can` is the action you are giving the user + # permission to do. + # If you pass :manage it will apply to every action. Other common actions + # here are :read, :create, :update and :destroy. + # + # The second argument is the resource the user can perform the action on. + # If you pass :all it will apply to every resource. Otherwise pass a Ruby + # class of the resource. + # + # The third argument is an optional hash of conditions to further filter the + # objects. + # For example, here the user can only update published articles. + # + # can :update, Article, published: true + # + # See the wiki for details: + # https://github.com/CanCanCommunity/cancancan/blob/develop/docs/define_check_abilities.md + end +end 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 @@ -