gitlab-experiment 0.6.5 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +410 -296
- data/lib/generators/gitlab/experiment/experiment_generator.rb +9 -4
- data/lib/generators/gitlab/experiment/install/templates/initializer.rb.tt +89 -45
- data/lib/generators/gitlab/experiment/templates/experiment.rb.tt +69 -3
- data/lib/gitlab/experiment/base_interface.rb +86 -24
- data/lib/gitlab/experiment/cache/redis_hash_store.rb +10 -10
- data/lib/gitlab/experiment/cache.rb +1 -3
- data/lib/gitlab/experiment/callbacks.rb +97 -6
- data/lib/gitlab/experiment/configuration.rb +196 -28
- data/lib/gitlab/experiment/context.rb +0 -2
- data/lib/gitlab/experiment/cookies.rb +0 -2
- data/lib/gitlab/experiment/engine.rb +2 -1
- data/lib/gitlab/experiment/errors.rb +21 -1
- data/lib/gitlab/experiment/nestable.rb +2 -2
- data/lib/gitlab/experiment/rollout/percent.rb +40 -17
- data/lib/gitlab/experiment/rollout/random.rb +25 -4
- data/lib/gitlab/experiment/rollout/round_robin.rb +27 -10
- data/lib/gitlab/experiment/rollout.rb +44 -8
- data/lib/gitlab/experiment/rspec.rb +208 -127
- data/lib/gitlab/experiment/version.rb +1 -1
- data/lib/gitlab/experiment.rb +112 -57
- metadata +35 -55
data/lib/gitlab/experiment.rb
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'scientist'
|
|
4
3
|
require 'request_store'
|
|
5
|
-
require 'active_support
|
|
6
|
-
require 'active_support/cache'
|
|
7
|
-
require 'active_support/concern'
|
|
8
|
-
require 'active_support/core_ext/object/blank'
|
|
9
|
-
require 'active_support/core_ext/string/inflections'
|
|
4
|
+
require 'active_support'
|
|
10
5
|
require 'active_support/core_ext/module/delegation'
|
|
11
6
|
|
|
12
7
|
require 'gitlab/experiment/errors'
|
|
@@ -32,87 +27,138 @@ module Gitlab
|
|
|
32
27
|
include Nestable
|
|
33
28
|
|
|
34
29
|
class << self
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
# Class level behavior registration methods.
|
|
31
|
+
|
|
32
|
+
def control(*filter_list, **options, &block)
|
|
33
|
+
variant(:control, *filter_list, **options, &block)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def candidate(*filter_list, **options, &block)
|
|
37
|
+
variant(:candidate, *filter_list, **options, &block)
|
|
38
|
+
end
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
def variant(variant, *filter_list, **options, &block)
|
|
41
|
+
build_behavior_callback(filter_list, variant, **options, &block)
|
|
39
42
|
end
|
|
40
43
|
|
|
44
|
+
# Class level callback registration methods.
|
|
45
|
+
|
|
41
46
|
def exclude(*filter_list, **options, &block)
|
|
42
|
-
|
|
43
|
-
throw(:abort) if target.instance_variable_get(:@excluded) || callback.call(target, nil) == true
|
|
44
|
-
end
|
|
47
|
+
build_exclude_callback(filter_list.unshift(block), **options)
|
|
45
48
|
end
|
|
46
49
|
|
|
47
50
|
def segment(*filter_list, variant:, **options, &block)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
build_segment_callback(filter_list.unshift(block), variant, **options)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def before_run(*filter_list, **options, &block)
|
|
55
|
+
build_run_callback(filter_list.unshift(:before, block), **options)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def around_run(*filter_list, **options, &block)
|
|
59
|
+
build_run_callback(filter_list.unshift(:around, block), **options)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def after_run(*filter_list, **options, &block)
|
|
63
|
+
build_run_callback(filter_list.unshift(:after, block), **options)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Class level definition methods.
|
|
67
|
+
|
|
68
|
+
def default_rollout(rollout = nil, options = {})
|
|
69
|
+
return @_rollout ||= Configuration.default_rollout if rollout.blank?
|
|
70
|
+
|
|
71
|
+
@_rollout = Rollout.resolve(rollout).new(options)
|
|
51
72
|
end
|
|
52
73
|
|
|
74
|
+
# Class level accessor methods.
|
|
75
|
+
|
|
53
76
|
def published_experiments
|
|
54
77
|
RequestStore.store[:published_gitlab_experiments] || {}
|
|
55
78
|
end
|
|
56
79
|
end
|
|
57
80
|
|
|
58
81
|
def name
|
|
59
|
-
[Configuration.name_prefix, @
|
|
82
|
+
[Configuration.name_prefix, @_name].compact.join('_')
|
|
60
83
|
end
|
|
61
84
|
|
|
62
85
|
def control(&block)
|
|
63
|
-
|
|
86
|
+
variant(:control, &block)
|
|
64
87
|
end
|
|
65
|
-
alias_method :use, :control
|
|
66
88
|
|
|
67
89
|
def candidate(name = nil, &block)
|
|
68
|
-
|
|
69
|
-
|
|
90
|
+
if name.present?
|
|
91
|
+
Configuration.deprecated(<<~MESSAGE, version: '0.7.0')
|
|
92
|
+
passing name to `candidate` is deprecated and will be removed from {{release}} (instead use `variant(#{name.inspect})`)
|
|
93
|
+
MESSAGE
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
variant(name || :candidate, &block)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def variant(name = nil, &block)
|
|
100
|
+
if block.present? # we know we're defining a variant block
|
|
101
|
+
raise ArgumentError, 'missing variant name' if name.blank?
|
|
102
|
+
|
|
103
|
+
return behaviors[name.to_s] = block
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
if name.present?
|
|
107
|
+
Configuration.deprecated(<<~MESSAGE, version: '0.7.0')
|
|
108
|
+
setting the variant using `variant` is deprecated and will be removed from {{release}} (instead use `assigned(#{name.inspect})`)
|
|
109
|
+
MESSAGE
|
|
110
|
+
else
|
|
111
|
+
Configuration.deprecated(<<~MESSAGE, version: '0.7.0')
|
|
112
|
+
getting the assigned variant using `variant` is deprecated and will be removed from {{release}} (instead use `assigned`)
|
|
113
|
+
MESSAGE
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
assigned(name)
|
|
70
117
|
end
|
|
71
|
-
alias_method :try, :candidate
|
|
72
118
|
|
|
73
119
|
def context(value = nil)
|
|
74
|
-
return @
|
|
120
|
+
return @_context if value.blank?
|
|
75
121
|
|
|
76
|
-
@
|
|
77
|
-
@
|
|
122
|
+
@_context.value(value)
|
|
123
|
+
@_context
|
|
78
124
|
end
|
|
79
125
|
|
|
80
|
-
def
|
|
81
|
-
@
|
|
82
|
-
|
|
126
|
+
def assigned(value = nil)
|
|
127
|
+
@_assigned_variant_name = cache_variant(value) if value.present?
|
|
128
|
+
if @_assigned_variant_name || @_resolving_variant
|
|
129
|
+
return Variant.new(name: (@_assigned_variant_name || :unresolved).to_s)
|
|
130
|
+
end
|
|
83
131
|
|
|
84
132
|
if enabled?
|
|
85
|
-
@
|
|
86
|
-
@
|
|
133
|
+
@_resolving_variant = true
|
|
134
|
+
@_assigned_variant_name = cached_variant_resolver(@_assigned_variant_name)
|
|
87
135
|
end
|
|
88
136
|
|
|
89
137
|
run_callbacks(segmentation_callback_chain) do
|
|
90
|
-
@
|
|
91
|
-
Variant.new(name: @
|
|
138
|
+
@_assigned_variant_name ||= :control
|
|
139
|
+
Variant.new(name: @_assigned_variant_name.to_s)
|
|
92
140
|
end
|
|
93
141
|
ensure
|
|
94
|
-
@
|
|
142
|
+
@_resolving_variant = false
|
|
95
143
|
end
|
|
96
144
|
|
|
97
145
|
def rollout(rollout = nil, options = {})
|
|
98
|
-
return @
|
|
146
|
+
return @_rollout ||= self.class.default_rollout(nil, options).for(self) if rollout.blank?
|
|
99
147
|
|
|
100
|
-
@
|
|
148
|
+
@_rollout = Rollout.resolve(rollout).new(options).for(self)
|
|
101
149
|
end
|
|
102
150
|
|
|
103
151
|
def exclude!
|
|
104
|
-
@
|
|
152
|
+
@_excluded = true
|
|
105
153
|
end
|
|
106
154
|
|
|
107
155
|
def run(variant_name = nil)
|
|
108
|
-
return @
|
|
156
|
+
return @_result if context.frozen?
|
|
109
157
|
|
|
110
|
-
@
|
|
111
|
-
rescue Scientist::BehaviorMissing => e
|
|
112
|
-
raise Error, e
|
|
158
|
+
@_result = run_callbacks(run_callback_chain) { super(assigned(variant_name).name) }
|
|
113
159
|
end
|
|
114
160
|
|
|
115
|
-
def publish(result)
|
|
161
|
+
def publish(result = nil)
|
|
116
162
|
instance_exec(result, &Configuration.publishing_behavior)
|
|
117
163
|
|
|
118
164
|
(RequestStore.store[:published_gitlab_experiments] ||= {})[name] = signature.merge(excluded: excluded?)
|
|
@@ -121,7 +167,7 @@ module Gitlab
|
|
|
121
167
|
def track(action, **event_args)
|
|
122
168
|
return unless should_track?
|
|
123
169
|
|
|
124
|
-
instance_exec(action, event_args, &Configuration.tracking_behavior)
|
|
170
|
+
instance_exec(action, tracking_context(event_args).try(:compact) || {}, &Configuration.tracking_behavior)
|
|
125
171
|
end
|
|
126
172
|
|
|
127
173
|
def process_redirect_url(url)
|
|
@@ -132,29 +178,25 @@ module Gitlab
|
|
|
132
178
|
end
|
|
133
179
|
|
|
134
180
|
def enabled?
|
|
135
|
-
|
|
181
|
+
rollout.enabled?
|
|
136
182
|
end
|
|
137
183
|
|
|
138
184
|
def excluded?
|
|
139
|
-
return @
|
|
140
|
-
|
|
141
|
-
@excluded = !run_callbacks(:exclusion_check) { :not_excluded }
|
|
142
|
-
end
|
|
185
|
+
return @_excluded if defined?(@_excluded)
|
|
143
186
|
|
|
144
|
-
|
|
145
|
-
instance_exec(@variant_name, &Configuration.inclusion_resolver)
|
|
187
|
+
@_excluded = !run_callbacks(exclusion_callback_chain) { :not_excluded }
|
|
146
188
|
end
|
|
147
189
|
|
|
148
190
|
def should_track?
|
|
149
|
-
enabled? &&
|
|
191
|
+
enabled? && context.trackable? && !excluded?
|
|
150
192
|
end
|
|
151
193
|
|
|
152
194
|
def signature
|
|
153
|
-
{ variant:
|
|
195
|
+
{ variant: assigned.name, experiment: name }.merge(context.signature)
|
|
154
196
|
end
|
|
155
197
|
|
|
156
198
|
def key_for(source, seed = name)
|
|
157
|
-
# TODO:
|
|
199
|
+
# TODO: Remove - deprecated in release 0.7.0
|
|
158
200
|
if (block = Configuration.instance_variable_get(:@__context_hash_strategy))
|
|
159
201
|
return instance_exec(source, seed, &block)
|
|
160
202
|
end
|
|
@@ -175,14 +217,27 @@ module Gitlab
|
|
|
175
217
|
(object.respond_to?(:to_global_id) ? object.to_global_id : object).to_s
|
|
176
218
|
end
|
|
177
219
|
|
|
178
|
-
def
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
220
|
+
def resolve_variant_name
|
|
221
|
+
if respond_to?(:experiment_group?, true)
|
|
222
|
+
# TODO: Remove - deprecated in release 0.7.0
|
|
223
|
+
Configuration.deprecated(:experiment_group?, <<~MESSAGE, version: '0.7.0')
|
|
224
|
+
instead put this logic into custom rollout strategies
|
|
225
|
+
MESSAGE
|
|
226
|
+
|
|
227
|
+
rollout.resolve if experiment_group?
|
|
228
|
+
elsif (block = Configuration.instance_variable_get(:@__inclusion_resolver))
|
|
229
|
+
# TODO: Remove - deprecated in release 0.7.0
|
|
230
|
+
rollout.resolve if instance_exec(@_assigned_variant_name, &block)
|
|
231
|
+
elsif (block = Configuration.instance_variable_get(:@__variant_resolver))
|
|
232
|
+
# TODO: Remove - deprecated in release 0.6.5
|
|
233
|
+
instance_exec(@_assigned_variant_name, &block)
|
|
234
|
+
else
|
|
235
|
+
rollout.resolve # this is the end result of all deprecations
|
|
236
|
+
end
|
|
182
237
|
end
|
|
183
238
|
|
|
184
|
-
def
|
|
185
|
-
|
|
239
|
+
def tracking_context(event_args)
|
|
240
|
+
{}.merge(event_args)
|
|
186
241
|
end
|
|
187
242
|
end
|
|
188
243
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gitlab-experiment
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- GitLab
|
|
@@ -38,26 +38,6 @@ dependencies:
|
|
|
38
38
|
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '1.0'
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: scientist
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - "~>"
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '1.6'
|
|
48
|
-
- - ">="
|
|
49
|
-
- !ruby/object:Gem::Version
|
|
50
|
-
version: 1.6.0
|
|
51
|
-
type: :runtime
|
|
52
|
-
prerelease: false
|
|
53
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
54
|
-
requirements:
|
|
55
|
-
- - "~>"
|
|
56
|
-
- !ruby/object:Gem::Version
|
|
57
|
-
version: '1.6'
|
|
58
|
-
- - ">="
|
|
59
|
-
- !ruby/object:Gem::Version
|
|
60
|
-
version: 1.6.0
|
|
61
41
|
description:
|
|
62
42
|
email:
|
|
63
43
|
- gitlab_rubygems@gitlab.com
|
|
@@ -65,56 +45,56 @@ executables: []
|
|
|
65
45
|
extensions: []
|
|
66
46
|
extra_rdoc_files: []
|
|
67
47
|
files:
|
|
68
|
-
- lib/generators/gitlab
|
|
69
|
-
- lib/generators/gitlab/experiment
|
|
70
|
-
- lib/generators/gitlab/experiment/install
|
|
71
|
-
- lib/generators/gitlab/experiment/install/install_generator.rb
|
|
72
|
-
- lib/generators/gitlab/experiment/install/templates
|
|
73
|
-
- lib/generators/gitlab/experiment/install/templates/application_experiment.rb.tt
|
|
74
|
-
- lib/generators/gitlab/experiment/install/templates/initializer.rb.tt
|
|
75
|
-
- lib/generators/gitlab/experiment/install/templates/POST_INSTALL
|
|
76
|
-
- lib/generators/gitlab/experiment/USAGE
|
|
77
|
-
- lib/generators/gitlab/experiment/experiment_generator.rb
|
|
78
|
-
- lib/generators/gitlab/experiment/templates
|
|
79
|
-
- lib/generators/gitlab/experiment/templates/experiment.rb.tt
|
|
80
48
|
- lib/generators/test_unit
|
|
81
49
|
- lib/generators/test_unit/experiment
|
|
82
|
-
- lib/generators/test_unit/experiment/experiment_generator.rb
|
|
83
50
|
- lib/generators/test_unit/experiment/templates
|
|
84
51
|
- lib/generators/test_unit/experiment/templates/experiment_test.rb.tt
|
|
52
|
+
- lib/generators/test_unit/experiment/experiment_generator.rb
|
|
85
53
|
- lib/generators/rspec
|
|
86
54
|
- lib/generators/rspec/experiment
|
|
87
|
-
- lib/generators/rspec/experiment/experiment_generator.rb
|
|
88
55
|
- lib/generators/rspec/experiment/templates
|
|
89
56
|
- lib/generators/rspec/experiment/templates/experiment_spec.rb.tt
|
|
90
|
-
- lib/
|
|
57
|
+
- lib/generators/rspec/experiment/experiment_generator.rb
|
|
58
|
+
- lib/generators/gitlab
|
|
59
|
+
- lib/generators/gitlab/experiment
|
|
60
|
+
- lib/generators/gitlab/experiment/USAGE
|
|
61
|
+
- lib/generators/gitlab/experiment/templates
|
|
62
|
+
- lib/generators/gitlab/experiment/templates/experiment.rb.tt
|
|
63
|
+
- lib/generators/gitlab/experiment/install
|
|
64
|
+
- lib/generators/gitlab/experiment/install/templates
|
|
65
|
+
- lib/generators/gitlab/experiment/install/templates/application_experiment.rb.tt
|
|
66
|
+
- lib/generators/gitlab/experiment/install/templates/initializer.rb.tt
|
|
67
|
+
- lib/generators/gitlab/experiment/install/templates/POST_INSTALL
|
|
68
|
+
- lib/generators/gitlab/experiment/install/install_generator.rb
|
|
69
|
+
- lib/generators/gitlab/experiment/experiment_generator.rb
|
|
91
70
|
- lib/gitlab/experiment
|
|
92
|
-
- lib/gitlab/experiment/
|
|
93
|
-
- lib/gitlab/experiment/
|
|
94
|
-
- lib/gitlab/experiment/
|
|
95
|
-
- lib/gitlab/experiment/
|
|
71
|
+
- lib/gitlab/experiment/dsl.rb
|
|
72
|
+
- lib/gitlab/experiment/rspec.rb
|
|
73
|
+
- lib/gitlab/experiment/context.rb
|
|
74
|
+
- lib/gitlab/experiment/nestable.rb
|
|
75
|
+
- lib/gitlab/experiment/configuration.rb
|
|
76
|
+
- lib/gitlab/experiment/rollout.rb
|
|
96
77
|
- lib/gitlab/experiment/cache
|
|
97
78
|
- lib/gitlab/experiment/cache/redis_hash_store.rb
|
|
98
|
-
- lib/gitlab/experiment/errors.rb
|
|
99
|
-
- lib/gitlab/experiment/callbacks.rb
|
|
100
|
-
- lib/gitlab/experiment/rollout.rb
|
|
101
|
-
- lib/gitlab/experiment/base_interface.rb
|
|
102
|
-
- lib/gitlab/experiment/nestable.rb
|
|
103
|
-
- lib/gitlab/experiment/context.rb
|
|
104
79
|
- lib/gitlab/experiment/engine.rb
|
|
105
|
-
- lib/gitlab/experiment/
|
|
80
|
+
- lib/gitlab/experiment/base_interface.rb
|
|
81
|
+
- lib/gitlab/experiment/middleware.rb
|
|
82
|
+
- lib/gitlab/experiment/version.rb
|
|
83
|
+
- lib/gitlab/experiment/cookies.rb
|
|
84
|
+
- lib/gitlab/experiment/errors.rb
|
|
85
|
+
- lib/gitlab/experiment/cache.rb
|
|
86
|
+
- lib/gitlab/experiment/variant.rb
|
|
106
87
|
- lib/gitlab/experiment/rollout
|
|
107
88
|
- lib/gitlab/experiment/rollout/random.rb
|
|
108
|
-
- lib/gitlab/experiment/rollout/round_robin.rb
|
|
109
89
|
- lib/gitlab/experiment/rollout/percent.rb
|
|
110
|
-
- lib/gitlab/experiment/
|
|
111
|
-
- lib/gitlab/experiment/
|
|
112
|
-
- lib/gitlab/experiment/
|
|
113
|
-
- lib/gitlab/experiment/
|
|
114
|
-
- lib/gitlab/experiment
|
|
90
|
+
- lib/gitlab/experiment/rollout/round_robin.rb
|
|
91
|
+
- lib/gitlab/experiment/callbacks.rb
|
|
92
|
+
- lib/gitlab/experiment/test_behaviors
|
|
93
|
+
- lib/gitlab/experiment/test_behaviors/trackable.rb
|
|
94
|
+
- lib/gitlab/experiment.rb
|
|
115
95
|
- LICENSE.txt
|
|
116
96
|
- README.md
|
|
117
|
-
homepage: https://gitlab.com/gitlab-org/gitlab-experiment
|
|
97
|
+
homepage: https://gitlab.com/gitlab-org/ruby/gems/gitlab-experiment
|
|
118
98
|
licenses:
|
|
119
99
|
- MIT
|
|
120
100
|
metadata: {}
|
|
@@ -136,5 +116,5 @@ requirements: []
|
|
|
136
116
|
rubygems_version: 3.1.6
|
|
137
117
|
signing_key:
|
|
138
118
|
specification_version: 4
|
|
139
|
-
summary: GitLab
|
|
119
|
+
summary: GitLab experimentation library.
|
|
140
120
|
test_files: []
|