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, Rollout, TestBehaviors
Classes: Configuration, Context, Engine, Middleware, Variant
Constant Summary
collapse
- Error =
Class.new(StandardError)
- InvalidRolloutRules =
Class.new(Error)
- NestingError =
Class.new(Error)
- VERSION =
'0.6.5'
Class Method Summary
collapse
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, #variant_names
Class Method Details
.default_rollout(rollout = nil, options = {}) ⇒ Object
35
36
37
38
39
|
# File 'lib/gitlab/experiment.rb', line 35
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
41
42
43
44
45
|
# File 'lib/gitlab/experiment.rb', line 41
def exclude(*filter_list, **options, &block)
build_callback(:exclusion_check, filter_list.unshift(block), **options) do |target, callback|
throw(:abort) if target.instance_variable_get(:@excluded) || callback.call(target, nil) == true
end
end
|
.model_name ⇒ Object
used for generating routes
10
11
12
|
# File 'lib/gitlab/experiment/engine.rb', line 10
def self.model_name
ActiveModel::Name.new(self, Gitlab)
end
|
.published_experiments ⇒ Object
53
54
55
|
# File 'lib/gitlab/experiment.rb', line 53
def published_experiments
RequestStore.store[:published_gitlab_experiments] || {}
end
|
.segment(*filter_list, variant:, **options, &block) ⇒ Object
47
48
49
50
51
|
# File 'lib/gitlab/experiment.rb', line 47
def segment(*filter_list, variant:, **options, &block)
build_callback(:segmentation_check, filter_list.unshift(block), **options) do |target, callback|
target.variant(variant) if target.instance_variable_get(:@variant_name).nil? && callback.call(target, nil)
end
end
|
Instance Method Details
#candidate(name = nil, &block) ⇒ Object
Also known as:
try
67
68
69
70
|
# File 'lib/gitlab/experiment.rb', line 67
def candidate(name = nil, &block)
name = (name || :candidate).to_s
behaviors[name] = block
end
|
#context(value = nil) ⇒ Object
73
74
75
76
77
78
|
# File 'lib/gitlab/experiment.rb', line 73
def context(value = nil)
return @context if value.blank?
@context.value(value)
@context
end
|
#control(&block) ⇒ Object
Also known as:
use
62
63
64
|
# File 'lib/gitlab/experiment.rb', line 62
def control(&block)
candidate(:control, &block)
end
|
#enabled? ⇒ Boolean
134
135
136
|
# File 'lib/gitlab/experiment.rb', line 134
def enabled?
true
end
|
#exclude! ⇒ Object
103
104
105
|
# File 'lib/gitlab/experiment.rb', line 103
def exclude!
@excluded = true
end
|
#excluded? ⇒ Boolean
138
139
140
141
142
|
# File 'lib/gitlab/experiment.rb', line 138
def excluded?
return @excluded if defined?(@excluded)
@excluded = !run_callbacks(:exclusion_check) { :not_excluded }
end
|
#experiment_group? ⇒ Boolean
144
145
146
|
# File 'lib/gitlab/experiment.rb', line 144
def experiment_group?
instance_exec(@variant_name, &Configuration.inclusion_resolver)
end
|
#key_for(source, seed = name) ⇒ Object
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
# File 'lib/gitlab/experiment.rb', line 156
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
|
#name ⇒ Object
58
59
60
|
# File 'lib/gitlab/experiment.rb', line 58
def name
[Configuration.name_prefix, @name].compact.join('_')
end
|
#process_redirect_url(url) ⇒ Object
127
128
129
130
131
132
|
# File 'lib/gitlab/experiment.rb', line 127
def process_redirect_url(url)
return unless Configuration.redirect_url_validator&.call(url)
track('visited', url: url)
url
end
|
#publish(result) ⇒ Object
115
116
117
118
119
|
# File 'lib/gitlab/experiment.rb', line 115
def publish(result)
instance_exec(result, &Configuration.publishing_behavior)
(RequestStore.store[:published_gitlab_experiments] ||= {})[name] = signature.merge(excluded: excluded?)
end
|
#rollout(rollout = nil, options = {}) ⇒ Object
97
98
99
100
101
|
# File 'lib/gitlab/experiment.rb', line 97
def rollout(rollout = nil, options = {})
return @rollout ||= self.class.default_rollout(nil, options) if rollout.blank?
@rollout = Rollout.resolve(rollout).new(options)
end
|
#run(variant_name = nil) ⇒ Object
107
108
109
110
111
112
113
|
# File 'lib/gitlab/experiment.rb', line 107
def run(variant_name = nil)
return @result if context.frozen?
@result = run_callbacks(:run) { super(variant(variant_name).name) }
rescue Scientist::BehaviorMissing => e
raise Error, e
end
|
#should_track? ⇒ Boolean
148
149
150
|
# File 'lib/gitlab/experiment.rb', line 148
def should_track?
enabled? && @context.trackable? && !excluded?
end
|
#signature ⇒ Object
152
153
154
|
# File 'lib/gitlab/experiment.rb', line 152
def signature
{ variant: variant.name, experiment: name }.merge(context.signature)
end
|
#track(action, **event_args) ⇒ Object
121
122
123
124
125
|
# File 'lib/gitlab/experiment.rb', line 121
def track(action, **event_args)
return unless should_track?
instance_exec(action, event_args, &Configuration.tracking_behavior)
end
|
#variant(value = nil) ⇒ Object
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
# File 'lib/gitlab/experiment.rb', line 80
def variant(value = nil)
@variant_name = cache_variant(value) if value.present?
return Variant.new(name: (@variant_name || :unresolved).to_s) if @variant_name || @resolving_variant
if enabled?
@resolving_variant = true
@variant_name = cached_variant_resolver(@variant_name)
end
run_callbacks(segmentation_callback_chain) do
@variant_name ||= :control
Variant.new(name: @variant_name.to_s)
end
ensure
@resolving_variant = false
end
|