Commit 174e38ba authored by Stan Hu's avatar Stan Hu

Merge branch '338734-remove-experiment-backwards-compatible-index-capability' into 'master'

Remove experiment backwards compatible index capability

See merge request gitlab-org/gitlab!68489
parents 19111ef5 150a2b49
...@@ -5,12 +5,8 @@ ...@@ -5,12 +5,8 @@
# Utility module for A/B testing experimental features. Define your experiments in the `EXPERIMENTS` constant. # Utility module for A/B testing experimental features. Define your experiments in the `EXPERIMENTS` constant.
# Experiment options: # Experiment options:
# - tracking_category (optional, used to set the category when tracking an experiment event) # - tracking_category (optional, used to set the category when tracking an experiment event)
# - 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.)
# - rollout_strategy: default is `:cookie` based rollout. We may also set it to `:user` based rollout # - rollout_strategy: default is `:cookie` based rollout. We may also set it to `:user` based rollout
# #
# 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.
#
# The experiment is controlled by a Feature Flag (https://docs.gitlab.com/ee/development/feature_flags/controls.html), # The experiment is controlled by a Feature Flag (https://docs.gitlab.com/ee/development/feature_flags/controls.html),
# which is named "#{experiment_key}_experiment_percentage" and *must* be set with a percentage and not be used for other purposes. # which is named "#{experiment_key}_experiment_percentage" and *must* be set with a percentage and not be used for other purposes.
# #
...@@ -118,11 +114,7 @@ module Gitlab ...@@ -118,11 +114,7 @@ module Gitlab
private private
def index_for_subject(experiment, subject) def index_for_subject(experiment, subject)
index = if experiment.use_backwards_compatible_subject_index index = Zlib.crc32("#{experiment.key}#{subject_id(subject)}")
Digest::SHA1.hexdigest(subject_id(subject)).hex
else
Zlib.crc32("#{experiment.key}#{subject_id(subject)}")
end
index % 100 index % 100
end end
......
...@@ -48,7 +48,7 @@ module Gitlab ...@@ -48,7 +48,7 @@ module Gitlab
Experimentation.log_invalid_rollout(experiment_key, subject) Experimentation.log_invalid_rollout(experiment_key, subject)
subject ||= fallback_experimentation_subject_index(experiment_key) subject ||= experimentation_subject_id
Experimentation.in_experiment_group?(experiment_key, subject: subject) Experimentation.in_experiment_group?(experiment_key, subject: subject)
end end
...@@ -106,16 +106,6 @@ module Gitlab ...@@ -106,16 +106,6 @@ module Gitlab
cookies.signed[:experimentation_subject_id] cookies.signed[:experimentation_subject_id]
end end
def fallback_experimentation_subject_index(experiment_key)
return if experimentation_subject_id.blank?
if Experimentation.get_experiment(experiment_key).use_backwards_compatible_subject_index
experimentation_subject_id.delete('-')
else
experimentation_subject_id
end
end
def track_experiment_event_for(experiment_key, action, value, subject: nil) def track_experiment_event_for(experiment_key, action, value, subject: nil)
return unless Experimentation.active?(experiment_key) return unless Experimentation.active?(experiment_key)
...@@ -139,7 +129,7 @@ module Gitlab ...@@ -139,7 +129,7 @@ module Gitlab
def tracking_group(experiment_key, suffix = nil, subject: nil) def tracking_group(experiment_key, suffix = nil, subject: nil)
return unless Experimentation.active?(experiment_key) return unless Experimentation.active?(experiment_key)
subject ||= fallback_experimentation_subject_index(experiment_key) subject ||= experimentation_subject_id
group = experiment_enabled?(experiment_key, subject: subject) ? GROUP_EXPERIMENTAL : GROUP_CONTROL group = experiment_enabled?(experiment_key, subject: subject) ? GROUP_EXPERIMENTAL : GROUP_CONTROL
suffix ? "#{group}#{suffix}" : group suffix ? "#{group}#{suffix}" : group
......
...@@ -5,12 +5,11 @@ module Gitlab ...@@ -5,12 +5,11 @@ module Gitlab
class Experiment class Experiment
FEATURE_FLAG_SUFFIX = "_experiment_percentage" FEATURE_FLAG_SUFFIX = "_experiment_percentage"
attr_reader :key, :tracking_category, :use_backwards_compatible_subject_index, :rollout_strategy attr_reader :key, :tracking_category, :rollout_strategy
def initialize(key, **params) def initialize(key, **params)
@key = key @key = key
@tracking_category = params[:tracking_category] @tracking_category = params[:tracking_category]
@use_backwards_compatible_subject_index = params[:use_backwards_compatible_subject_index]
@rollout_strategy = params[:rollout_strategy] || :cookie @rollout_strategy = params[:rollout_strategy] || :cookie
end end
......
...@@ -7,10 +7,6 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do ...@@ -7,10 +7,6 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
before do before do
stub_const('Gitlab::Experimentation::EXPERIMENTS', { stub_const('Gitlab::Experimentation::EXPERIMENTS', {
backwards_compatible_test_experiment: {
tracking_category: 'Team',
use_backwards_compatible_subject_index: true
},
test_experiment: { test_experiment: {
tracking_category: 'Team', tracking_category: 'Team',
rollout_strategy: rollout_strategy rollout_strategy: rollout_strategy
...@@ -23,7 +19,6 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do ...@@ -23,7 +19,6 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
allow(Gitlab).to receive(:dev_env_or_com?).and_return(is_gitlab_com) allow(Gitlab).to receive(:dev_env_or_com?).and_return(is_gitlab_com)
Feature.enable_percentage_of_time(:backwards_compatible_test_experiment_experiment_percentage, enabled_percentage)
Feature.enable_percentage_of_time(:test_experiment_experiment_percentage, enabled_percentage) Feature.enable_percentage_of_time(:test_experiment_experiment_percentage, enabled_percentage)
end end
...@@ -124,24 +119,15 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do ...@@ -124,24 +119,15 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
end end
context 'cookie is present' do context 'cookie is present' do
using RSpec::Parameterized::TableSyntax
before do before do
cookies.permanent.signed[:experimentation_subject_id] = 'abcd-1234' cookies.permanent.signed[:experimentation_subject_id] = 'abcd-1234'
get :index get :index
end end
where(:experiment_key, :index_value) do it 'calls Gitlab::Experimentation.in_experiment_group? with the name of the experiment and the calculated experimentation_subject_index based on the uuid' do
:test_experiment | 'abcd-1234' expect(Gitlab::Experimentation).to receive(:in_experiment_group?).with(:test_experiment, subject: 'abcd-1234')
:backwards_compatible_test_experiment | 'abcd1234'
end
with_them do
it 'calls Gitlab::Experimentation.in_experiment_group?? with the name of the experiment and the calculated experimentation_subject_index based on the uuid' do
expect(Gitlab::Experimentation).to receive(:in_experiment_group?).with(experiment_key, subject: index_value)
check_experiment(experiment_key) check_experiment(:test_experiment)
end
end end
context 'when subject is given' do context 'when subject is given' do
......
...@@ -9,7 +9,6 @@ RSpec.describe Gitlab::Experimentation::Experiment do ...@@ -9,7 +9,6 @@ RSpec.describe Gitlab::Experimentation::Experiment do
let(:params) do let(:params) do
{ {
tracking_category: 'Category1', tracking_category: 'Category1',
use_backwards_compatible_subject_index: true,
rollout_strategy: nil rollout_strategy: nil
} }
end end
......
...@@ -7,10 +7,6 @@ RSpec.describe Gitlab::Experimentation do ...@@ -7,10 +7,6 @@ RSpec.describe Gitlab::Experimentation do
before do before do
stub_const('Gitlab::Experimentation::EXPERIMENTS', { stub_const('Gitlab::Experimentation::EXPERIMENTS', {
backwards_compatible_test_experiment: {
tracking_category: 'Team',
use_backwards_compatible_subject_index: true
},
test_experiment: { test_experiment: {
tracking_category: 'Team' tracking_category: 'Team'
}, },
...@@ -22,7 +18,6 @@ RSpec.describe Gitlab::Experimentation do ...@@ -22,7 +18,6 @@ RSpec.describe Gitlab::Experimentation do
skip_feature_flags_yaml_validation skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check skip_default_enabled_yaml_check
Feature.enable_percentage_of_time(:backwards_compatible_test_experiment_experiment_percentage, enabled_percentage)
Feature.enable_percentage_of_time(:test_experiment_experiment_percentage, enabled_percentage) Feature.enable_percentage_of_time(:test_experiment_experiment_percentage, enabled_percentage)
allow(Gitlab).to receive(:com?).and_return(true) allow(Gitlab).to receive(:com?).and_return(true)
end end
...@@ -65,97 +60,47 @@ RSpec.describe Gitlab::Experimentation do ...@@ -65,97 +60,47 @@ RSpec.describe Gitlab::Experimentation do
end end
describe '.in_experiment_group?' do describe '.in_experiment_group?' do
context 'with new index calculation' do let(:enabled_percentage) { 50 }
let(:enabled_percentage) { 50 } let(:experiment_subject) { 'z' } # Zlib.crc32('test_experimentz') % 100 = 33
let(:experiment_subject) { 'z' } # Zlib.crc32('test_experimentz') % 100 = 33
subject { described_class.in_experiment_group?(:test_experiment, subject: experiment_subject) }
context 'when experiment is active' do
context 'when subject is part of the experiment' do
it { is_expected.to eq(true) }
end
context 'when subject is not part of the experiment' do subject { described_class.in_experiment_group?(:test_experiment, subject: experiment_subject) }
let(:experiment_subject) { 'a' } # Zlib.crc32('test_experimenta') % 100 = 61
it { is_expected.to eq(false) } context 'when experiment is active' do
end context 'when subject is part of the experiment' do
it { is_expected.to eq(true) }
end
context 'when subject has a global_id' do context 'when subject is not part of the experiment' do
let(:experiment_subject) { double(:subject, to_global_id: 'z') } let(:experiment_subject) { 'a' } # Zlib.crc32('test_experimenta') % 100 = 61
it { is_expected.to eq(true) } it { is_expected.to eq(false) }
end end
context 'when subject is nil' do context 'when subject has a global_id' do
let(:experiment_subject) { nil } let(:experiment_subject) { double(:subject, to_global_id: 'z') }
it { is_expected.to eq(false) } it { is_expected.to eq(true) }
end end
context 'when subject is an empty string' do context 'when subject is nil' do
let(:experiment_subject) { '' } let(:experiment_subject) { nil }
it { is_expected.to eq(false) } it { is_expected.to eq(false) }
end
end end
context 'when experiment is not active' do context 'when subject is an empty string' do
before do let(:experiment_subject) { '' }
allow(described_class).to receive(:active?).and_return(false)
end
it { is_expected.to eq(false) } it { is_expected.to eq(false) }
end end
end end
context 'with backwards compatible index calculation' do context 'when experiment is not active' do
let(:experiment_subject) { 'abcd' } # Digest::SHA1.hexdigest('abcd').hex % 100 = 7 before do
allow(described_class).to receive(:active?).and_return(false)
subject { described_class.in_experiment_group?(:backwards_compatible_test_experiment, subject: experiment_subject) }
context 'when experiment is active' do
before do
allow(described_class).to receive(:active?).and_return(true)
end
context 'when subject is part of the experiment' do
it { is_expected.to eq(true) }
end
context 'when subject is not part of the experiment' do
let(:experiment_subject) { 'abc' } # Digest::SHA1.hexdigest('abc').hex % 100 = 17
it { is_expected.to eq(false) }
end
context 'when subject has a global_id' do
let(:experiment_subject) { double(:subject, to_global_id: 'abcd') }
it { is_expected.to eq(true) }
end
context 'when subject is nil' do
let(:experiment_subject) { nil }
it { is_expected.to eq(false) }
end
context 'when subject is an empty string' do
let(:experiment_subject) { '' }
it { is_expected.to eq(false) }
end
end end
context 'when experiment is not active' do it { is_expected.to eq(false) }
before do
allow(described_class).to receive(:active?).and_return(false)
end
it { is_expected.to eq(false) }
end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment