gitlab-experiment 0.6.2 → 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 -290
- 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 +87 -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 -5
- data/lib/gitlab/experiment/configuration.rb +196 -28
- data/lib/gitlab/experiment/context.rb +9 -8
- data/lib/gitlab/experiment/cookies.rb +1 -3
- data/lib/gitlab/experiment/engine.rb +7 -3
- data/lib/gitlab/experiment/errors.rb +21 -0
- data/lib/gitlab/experiment/nestable.rb +41 -0
- 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 +216 -127
- data/lib/gitlab/experiment/test_behaviors/trackable.rb +69 -0
- data/lib/gitlab/experiment/version.rb +1 -1
- data/lib/gitlab/experiment.rb +117 -56
- metadata +36 -53
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'
|
@@ -19,6 +14,7 @@ require 'gitlab/experiment/cookies'
|
|
19
14
|
require 'gitlab/experiment/context'
|
20
15
|
require 'gitlab/experiment/dsl'
|
21
16
|
require 'gitlab/experiment/middleware'
|
17
|
+
require 'gitlab/experiment/nestable'
|
22
18
|
require 'gitlab/experiment/variant'
|
23
19
|
require 'gitlab/experiment/version'
|
24
20
|
require 'gitlab/experiment/engine' if defined?(Rails::Engine)
|
@@ -28,87 +24,141 @@ module Gitlab
|
|
28
24
|
include BaseInterface
|
29
25
|
include Cache
|
30
26
|
include Callbacks
|
27
|
+
include Nestable
|
31
28
|
|
32
29
|
class << self
|
33
|
-
|
34
|
-
|
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
|
35
39
|
|
36
|
-
|
40
|
+
def variant(variant, *filter_list, **options, &block)
|
41
|
+
build_behavior_callback(filter_list, variant, **options, &block)
|
37
42
|
end
|
38
43
|
|
44
|
+
# Class level callback registration methods.
|
45
|
+
|
39
46
|
def exclude(*filter_list, **options, &block)
|
40
|
-
|
41
|
-
throw(:abort) if target.instance_variable_get(:@excluded) || callback.call(target, nil) == true
|
42
|
-
end
|
47
|
+
build_exclude_callback(filter_list.unshift(block), **options)
|
43
48
|
end
|
44
49
|
|
45
50
|
def segment(*filter_list, variant:, **options, &block)
|
46
|
-
|
47
|
-
|
48
|
-
|
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)
|
49
64
|
end
|
50
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)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Class level accessor methods.
|
75
|
+
|
51
76
|
def published_experiments
|
52
77
|
RequestStore.store[:published_gitlab_experiments] || {}
|
53
78
|
end
|
54
79
|
end
|
55
80
|
|
56
81
|
def name
|
57
|
-
[Configuration.name_prefix, @
|
82
|
+
[Configuration.name_prefix, @_name].compact.join('_')
|
58
83
|
end
|
59
84
|
|
60
85
|
def control(&block)
|
61
|
-
|
86
|
+
variant(:control, &block)
|
62
87
|
end
|
63
|
-
alias_method :use, :control
|
64
88
|
|
65
89
|
def candidate(name = nil, &block)
|
66
|
-
|
67
|
-
|
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)
|
68
117
|
end
|
69
|
-
alias_method :try, :candidate
|
70
118
|
|
71
119
|
def context(value = nil)
|
72
|
-
return @
|
120
|
+
return @_context if value.blank?
|
73
121
|
|
74
|
-
@
|
75
|
-
@
|
122
|
+
@_context.value(value)
|
123
|
+
@_context
|
76
124
|
end
|
77
125
|
|
78
|
-
def
|
79
|
-
@
|
80
|
-
|
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
|
81
131
|
|
82
132
|
if enabled?
|
83
|
-
@
|
84
|
-
@
|
133
|
+
@_resolving_variant = true
|
134
|
+
@_assigned_variant_name = cached_variant_resolver(@_assigned_variant_name)
|
85
135
|
end
|
86
136
|
|
87
137
|
run_callbacks(segmentation_callback_chain) do
|
88
|
-
@
|
89
|
-
Variant.new(name: @
|
138
|
+
@_assigned_variant_name ||= :control
|
139
|
+
Variant.new(name: @_assigned_variant_name.to_s)
|
90
140
|
end
|
91
141
|
ensure
|
92
|
-
@
|
142
|
+
@_resolving_variant = false
|
93
143
|
end
|
94
144
|
|
95
145
|
def rollout(rollout = nil, options = {})
|
96
|
-
return @
|
146
|
+
return @_rollout ||= self.class.default_rollout(nil, options).for(self) if rollout.blank?
|
97
147
|
|
98
|
-
@
|
148
|
+
@_rollout = Rollout.resolve(rollout).new(options).for(self)
|
99
149
|
end
|
100
150
|
|
101
151
|
def exclude!
|
102
|
-
@
|
152
|
+
@_excluded = true
|
103
153
|
end
|
104
154
|
|
105
155
|
def run(variant_name = nil)
|
106
|
-
@
|
107
|
-
|
108
|
-
|
156
|
+
return @_result if context.frozen?
|
157
|
+
|
158
|
+
@_result = run_callbacks(run_callback_chain) { super(assigned(variant_name).name) }
|
109
159
|
end
|
110
160
|
|
111
|
-
def publish(result)
|
161
|
+
def publish(result = nil)
|
112
162
|
instance_exec(result, &Configuration.publishing_behavior)
|
113
163
|
|
114
164
|
(RequestStore.store[:published_gitlab_experiments] ||= {})[name] = signature.merge(excluded: excluded?)
|
@@ -117,7 +167,7 @@ module Gitlab
|
|
117
167
|
def track(action, **event_args)
|
118
168
|
return unless should_track?
|
119
169
|
|
120
|
-
instance_exec(action, event_args, &Configuration.tracking_behavior)
|
170
|
+
instance_exec(action, tracking_context(event_args).try(:compact) || {}, &Configuration.tracking_behavior)
|
121
171
|
end
|
122
172
|
|
123
173
|
def process_redirect_url(url)
|
@@ -128,33 +178,31 @@ module Gitlab
|
|
128
178
|
end
|
129
179
|
|
130
180
|
def enabled?
|
131
|
-
|
181
|
+
rollout.enabled?
|
132
182
|
end
|
133
183
|
|
134
184
|
def excluded?
|
135
|
-
return @
|
185
|
+
return @_excluded if defined?(@_excluded)
|
136
186
|
|
137
|
-
@
|
138
|
-
end
|
139
|
-
|
140
|
-
def experiment_group?
|
141
|
-
instance_exec(@variant_name, &Configuration.inclusion_resolver)
|
187
|
+
@_excluded = !run_callbacks(exclusion_callback_chain) { :not_excluded }
|
142
188
|
end
|
143
189
|
|
144
190
|
def should_track?
|
145
|
-
enabled? &&
|
191
|
+
enabled? && context.trackable? && !excluded?
|
146
192
|
end
|
147
193
|
|
148
194
|
def signature
|
149
|
-
{ variant:
|
195
|
+
{ variant: assigned.name, experiment: name }.merge(context.signature)
|
150
196
|
end
|
151
197
|
|
152
198
|
def key_for(source, seed = name)
|
153
|
-
# TODO:
|
199
|
+
# TODO: Remove - deprecated in release 0.7.0
|
154
200
|
if (block = Configuration.instance_variable_get(:@__context_hash_strategy))
|
155
201
|
return instance_exec(source, seed, &block)
|
156
202
|
end
|
157
203
|
|
204
|
+
return source if source.is_a?(String)
|
205
|
+
|
158
206
|
source = source.keys + source.values if source.is_a?(Hash)
|
159
207
|
|
160
208
|
ingredients = Array(source).map { |v| identify(v) }
|
@@ -169,14 +217,27 @@ module Gitlab
|
|
169
217
|
(object.respond_to?(:to_global_id) ? object.to_global_id : object).to_s
|
170
218
|
end
|
171
219
|
|
172
|
-
def
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
176
237
|
end
|
177
238
|
|
178
|
-
def
|
179
|
-
|
239
|
+
def tracking_context(event_args)
|
240
|
+
{}.merge(event_args)
|
180
241
|
end
|
181
242
|
end
|
182
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,53 +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/
|
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
|
94
77
|
- lib/gitlab/experiment/cache
|
95
78
|
- lib/gitlab/experiment/cache/redis_hash_store.rb
|
96
|
-
- lib/gitlab/experiment/errors.rb
|
97
|
-
- lib/gitlab/experiment/callbacks.rb
|
98
|
-
- lib/gitlab/experiment/rollout.rb
|
99
|
-
- lib/gitlab/experiment/base_interface.rb
|
100
|
-
- lib/gitlab/experiment/context.rb
|
101
79
|
- lib/gitlab/experiment/engine.rb
|
102
|
-
- 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
|
103
87
|
- lib/gitlab/experiment/rollout
|
104
88
|
- lib/gitlab/experiment/rollout/random.rb
|
105
|
-
- lib/gitlab/experiment/rollout/round_robin.rb
|
106
89
|
- lib/gitlab/experiment/rollout/percent.rb
|
107
|
-
- lib/gitlab/experiment/
|
108
|
-
- lib/gitlab/experiment/
|
109
|
-
- lib/gitlab/experiment/
|
110
|
-
- lib/gitlab/experiment/
|
111
|
-
- 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
|
112
95
|
- LICENSE.txt
|
113
96
|
- README.md
|
114
|
-
homepage: https://gitlab.com/gitlab-org/gitlab-experiment
|
97
|
+
homepage: https://gitlab.com/gitlab-org/ruby/gems/gitlab-experiment
|
115
98
|
licenses:
|
116
99
|
- MIT
|
117
100
|
metadata: {}
|
@@ -130,8 +113,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
113
|
- !ruby/object:Gem::Version
|
131
114
|
version: '0'
|
132
115
|
requirements: []
|
133
|
-
rubygems_version: 3.1.
|
116
|
+
rubygems_version: 3.1.6
|
134
117
|
signing_key:
|
135
118
|
specification_version: 4
|
136
|
-
summary: GitLab
|
119
|
+
summary: GitLab experimentation library.
|
137
120
|
test_files: []
|