gitlab-experiment 0.4.0 → 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8524dff7f908e481923e3131f3dd67c34f426e31ed10171896ea10a209e0b74a
4
- data.tar.gz: 151f2e7f7bbf9692350c02f46f0c4c8cfa6f9c7ec05fdcb0fda50e34324c06fb
3
+ metadata.gz: 81476752521c6ead83308324fdcf360db7b8182bab340c9ffe7ad4eec21b998e
4
+ data.tar.gz: 006d94ff92104bef820be7d6c7c9638b20a6e0a4a533439fca056b4cba31a0bc
5
5
  SHA512:
6
- metadata.gz: 3f23698aabf3968c77f49cdb924125879663670c12d22c390a47f7c791687f87fb1211adf432777f66012e3be1db2e90d5c33a1dc0a156332bb8926938c4e587
7
- data.tar.gz: 51973494136edef02672c9086970fec2ecb1fb3a6716edb10d1cfe758172f8d256ecd6ef1b5cefdd50133898637e022af527fc7e20df2212b1f7210db2dd087e
6
+ metadata.gz: a5644f3cf6798ddc27034b8857cf03bb77904b3b5c8a80b84c398134ad143b919e16ddc23746a58e63b71d0464c92ec5cce682ddb2039e471b55cfeb42e3cd4a
7
+ data.tar.gz: 2dd2adf62427f96a9745d9681ee87b5621acf43fc4dae33ffefdadfad5af6ec3a0ad1e79e601a4d6260e61fa64faf635af9ae753e3fb474e033af7d2d7d55381
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
- # GitLab Experiment
1
+ GitLab Experiment
2
+ =================
2
3
 
3
4
  <img alt="experiment" src="/uploads/60990b2dbf4c0406bbf8b7f998de2dea/experiment.png" align="right" width="40%">
4
5
 
@@ -16,6 +17,8 @@ When we discuss the behavior of this gem, we'll use terms like experiment, conte
16
17
 
17
18
  Candidate and variant are the same concept, but simplify how we speak about experimental paths.<br clear="all">
18
19
 
20
+ [[_TOC_]]
21
+
19
22
  ## Installation
20
23
 
21
24
  Add the gem to your Gemfile and then `bundle install`.
@@ -24,7 +27,7 @@ Add the gem to your Gemfile and then `bundle install`.
24
27
  gem 'gitlab-experiment'
25
28
  ```
26
29
 
27
- If you're using Rails, you can install the initializer. It provides basic configuration and documentation.
30
+ If you're using Rails, you can install the initializer which provides basic configuration, documentation, and the base experiment class that all your experiments can inherit from.
28
31
 
29
32
  ```shell
30
33
  $ rails generate gitlab:experiment:install
@@ -79,72 +82,84 @@ To this end, we track events that are important by calling the same experiment e
79
82
  experiment(:notification_toggle, actor: user).track(:clicked_button)
80
83
  ```
81
84
 
82
- <details>
83
- <summary>You can also use the more low level class or instance interfaces...</summary>
85
+ ### Custom experiments
84
86
 
85
- ### Class level interface using `.run`
87
+ You can craft more advanced behaviors by defining custom experiments at a higher level. To do this you can define a class that inherits from `ApplicationExperiment` (or `Gitlab::Experiment`).
86
88
 
87
- ```ruby
88
- exp = Gitlab::Experiment.run(:notification_toggle, actor: user) do |e|
89
- # Context may be passed in the block, but must be finalized before calling
90
- # run or track.
91
- e.context(project: project) # add the project to the context
89
+ Let's say you want to do more advanced segmentation, or provide default behavior for the variants on the experiment we've already outlined above -- that way if the variants aren't defined in the block at the time the experiment is run, these methods will be used.
92
90
 
93
- # Define the control and candidate variant.
94
- e.use { render_toggle } # control
95
- e.try { render_button } # candidate
96
- end
91
+ You can generate a custom experiment by running:
97
92
 
98
- # Track an event on the experiment we've defined.
99
- exp.track(:clicked_button)
100
- ```
93
+ ```shell
94
+ $ rails generate gitlab:experiment NotificationToggle control candidate
95
+ ```
96
+
97
+ This will generate a file in `app/experiments/notification_toggle_experiment.rb`, as well as a test file for you to further expand on.
101
98
 
102
- ### Instance level interface
99
+ Here are some examples of what you can introduce once you have a custom experiment defined.
103
100
 
104
101
  ```ruby
105
- exp = Gitlab::Experiment.new(:notification_toggle, actor: user)
106
- # Additional context may be provided to the instance (exp) but must be
107
- # finalized before calling run or track.
108
- exp.context(project: project) # add the project id to the context
102
+ class NotificationToggleExperiment < ApplicationExperiment
103
+ # Segment any account less than 2 weeks old into the candidate, without
104
+ # asking the variant resolver to decide which variant to provide.
105
+ segment :account_age, variant: :candidate
106
+
107
+ # Define the default control behavior, which can be overridden at
108
+ # experiment time.
109
+ def control_behavior
110
+ render_toggle
111
+ end
112
+
113
+ # Define the default candidate behavior, which can be overridden
114
+ # at experiment time.
115
+ def candidate_behavior
116
+ render_button
117
+ end
118
+
119
+ private
109
120
 
110
- # Define the control and candidate variant.
111
- exp.use { render_toggle } # control
112
- exp.try { render_button } # candidate
121
+ def account_age
122
+ context.actor && context.actor.created_at < 2.weeks.ago
123
+ end
124
+ end
113
125
 
114
- # Run the experiment, returning the result.
126
+ # The class will be looked up based on the experiment name provided.
127
+ exp = experiment(:notification_toggle, actor: user)
128
+ exp # => instance of NotificationToggleExperiment
129
+
130
+ # Run the experiment -- returning the result.
115
131
  exp.run
116
132
 
117
133
  # Track an event on the experiment we've defined.
118
134
  exp.track(:clicked_button)
119
135
  ```
120
136
 
121
- </details>
137
+ You can now also do things very similar to the simple examples and override the default variant behaviors defined in the custom experiment -- keeping in mind that this should be carefully considered within the scope of your experiment.
138
+
139
+ ```ruby
140
+ experiment(:notification_toggle, actor: user) do |e|
141
+ e.use { render_special_toggle } # override default control behavior
142
+ end
143
+ ```
122
144
 
123
145
  <details>
124
- <summary>You can define and use custom classes...</summary>
146
+ <summary>You can also use the lower level class interface...</summary>
125
147
 
126
- ### Custom class
148
+ ### Using the `.run` approach
127
149
 
128
- ```ruby
129
- class NotificationExperiment < Gitlab::Experiment
130
- def initialize(variant_name = nil, **context, &block)
131
- super(:notification_toggle, variant_name, **context, &block)
150
+ This is useful if you haven't included the DSL and so don't have access to the `experiment` method, but still want to execute an experiment. This is ultimately what the `experiment` method calls through to, and the method signatures are the same.
132
151
 
133
- # Define the control and candidate variant.
134
- use { render_toggle } # control
135
- try { render_button } # candidate
136
- end
137
- end
152
+ ```ruby
153
+ exp = Gitlab::Experiment.run(:notification_toggle, actor: user) do |e|
154
+ # Context may be passed in the block, but must be finalized before calling
155
+ # run or track.
156
+ e.context(project: project) # add the project to the context
138
157
 
139
- exp = NotificationExperiment.new(actor: user) do |e|
140
- # Context may be provided within the block or to the instance (exp) but must
141
- # be finalized before calling run or track.
142
- e.context(project: project) # add the project id to the context
158
+ # Define the control and candidate variant.
159
+ e.use { render_toggle } # control
160
+ e.try { render_button } # candidate
143
161
  end
144
162
 
145
- # Run the experiment -- returning the result.
146
- exp.run
147
-
148
163
  # Track an event on the experiment we've defined.
149
164
  exp.track(:clicked_button)
150
165
  ```
@@ -152,11 +167,11 @@ exp.track(:clicked_button)
152
167
  </details>
153
168
 
154
169
  <details>
155
- <summary>You can also specify the variant to use...</summary>
170
+ <summary>You can also specify the variant to use for segmentation...</summary>
156
171
 
157
172
  ### Specifying variant
158
173
 
159
- You can hardcode the variant if you want. It's important to know what this might do to your data during rollout, so use this with consideration.
174
+ Generally, defining segmentation rules is a better way to approach routing into specific variants, but it's possible to explicitly specify the variant when running an experiment. It's important to know what this might do to your data during rollout, so use this with careful consideration.
160
175
 
161
176
  ```ruby
162
177
  experiment(:notification_toggle, :no_interface, actor: user) do |e|
@@ -188,6 +203,25 @@ end
188
203
 
189
204
  </details>
190
205
 
206
+ ### Segmentation rules
207
+
208
+ This library comes with the capability to segment contexts into a specific variant, before asking the variant resolver which variant to provide.
209
+
210
+ Segmentation can be achieved by using a custom experiment class and specifying the segmentation rules at a class level.
211
+
212
+ ```ruby
213
+ class NotificationToggleExperiment < ApplicationExperiment
214
+ segment(variant: :variant_one) { context.actor.username == 'jejacks0n' }
215
+ segment(variant: :variant_two) { context.actor.created_at < 2.weeks.ago }
216
+ end
217
+ ```
218
+
219
+ In the previous examples, any user with the username `'jejacks0n'` would always receive the experience defined in "variant_one". As well, any account less than 2 weeks old would get the alternate experience defined in "variant_two".
220
+
221
+ When an experiment is run, the segmentation rules are executed in the order they're defined. The first segmentation rule to produce a truthy result is the one which gets used to assign the variant. The remaining segmentation rules are skipped.
222
+
223
+ This means that any user with the name `'jejacks0n'`, regardless of account age, will always be provided the experience as defined in "variant_one".
224
+
191
225
  ### Return value
192
226
 
193
227
  By default the return value is a `Gitlab::Experiment` instance. In simple cases you may want only the results of the experiment though. You can call `run` within the block to get the return value of the assigned variant.
@@ -307,7 +341,7 @@ Gitlab::Experiment.configure do |config|
307
341
  end
308
342
  ```
309
343
 
310
- More examples for configuration are available in the provided [rails initializer](lib/generators/gitlab/experiment/install/templates/initializer.rb).
344
+ More examples for configuration are available in the provided [rails initializer](lib/generators/gitlab/experiment/install/templates/initializer.rb.tt).
311
345
 
312
346
  ### Client layer / JavaScript
313
347
 
@@ -351,4 +385,32 @@ If you only include a user, that user would get the same experience across every
351
385
 
352
386
  Each of these approaches could be desirable given the objectives of your experiment.
353
387
 
354
- ### Make code not war
388
+ ## Development
389
+
390
+ After checking out the repo, run `bundle install` to install dependencies.
391
+ Then, run `bundle exec rake` to run the tests. You can also run `bundle exec pry` for an
392
+ interactive prompt that will allow you to experiment.
393
+
394
+ ## Contributing
395
+
396
+ Bug reports and merge requests are welcome on GitLab at
397
+ https://gitlab.com/gitlab-org/gitlab-experiment. This project is intended to be a
398
+ safe, welcoming space for collaboration, and contributors are expected to adhere
399
+ to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
400
+
401
+ ## Release Process
402
+
403
+ Please refer to the [Release Process](docs/release_process.md).
404
+
405
+ ## License
406
+
407
+ The gem is available as open source under the terms of the
408
+ [MIT License](http://opensource.org/licenses/MIT).
409
+
410
+ ## Code of Conduct
411
+
412
+ Everyone interacting in the `Gitlab::Experiment` project’s codebases, issue trackers,
413
+ chat rooms and mailing lists is expected to follow the
414
+ [code of conduct](CODE_OF_CONDUCT.md).
415
+
416
+ ***Make code not war***
@@ -7,44 +7,41 @@ Gitlab::Experiment.configure do |config|
7
7
  # The logger is used to log various details of the experiments.
8
8
  config.logger = Logger.new($stdout)
9
9
 
10
- # The base class that should be instantiated for basic experiments.
10
+ # The base class that should be instantiated for basic experiments. It should
11
+ # be a string, so we can constantize it later.
11
12
  config.base_class = 'ApplicationExperiment'
12
13
 
13
- # The caching layer is expected to respond to fetch, like Rails.cache.
14
+ # The caching layer is expected to respond to fetch, like Rails.cache for
15
+ # instance -- or anything that adheres to ActiveSupport::Cache::Store.
14
16
  config.cache = nil
15
17
 
18
+ # The domain to use on cookies.
19
+ #
20
+ # When not set, it uses the current host. If you want to provide specific
21
+ # hosts, you use `:all`, or provide an array like
22
+ # `['www.gitlab.com', '.gitlab.com']`.
23
+ config.cookie_domain = :all
24
+
16
25
  # Logic this project uses to resolve a variant for a given experiment.
17
26
  #
18
- # This can return an instance of any object that responds to `name`, or can
19
- # return a variant name as a string, in which case the build in variant
20
- # class will be used.
27
+ # Should return a symbol or string that represents the variant that should
28
+ # be assigned. Blank or nil values will be defaulted to the control.
21
29
  #
22
- # This block will be executed within the scope of the experiment instance,
23
- # so can easily access experiment methods, like getting the name or context.
30
+ # This block is executed within the scope of the experiment and so can access
31
+ # experiment methods, like `name`, `context`, and `signature`.
24
32
  config.variant_resolver = lambda do |requested_variant|
25
33
  # Run the control, unless a variant was requested in code:
26
- requested_variant || 'control'
34
+ requested_variant
27
35
 
28
36
  # Run the candidate, unless a variant was requested, with a fallback:
29
37
  #
30
- # requested_variant || variant_names.first || 'control'
31
-
32
- # Using Unleash to determine the variant:
33
- #
34
- # fallback = Unleash::Variant.new(name: requested_variant || 'control', enabled: true)
35
- # Unleash.get_variant(name, context.value, fallback)
36
-
37
- # Using Flipper to determine the variant:
38
- #
39
- # TODO: provide example.
40
- # Variant.new(name: requested_variant || 'control')
38
+ # requested_variant || variant_names.first || nil
41
39
  end
42
40
 
43
41
  # Tracking behavior can be implemented to link an event to an experiment.
44
42
  #
45
- # Similar to the variant_resolver, this is called within the scope of the
46
- # experiment instance and so can access any methods on the experiment,
47
- # such as name and signature.
43
+ # This block is executed within the scope of the experiment and so can access
44
+ # experiment methods, like `name`, `context`, and `signature`.
48
45
  config.tracking_behavior = lambda do |event, args|
49
46
  # An example of using a generic logger to track events:
50
47
  config.logger.info "Gitlab::Experiment[#{name}] #{event}: #{args.merge(signature: signature)}"
@@ -61,8 +58,11 @@ Gitlab::Experiment.configure do |config|
61
58
  # Called at the end of every experiment run, with the result.
62
59
  #
63
60
  # You may want to track that you've assigned a variant to a given context,
64
- # or push the experiment into the client or publish results elsewhere, like
65
- # into redis. Also called within the scope of the experiment instance.
61
+ # or push the experiment into the client or publish results elsewhere like
62
+ # into redis.
63
+ #
64
+ # This block is executed within the scope of the experiment and so can access
65
+ # experiment methods, like `name`, `context`, and `signature`.
66
66
  config.publishing_behavior = lambda do |result|
67
67
  # Track the event using our own configured tracking logic.
68
68
  track(:assignment)
@@ -83,14 +83,11 @@ Gitlab::Experiment.configure do |config|
83
83
  # Given a specific context hash map, we need to generate a consistent hash
84
84
  # key. The logic in here will be used for generating cache keys, and may also
85
85
  # be used when determining which variant may be presented.
86
+ #
87
+ # This block is executed within the scope of the experiment and so can access
88
+ # experiment methods, like `name`, `context`, and `signature`.
86
89
  config.context_hash_strategy = lambda do |context|
87
90
  values = context.values.map { |v| (v.respond_to?(:to_global_id) ? v.to_global_id : v).to_s }
88
91
  Digest::MD5.hexdigest((context.keys + values).join('|'))
89
92
  end
90
-
91
- # The domain for which this cookie applies so you can restrict to the domain level.
92
- #
93
- # When not set, it uses the current host. If you want to provide specific hosts, you can
94
- # provide them either via an array like `['www.gitlab.com', .gitlab.com']`, or set it to `:all`.
95
- config.cookie_domain = :all
96
93
  end
@@ -30,7 +30,7 @@ module Gitlab
30
30
  raise ArgumentError, 'name is required' if name.nil? && base?
31
31
 
32
32
  instance = constantize(name).new(name, variant_name, **context, &block)
33
- return instance unless block_given?
33
+ return instance unless block
34
34
 
35
35
  instance.context.frozen? ? instance.run : instance.tap(&:run)
36
36
  end
@@ -58,9 +58,9 @@ module Gitlab
58
58
  raise ArgumentError, 'name is required' if name.blank? && self.class.base?
59
59
 
60
60
  @name = self.class.experiment_name(name, suffix: false)
61
- @variant_name = variant_name
62
61
  @excluded = []
63
- @context = Context.new(self, context)
62
+ @context = Context.new(self, **context)
63
+ @variant_name = cache_variant(variant_name) { nil } if variant_name.present?
64
64
 
65
65
  exclude { !@context.trackable? }
66
66
  compare { false }
@@ -76,10 +76,22 @@ module Gitlab
76
76
  end
77
77
 
78
78
  def variant(value = nil)
79
- return @variant_name = value unless value.nil?
79
+ if value.blank? && @variant_name || @resolving_variant
80
+ return Variant.new(name: (@variant_name || :unresolved).to_s)
81
+ end
82
+
83
+ @variant_name = value unless value.blank?
84
+ @variant_name ||= :control if excluded?
85
+
86
+ @resolving_variant = true
87
+ resolved = :control
88
+ if (result = cache_variant(@variant_name) { resolve_variant_name }).present?
89
+ @variant_name = resolved = result.to_sym
90
+ end
80
91
 
81
- result = instance_exec(@variant_name, &Configuration.variant_resolver)
82
- result.respond_to?(:name) ? result : Variant.new(name: result.to_s)
92
+ Variant.new(name: resolved.to_s)
93
+ ensure
94
+ @resolving_variant = false
83
95
  end
84
96
 
85
97
  def exclude(&block)
@@ -88,19 +100,9 @@ module Gitlab
88
100
 
89
101
  def run(variant_name = nil)
90
102
  @result ||= begin
91
- @variant_name = variant_name unless variant_name.nil?
92
- @variant_name ||= :control if excluded?
93
-
94
- chain = variant_assigned? ? :unsegmented_run : :segmented_run
95
- run_callbacks(chain) do
96
- variant_name = cache { variant.name }
97
-
98
- method_name = "#{variant_name}_behavior"
99
- if respond_to?(method_name)
100
- behaviors[variant_name] ||= -> { send(method_name) } # rubocop:disable GitlabSecurity/PublicSend
101
- end
102
-
103
- super(variant_name)
103
+ variant_name = variant(variant_name).name
104
+ run_callbacks(variant_assigned? ? :unsegmented_run : :segmented_run) do
105
+ super(@variant_name ||= variant_name)
104
106
  end
105
107
  end
106
108
  end
@@ -123,6 +125,20 @@ module Gitlab
123
125
  @variant_names ||= behaviors.keys.map(&:to_sym) - [:control]
124
126
  end
125
127
 
128
+ def behaviors
129
+ @behaviors ||= public_methods.each_with_object(super) do |name, behaviors|
130
+ next unless name.end_with?('_behavior')
131
+
132
+ behavior_name = name.to_s.sub(/_behavior$/, '')
133
+ behaviors[behavior_name] ||= -> { send(name) } # rubocop:disable GitlabSecurity/PublicSend
134
+ end
135
+ end
136
+
137
+ def try(name = nil, &block)
138
+ name = (name || 'candidate').to_s
139
+ behaviors[name] = block
140
+ end
141
+
126
142
  def signature
127
143
  { variant: variant.name, experiment: name }.merge(context.signature)
128
144
  end
@@ -140,7 +156,7 @@ module Gitlab
140
156
  end
141
157
 
142
158
  def id
143
- "#{name}:#{signature[:key]}"
159
+ "#{name}:#{key_for(context.value)}"
144
160
  end
145
161
  alias_method :session_id, :id
146
162
 
@@ -154,6 +170,10 @@ module Gitlab
154
170
 
155
171
  protected
156
172
 
173
+ def resolve_variant_name
174
+ instance_exec(@variant_name, &Configuration.variant_resolver)
175
+ end
176
+
157
177
  def generate_result(variant_name)
158
178
  observation = Scientist::Observation.new(variant_name, self, &behaviors[variant_name])
159
179
  Scientist::Result.new(self, [observation], observation)
@@ -3,20 +3,26 @@
3
3
  module Gitlab
4
4
  class Experiment
5
5
  module Caching
6
- def cache(&block)
7
- return yield unless (cache = Configuration.cache)
6
+ def cache_variant(specified = nil, &block)
7
+ cache = Configuration.cache
8
+ return (specified.presence || yield) unless cache
8
9
 
9
10
  key, migrations = cache_strategy
10
- migrated_cache(cache, migrations || [], key) or cache.fetch(key, &block)
11
+ result = migrated_cache(cache, migrations || [], key) || cache.fetch(key, &block)
12
+ return result unless specified.present?
13
+
14
+ cache.write(cache_key, specified) if result != specified
15
+ specified
16
+ end
17
+
18
+ def cache_key(key = nil)
19
+ "#{name}:#{key || context.signature[:key]}"
11
20
  end
12
21
 
13
22
  private
14
23
 
15
24
  def cache_strategy
16
- [
17
- "#{name}:#{signature[:key]}",
18
- signature[:migration_keys]&.map { |key| "#{name}:#{key}" }
19
- ]
25
+ [cache_key, context.signature[:migration_keys]&.map { |key| cache_key(key) }]
20
26
  end
21
27
 
22
28
  def migrated_cache(cache, migrations, new_key)
@@ -7,31 +7,20 @@ module Gitlab
7
7
  include ActiveSupport::Callbacks
8
8
 
9
9
  included do
10
- define_callbacks(
11
- :unsegmented_run,
12
- skip_after_callbacks_if_terminated: true
13
- )
14
-
15
- define_callbacks(
16
- :segmented_run,
17
- skip_after_callbacks_if_terminated: false,
18
- terminator: lambda do |target, result_lambda|
19
- result_lambda.call
20
- target.variant_assigned?
21
- end
22
- )
10
+ define_callbacks(:unsegmented_run)
11
+ define_callbacks(:segmented_run)
23
12
  end
24
13
 
25
14
  class_methods do
26
15
  def segment(*filter_list, variant:, **options, &block)
27
16
  filters = filter_list.unshift(block).compact.map do |filter|
28
17
  result_lambda = ActiveSupport::Callbacks::CallTemplate.build(filter, self).make_lambda
29
- ->(target) { target.variant(variant) if result_lambda.call(target, nil) }
18
+ ->(target) { target.variant(variant) if !target.variant_assigned? && result_lambda.call(target, nil) }
30
19
  end
31
20
 
32
21
  raise ArgumentError, 'no filters provided' if filters.empty?
33
22
 
34
- set_callback(:segmented_run, :before, *filters, options, &block)
23
+ set_callback(:segmented_run, :before, *filters, options)
35
24
  end
36
25
  end
37
26
  end
@@ -18,12 +18,16 @@ module Gitlab
18
18
  # The base class that should be instantiated for basic experiments.
19
19
  @base_class = 'Gitlab::Experiment'
20
20
 
21
- # Cache layer. Expected to respond to fetch, like Rails.cache.
21
+ # The caching layer is expected to respond to fetch, like Rails.cache.
22
22
  @cache = nil
23
23
 
24
+ # The domain to use on cookies.
25
+ @cookie_domain = :all
26
+
24
27
  # Logic this project uses to resolve a variant for a given experiment.
28
+ # If no variant is determined, the control will be used.
25
29
  @variant_resolver = lambda do |requested_variant|
26
- requested_variant || 'control'
30
+ requested_variant
27
31
  end
28
32
 
29
33
  # Tracking behavior can be implemented to link an event to an experiment.
@@ -31,8 +35,7 @@ module Gitlab
31
35
  Configuration.logger.info "Gitlab::Experiment[#{name}] #{event}: #{args.merge(signature: signature)}"
32
36
  end
33
37
 
34
- # Called at the end of every experiment run, with the results. You may
35
- # want to push the experiment into the client or push results elsewhere.
38
+ # Called at the end of every experiment run, with the result.
36
39
  @publishing_behavior = lambda do |_result|
37
40
  track(:assignment)
38
41
  end
@@ -43,22 +46,17 @@ module Gitlab
43
46
  Digest::MD5.hexdigest(([name] + hash_map.keys + values).join('|'))
44
47
  end
45
48
 
46
- # The domain for which this cookie applies so you can restrict to the domain level.
47
- # When not set, it uses the current host. If you want to provide specific hosts, you can
48
- # provide them either via an array like `['www.gitlab.com', .gitlab.com']`, or set it to `:all`.
49
- @cookie_domain = :all
50
-
51
49
  class << self
52
50
  attr_accessor(
53
51
  :name_prefix,
54
52
  :logger,
55
53
  :base_class,
56
54
  :cache,
55
+ :cookie_domain,
57
56
  :variant_resolver,
58
57
  :tracking_behavior,
59
58
  :publishing_behavior,
60
- :context_hash_strategy,
61
- :cookie_domain
59
+ :context_hash_strategy
62
60
  )
63
61
  end
64
62
  end
@@ -11,7 +11,7 @@ module Gitlab
11
11
  return hash if cookie_jar.nil?
12
12
 
13
13
  resolver = [hash, :actor, cookie_name, cookie_jar.signed[cookie_name]]
14
- resolve_cookie(*resolver) or generate_cookie(*resolver)
14
+ resolve_cookie(*resolver) || generate_cookie(*resolver)
15
15
  end
16
16
 
17
17
  def cookie_jar
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Gitlab
4
4
  class Experiment
5
- VERSION = '0.4.0'
5
+ VERSION = '0.4.5'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-experiment
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitLab
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-17 00:00:00.000000000 Z
11
+ date: 2021-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport