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.
@@ -1,12 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'scientist'
4
3
  require 'request_store'
5
- require 'active_support/callbacks'
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
- def default_rollout(rollout = nil, options = {})
36
- return @rollout ||= Configuration.default_rollout if rollout.blank?
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
- @rollout = Rollout.resolve(rollout).new(options)
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
- build_callback(:exclusion_check, filter_list.unshift(block), **options) do |target, callback|
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
- build_callback(:segmentation_check, filter_list.unshift(block), **options) do |target, callback|
49
- target.variant(variant) if target.instance_variable_get(:@variant_name).nil? && callback.call(target, nil)
50
- end
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, @name].compact.join('_')
82
+ [Configuration.name_prefix, @_name].compact.join('_')
60
83
  end
61
84
 
62
85
  def control(&block)
63
- candidate(:control, &block)
86
+ variant(:control, &block)
64
87
  end
65
- alias_method :use, :control
66
88
 
67
89
  def candidate(name = nil, &block)
68
- name = (name || :candidate).to_s
69
- behaviors[name] = block
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 @context if value.blank?
120
+ return @_context if value.blank?
75
121
 
76
- @context.value(value)
77
- @context
122
+ @_context.value(value)
123
+ @_context
78
124
  end
79
125
 
80
- def variant(value = nil)
81
- @variant_name = cache_variant(value) if value.present?
82
- return Variant.new(name: (@variant_name || :unresolved).to_s) if @variant_name || @resolving_variant
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
- @resolving_variant = true
86
- @variant_name = cached_variant_resolver(@variant_name)
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
- @variant_name ||= :control
91
- Variant.new(name: @variant_name.to_s)
138
+ @_assigned_variant_name ||= :control
139
+ Variant.new(name: @_assigned_variant_name.to_s)
92
140
  end
93
141
  ensure
94
- @resolving_variant = false
142
+ @_resolving_variant = false
95
143
  end
96
144
 
97
145
  def rollout(rollout = nil, options = {})
98
- return @rollout ||= self.class.default_rollout(nil, options) if rollout.blank?
146
+ return @_rollout ||= self.class.default_rollout(nil, options).for(self) if rollout.blank?
99
147
 
100
- @rollout = Rollout.resolve(rollout).new(options)
148
+ @_rollout = Rollout.resolve(rollout).new(options).for(self)
101
149
  end
102
150
 
103
151
  def exclude!
104
- @excluded = true
152
+ @_excluded = true
105
153
  end
106
154
 
107
155
  def run(variant_name = nil)
108
- return @result if context.frozen?
156
+ return @_result if context.frozen?
109
157
 
110
- @result = run_callbacks(:run) { super(variant(variant_name).name) }
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
- true
181
+ rollout.enabled?
136
182
  end
137
183
 
138
184
  def excluded?
139
- return @excluded if defined?(@excluded)
140
-
141
- @excluded = !run_callbacks(:exclusion_check) { :not_excluded }
142
- end
185
+ return @_excluded if defined?(@_excluded)
143
186
 
144
- def experiment_group?
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? && @context.trackable? && !excluded?
191
+ enabled? && context.trackable? && !excluded?
150
192
  end
151
193
 
152
194
  def signature
153
- { variant: variant.name, experiment: name }.merge(context.signature)
195
+ { variant: assigned.name, experiment: name }.merge(context.signature)
154
196
  end
155
197
 
156
198
  def key_for(source, seed = name)
157
- # TODO: Added deprecation in release 0.6.0
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 segmentation_callback_chain
179
- return :segmentation_check if @variant_name.nil? && enabled? && !excluded?
180
-
181
- :unsegmented
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 resolve_variant_name
185
- rollout.rollout_for(self) if experiment_group?
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.6.5
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/gitlab/experiment.rb
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/variant.rb
93
- - lib/gitlab/experiment/middleware.rb
94
- - lib/gitlab/experiment/test_behaviors
95
- - lib/gitlab/experiment/test_behaviors/trackable.rb
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/rspec.rb
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/cache.rb
111
- - lib/gitlab/experiment/version.rb
112
- - lib/gitlab/experiment/cookies.rb
113
- - lib/gitlab/experiment/configuration.rb
114
- - lib/gitlab/experiment/dsl.rb
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 experiment library built on top of scientist.
119
+ summary: GitLab experimentation library.
140
120
  test_files: []