2019-10-01 14:06:28 -04:00
# frozen_string_literal: true
# == Experimentation
#
2020-04-21 11:21:10 -04:00
# Utility module for A/B testing experimental features. Define your experiments in the `EXPERIMENTS` constant.
# Experiment options:
# - tracking_category (optional, used to set the category when tracking an experiment event)
2020-12-04 04:09:36 -05:00
# - use_backwards_compatible_subject_index (optional, set this to true if you need backwards compatibility -- you likely do not need this, see note in the next paragraph.)
2021-01-19 13:11:04 -05:00
# - rollout_strategy: default is `:cookie` based rollout. We may also set it to `:user` based rollout
2020-12-04 04:09:36 -05:00
#
# Using the backwards-compatible subject index (use_backwards_compatible_subject_index option):
# This option was added when [the calculation of experimentation_subject_index was changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45733/diffs#41af4a6fa5a10c7068559ce21c5188483751d934_157_173). It is not intended to be used by new experiments, it exists merely for the segmentation integrity of in-flight experiments at the time the change was deployed. That is, we want users who were assigned to the "experimental" group or the "control" group before the change to still be in those same groups after the change. See [the original issue](https://gitlab.com/gitlab-org/gitlab/-/issues/270858) and [this related comment](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48110#note_458223745) for more information.
2020-04-21 11:21:10 -04:00
#
# The experiment is controlled by a Feature Flag (https://docs.gitlab.com/ee/development/feature_flags/controls.html),
2020-05-14 14:08:06 -04:00
# which is named "#{experiment_key}_experiment_percentage" and *must* be set with a percentage and not be used for other purposes.
2020-04-21 11:21:10 -04:00
#
2020-05-14 14:08:06 -04:00
# To enable the experiment for 10% of the users:
#
2020-06-05 11:08:23 -04:00
# chatops: `/chatops run feature set experiment_key_experiment_percentage 10`
# console: `Feature.enable_percentage_of_time(:experiment_key_experiment_percentage, 10)`
2020-04-21 11:21:10 -04:00
#
# To disable the experiment:
#
2020-05-14 14:08:06 -04:00
# chatops: `/chatops run feature delete experiment_key_experiment_percentage`
2020-06-02 08:08:33 -04:00
# console: `Feature.remove(:experiment_key_experiment_percentage)`
2020-05-14 14:08:06 -04:00
#
# To check the current rollout percentage:
#
# chatops: `/chatops run feature get experiment_key_experiment_percentage`
# console: `Feature.get(:experiment_key_experiment_percentage).percentage_of_time_value`
2019-10-01 14:06:28 -04:00
#
2020-06-02 08:08:33 -04:00
2020-06-05 11:08:23 -04:00
# TODO: see https://gitlab.com/gitlab-org/gitlab/-/issues/217490
2019-10-01 14:06:28 -04:00
module Gitlab
module Experimentation
EXPERIMENTS = {
2020-10-26 11:08:40 -04:00
invite_members_empty_group_version_a : {
2020-10-28 02:08:59 -04:00
tracking_category : 'Growth::Expansion::Experiment::InviteMembersEmptyGroupVersionA' ,
use_backwards_compatible_subject_index : true
2020-10-26 11:08:40 -04:00
} ,
2020-08-07 11:10:17 -04:00
contact_sales_btn_in_app : {
2020-10-28 02:08:59 -04:00
tracking_category : 'Growth::Conversion::Experiment::ContactSalesInApp' ,
use_backwards_compatible_subject_index : true
2020-08-18 17:09:57 -04:00
} ,
2020-12-02 16:09:44 -05:00
remove_known_trial_form_fields : {
tracking_category : 'Growth::Conversion::Experiment::RemoveKnownTrialFormFields'
2020-12-03 10:09:46 -05:00
} ,
2020-12-22 16:10:06 -05:00
invite_members_new_dropdown : {
tracking_category : 'Growth::Expansion::Experiment::InviteMembersNewDropdown'
2020-12-28 04:10:18 -05:00
} ,
2021-01-06 13:10:52 -05:00
show_trial_status_in_sidebar : {
2021-01-19 13:11:04 -05:00
tracking_category : 'Growth::Conversion::Experiment::ShowTrialStatusInSidebar' ,
rollout_strategy : :group
2021-01-06 13:10:52 -05:00
} ,
2020-12-28 04:10:18 -05:00
trial_onboarding_issues : {
tracking_category : 'Growth::Conversion::Experiment::TrialOnboardingIssues'
2021-01-29 10:09:40 -05:00
} ,
2021-02-16 04:09:36 -05:00
learn_gitlab_a : {
2021-05-24 14:10:28 -04:00
tracking_category : 'Growth::Conversion::Experiment::LearnGitLabA' ,
rollout_strategy : :user
2021-02-16 04:09:36 -05:00
} ,
learn_gitlab_b : {
2021-05-24 14:10:28 -04:00
tracking_category : 'Growth::Activation::Experiment::LearnGitLabB' ,
rollout_strategy : :user
2021-02-16 04:09:36 -05:00
} ,
2021-01-29 10:09:40 -05:00
in_product_marketing_emails : {
tracking_category : 'Growth::Activation::Experiment::InProductMarketingEmails'
2019-10-01 14:06:28 -04:00
}
} . freeze
class << self
2020-12-02 19:09:53 -05:00
def get_experiment ( experiment_key )
return unless EXPERIMENTS . key? ( experiment_key )
:: Gitlab :: Experimentation :: Experiment . new ( experiment_key , ** EXPERIMENTS [ experiment_key ] )
2019-10-18 17:06:37 -04:00
end
2020-12-02 19:09:53 -05:00
def active? ( experiment_key )
experiment = get_experiment ( experiment_key )
return false unless experiment
2019-10-01 14:06:28 -04:00
2020-12-02 19:09:53 -05:00
experiment . active?
2019-10-23 11:06:29 -04:00
end
2019-10-01 14:06:28 -04:00
2020-12-02 19:09:53 -05:00
def in_experiment_group? ( experiment_key , subject : )
return false if subject . blank?
return false unless active? ( experiment_key )
2021-01-19 13:11:04 -05:00
log_invalid_rollout ( experiment_key , subject )
2020-12-02 19:09:53 -05:00
experiment = get_experiment ( experiment_key )
return false unless experiment
experiment . enabled_for_index? ( index_for_subject ( experiment , subject ) )
end
2021-01-19 13:11:04 -05:00
def rollout_strategy ( experiment_key )
experiment = get_experiment ( experiment_key )
return unless experiment
experiment . rollout_strategy
end
def log_invalid_rollout ( experiment_key , subject )
return if valid_subject_for_rollout_strategy? ( experiment_key , subject )
logger = Gitlab :: ExperimentationLogger . build
logger . warn message : 'Subject must conform to the rollout strategy' ,
experiment_key : experiment_key ,
subject : subject . class . to_s ,
rollout_strategy : rollout_strategy ( experiment_key )
end
def valid_subject_for_rollout_strategy? ( experiment_key , subject )
case rollout_strategy ( experiment_key )
when :user
subject . is_a? ( User )
when :group
subject . is_a? ( Group )
when :cookie
subject . nil? || subject . is_a? ( String )
else
false
end
end
2020-12-02 19:09:53 -05:00
private
def index_for_subject ( experiment , subject )
index = if experiment . use_backwards_compatible_subject_index
Digest :: SHA1 . hexdigest ( subject_id ( subject ) ) . hex
else
Zlib . crc32 ( " #{ experiment . key } #{ subject_id ( subject ) } " )
end
index % 100
2020-09-24 05:09:35 -04:00
end
2020-12-02 19:09:53 -05:00
def subject_id ( subject )
if subject . respond_to? ( :to_global_id )
subject . to_global_id . to_s
elsif subject . respond_to? ( :to_s )
subject . to_s
else
2021-05-04 11:10:36 -04:00
raise ArgumentError , 'Subject must respond to `to_global_id` or `to_s`'
2020-12-02 19:09:53 -05:00
end
2019-10-01 14:06:28 -04:00
end
end
end
end