diff options
Diffstat (limited to 'lib/gitlab/experimentation')
-rw-r--r-- | lib/gitlab/experimentation/controller_concern.rb | 127 | ||||
-rw-r--r-- | lib/gitlab/experimentation/group_types.rb | 10 |
2 files changed, 137 insertions, 0 deletions
diff --git a/lib/gitlab/experimentation/controller_concern.rb b/lib/gitlab/experimentation/controller_concern.rb new file mode 100644 index 00000000000..c6d15d7d82d --- /dev/null +++ b/lib/gitlab/experimentation/controller_concern.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +require 'zlib' + +# Controller concern that checks if an `experimentation_subject_id cookie` is present and sets it if absent. +# Used for A/B testing of experimental features. Exposes the `experiment_enabled?(experiment_name)` method +# to controllers and views. It returns true when the experiment is enabled and the user is selected as part +# of the experimental group. +# +module Gitlab + module Experimentation + module ControllerConcern + include ::Gitlab::Experimentation::GroupTypes + extend ActiveSupport::Concern + + included do + before_action :set_experimentation_subject_id_cookie, unless: :dnt_enabled? + helper_method :experiment_enabled?, :experiment_tracking_category_and_group + end + + def set_experimentation_subject_id_cookie + return if cookies[:experimentation_subject_id].present? + + cookies.permanent.signed[:experimentation_subject_id] = { + value: SecureRandom.uuid, + secure: ::Gitlab.config.gitlab.https, + httponly: true + } + end + + def push_frontend_experiment(experiment_key) + var_name = experiment_key.to_s.camelize(:lower) + enabled = experiment_enabled?(experiment_key) + + gon.push({ experiments: { var_name => enabled } }, true) + end + + def experiment_enabled?(experiment_key) + return false if dnt_enabled? + + return true if Experimentation.enabled_for_value?(experiment_key, experimentation_subject_index(experiment_key)) + return true if forced_enabled?(experiment_key) + + false + end + + def track_experiment_event(experiment_key, action, value = nil) + return if dnt_enabled? + + track_experiment_event_for(experiment_key, action, value) do |tracking_data| + ::Gitlab::Tracking.event(tracking_data.delete(:category), tracking_data.delete(:action), **tracking_data) + end + end + + def frontend_experimentation_tracking_data(experiment_key, action, value = nil) + return if dnt_enabled? + + track_experiment_event_for(experiment_key, action, value) do |tracking_data| + gon.push(tracking_data: tracking_data) + end + end + + def record_experiment_user(experiment_key) + return if dnt_enabled? + return unless Experimentation.enabled?(experiment_key) && current_user + + ::Experiment.add_user(experiment_key, tracking_group(experiment_key), current_user) + end + + def experiment_tracking_category_and_group(experiment_key) + "#{tracking_category(experiment_key)}:#{tracking_group(experiment_key, '_group')}" + end + + private + + def dnt_enabled? + Gitlab::Utils.to_boolean(request.headers['DNT']) + end + + def experimentation_subject_id + cookies.signed[:experimentation_subject_id] + end + + def experimentation_subject_index(experiment_key) + return if experimentation_subject_id.blank? + + if Experimentation.experiment(experiment_key).use_backwards_compatible_subject_index + experimentation_subject_id.delete('-').hex % 100 + else + Zlib.crc32("#{experiment_key}#{experimentation_subject_id}") % 100 + end + end + + def track_experiment_event_for(experiment_key, action, value) + return unless Experimentation.enabled?(experiment_key) + + yield experimentation_tracking_data(experiment_key, action, value) + end + + def experimentation_tracking_data(experiment_key, action, value) + { + category: tracking_category(experiment_key), + action: action, + property: tracking_group(experiment_key, "_group"), + label: experimentation_subject_id, + value: value + }.compact + end + + def tracking_category(experiment_key) + Experimentation.experiment(experiment_key).tracking_category + end + + def tracking_group(experiment_key, suffix = nil) + return unless Experimentation.enabled?(experiment_key) + + group = experiment_enabled?(experiment_key) ? GROUP_EXPERIMENTAL : GROUP_CONTROL + + suffix ? "#{group}#{suffix}" : group + end + + def forced_enabled?(experiment_key) + params.has_key?(:force_experiment) && params[:force_experiment] == experiment_key.to_s + end + end + end +end diff --git a/lib/gitlab/experimentation/group_types.rb b/lib/gitlab/experimentation/group_types.rb new file mode 100644 index 00000000000..8e8f7284b99 --- /dev/null +++ b/lib/gitlab/experimentation/group_types.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Gitlab + module Experimentation + module GroupTypes + GROUP_CONTROL = :control + GROUP_EXPERIMENTAL = :experimental + end + end +end |