Class: Gitlab::Experiment
- Inherits:
-
Object
- Object
- Gitlab::Experiment
show all
- Includes:
- ActiveModel::Model, BaseInterface, Cache, Callbacks, Nestable
- Defined in:
- lib/gitlab/experiment.rb,
lib/gitlab/experiment/dsl.rb,
lib/gitlab/experiment/cache.rb,
lib/gitlab/experiment/rspec.rb,
lib/gitlab/experiment/engine.rb,
lib/gitlab/experiment/errors.rb,
lib/gitlab/experiment/context.rb,
lib/gitlab/experiment/cookies.rb,
lib/gitlab/experiment/rollout.rb,
lib/gitlab/experiment/variant.rb,
lib/gitlab/experiment/version.rb,
lib/gitlab/experiment/nestable.rb,
lib/gitlab/experiment/callbacks.rb,
lib/gitlab/experiment/middleware.rb,
lib/gitlab/experiment/configuration.rb,
lib/gitlab/experiment/base_interface.rb,
lib/gitlab/experiment/rollout/random.rb,
lib/gitlab/experiment/rollout/percent.rb,
lib/gitlab/experiment/rollout/round_robin.rb,
lib/gitlab/experiment/cache/redis_hash_store.rb,
lib/gitlab/experiment/test_behaviors/trackable.rb
Defined Under Namespace
Modules: BaseInterface, Cache, Callbacks, Cookies, Dsl, Nestable, RSpecHelpers, RSpecMatchers, RSpecMocks, Rollout, TestBehaviors
Classes: Configuration, Context, Engine, Middleware, NestingError, Variant, WrappedExperiment
Constant Summary
collapse
- Error =
Class.new(StandardError)
- InvalidRolloutRules =
Class.new(Error)
- UnregisteredExperiment =
Class.new(Error)
- ExistingBehaviorError =
Class.new(Error)
- BehaviorMissingError =
Class.new(Error)
- VERSION =
'0.7.0'
Class Method Summary
collapse
-
.after_run(*filter_list, **options, &block) ⇒ Object
-
.around_run(*filter_list, **options, &block) ⇒ Object
-
.before_run(*filter_list, **options, &block) ⇒ Object
-
.candidate(*filter_list, **options, &block) ⇒ Object
-
.control(*filter_list, **options, &block) ⇒ Object
Class level behavior registration methods.
-
.default_rollout(rollout = nil, options = {}) ⇒ Object
Class level definition methods.
-
.exclude(*filter_list, **options, &block) ⇒ Object
Class level callback registration methods.
-
.model_name ⇒ Object
Used for generating routes.
-
.published_experiments ⇒ Object
Class level accessor methods.
-
.segment(*filter_list, variant:, **options, &block) ⇒ Object
-
.variant(variant, *filter_list, **options, &block) ⇒ Object
Instance Method Summary
collapse
Methods included from Nestable
#nest_experiment
Methods included from Cache
#cache, #cache_key, #cache_variant
#behaviors, #flipper_id, #id, #initialize, #inspect, #public_behaviors_with_deprecations, #session_id, #try, #use, #variant_names
Class Method Details
.after_run(*filter_list, **options, &block) ⇒ Object
62
63
64
|
# File 'lib/gitlab/experiment.rb', line 62
def after_run(*filter_list, **options, &block)
build_run_callback(filter_list.unshift(:after, block), **options)
end
|
.around_run(*filter_list, **options, &block) ⇒ Object
58
59
60
|
# File 'lib/gitlab/experiment.rb', line 58
def around_run(*filter_list, **options, &block)
build_run_callback(filter_list.unshift(:around, block), **options)
end
|
.before_run(*filter_list, **options, &block) ⇒ Object
54
55
56
|
# File 'lib/gitlab/experiment.rb', line 54
def before_run(*filter_list, **options, &block)
build_run_callback(filter_list.unshift(:before, block), **options)
end
|
.candidate(*filter_list, **options, &block) ⇒ Object
36
37
38
|
# File 'lib/gitlab/experiment.rb', line 36
def candidate(*filter_list, **options, &block)
variant(:candidate, *filter_list, **options, &block)
end
|
.control(*filter_list, **options, &block) ⇒ Object
Class level behavior registration methods.
32
33
34
|
# File 'lib/gitlab/experiment.rb', line 32
def control(*filter_list, **options, &block)
variant(:control, *filter_list, **options, &block)
end
|
.default_rollout(rollout = nil, options = {}) ⇒ Object
Class level definition methods.
68
69
70
71
72
|
# File 'lib/gitlab/experiment.rb', line 68
def default_rollout(rollout = nil, options = {})
return @_rollout ||= Configuration.default_rollout if rollout.blank?
@_rollout = Rollout.resolve(rollout).new(options)
end
|
.exclude(*filter_list, **options, &block) ⇒ Object
Class level callback registration methods.
46
47
48
|
# File 'lib/gitlab/experiment.rb', line 46
def exclude(*filter_list, **options, &block)
build_exclude_callback(filter_list.unshift(block), **options)
end
|
.model_name ⇒ Object
Used for generating routes. We’ve included the method and ‘ActiveModel::Model` here because these things don’t make sense outside of Rails environments.
11
12
13
|
# File 'lib/gitlab/experiment/engine.rb', line 11
def self.model_name
ActiveModel::Name.new(self, Gitlab)
end
|
.published_experiments ⇒ Object
Class level accessor methods.
76
77
78
|
# File 'lib/gitlab/experiment.rb', line 76
def published_experiments
RequestStore.store[:published_gitlab_experiments] || {}
end
|
.segment(*filter_list, variant:, **options, &block) ⇒ Object
50
51
52
|
# File 'lib/gitlab/experiment.rb', line 50
def segment(*filter_list, variant:, **options, &block)
build_segment_callback(filter_list.unshift(block), variant, **options)
end
|
.variant(variant, *filter_list, **options, &block) ⇒ Object
40
41
42
|
# File 'lib/gitlab/experiment.rb', line 40
def variant(variant, *filter_list, **options, &block)
build_behavior_callback(filter_list, variant, **options, &block)
end
|
Instance Method Details
#assigned(value = nil) ⇒ Object
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
# File 'lib/gitlab/experiment.rb', line 126
def assigned(value = nil)
@_assigned_variant_name = cache_variant(value) if value.present?
if @_assigned_variant_name || @_resolving_variant
return Variant.new(name: (@_assigned_variant_name || :unresolved).to_s)
end
if enabled?
@_resolving_variant = true
@_assigned_variant_name = cached_variant_resolver(@_assigned_variant_name)
end
run_callbacks(segmentation_callback_chain) do
@_assigned_variant_name ||= :control
Variant.new(name: @_assigned_variant_name.to_s)
end
ensure
@_resolving_variant = false
end
|
#candidate(name = nil, &block) ⇒ Object
89
90
91
92
93
94
95
96
97
|
# File 'lib/gitlab/experiment.rb', line 89
def candidate(name = nil, &block)
if name.present?
Configuration.deprecated(<<~MESSAGE, version: '0.7.0')
passing name to `candidate` is deprecated and will be removed from {{release}} (instead use `variant(#{name.inspect})`)
MESSAGE
end
variant(name || :candidate, &block)
end
|
#context(value = nil) ⇒ Object
119
120
121
122
123
124
|
# File 'lib/gitlab/experiment.rb', line 119
def context(value = nil)
return @_context if value.blank?
@_context.value(value)
@_context
end
|
#control(&block) ⇒ Object
85
86
87
|
# File 'lib/gitlab/experiment.rb', line 85
def control(&block)
variant(:control, &block)
end
|
#enabled? ⇒ Boolean
180
181
182
|
# File 'lib/gitlab/experiment.rb', line 180
def enabled?
rollout.enabled?
end
|
#exclude! ⇒ Object
151
152
153
|
# File 'lib/gitlab/experiment.rb', line 151
def exclude!
@_excluded = true
end
|
#excluded? ⇒ Boolean
184
185
186
187
188
|
# File 'lib/gitlab/experiment.rb', line 184
def excluded?
return @_excluded if defined?(@_excluded)
@_excluded = !run_callbacks(exclusion_callback_chain) { :not_excluded }
end
|
#key_for(source, seed = name) ⇒ Object
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
|
# File 'lib/gitlab/experiment.rb', line 198
def key_for(source, seed = name)
if (block = Configuration.instance_variable_get(:@__context_hash_strategy))
return instance_exec(source, seed, &block)
end
return source if source.is_a?(String)
source = source.keys + source.values if source.is_a?(Hash)
ingredients = Array(source).map { |v| identify(v) }
ingredients.unshift(seed).unshift(Configuration.context_key_secret)
Digest::SHA2.new(Configuration.context_key_bit_length).hexdigest(ingredients.join('|'))
end
|
#process_redirect_url(url) ⇒ Object
173
174
175
176
177
178
|
# File 'lib/gitlab/experiment.rb', line 173
def process_redirect_url(url)
return unless Configuration.redirect_url_validator&.call(url)
track('visited', url: url)
url end
|
#publish(result = nil) ⇒ Object
161
162
163
164
165
|
# File 'lib/gitlab/experiment.rb', line 161
def publish(result = nil)
instance_exec(result, &Configuration.publishing_behavior)
(RequestStore.store[:published_gitlab_experiments] ||= {})[name] = signature.merge(excluded: excluded?)
end
|
#rollout(rollout = nil, options = {}) ⇒ Object
145
146
147
148
149
|
# File 'lib/gitlab/experiment.rb', line 145
def rollout(rollout = nil, options = {})
return @_rollout ||= self.class.default_rollout(nil, options).for(self) if rollout.blank?
@_rollout = Rollout.resolve(rollout).new(options).for(self)
end
|
#run(variant_name = nil) ⇒ Object
155
156
157
158
159
|
# File 'lib/gitlab/experiment.rb', line 155
def run(variant_name = nil)
return @_result if context.frozen?
@_result = run_callbacks(run_callback_chain) { super(assigned(variant_name).name) }
end
|
#should_track? ⇒ Boolean
190
191
192
|
# File 'lib/gitlab/experiment.rb', line 190
def should_track?
enabled? && context.trackable? && !excluded?
end
|
#signature ⇒ Object
194
195
196
|
# File 'lib/gitlab/experiment.rb', line 194
def signature
{ variant: assigned.name, experiment: name }.merge(context.signature)
end
|
#track(action, **event_args) ⇒ Object
167
168
169
170
171
|
# File 'lib/gitlab/experiment.rb', line 167
def track(action, **event_args)
return unless should_track?
instance_exec(action, tracking_context(event_args).try(:compact) || {}, &Configuration.tracking_behavior)
end
|
#variant(name = nil, &block) ⇒ Object
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
# File 'lib/gitlab/experiment.rb', line 99
def variant(name = nil, &block)
if block.present? raise ArgumentError, 'missing variant name' if name.blank?
return behaviors[name.to_s] = block
end
if name.present?
Configuration.deprecated(<<~MESSAGE, version: '0.7.0')
setting the variant using `variant` is deprecated and will be removed from {{release}} (instead use `assigned(#{name.inspect})`)
MESSAGE
else
Configuration.deprecated(<<~MESSAGE, version: '0.7.0')
getting the assigned variant using `variant` is deprecated and will be removed from {{release}} (instead use `assigned`)
MESSAGE
end
assigned(name)
end
|