gitlab-experiment 0.6.5 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []