gitlab-experiment 0.9.1 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2ff3ecacc0f83a605ecaad79cef7368802307b9bc1c106eca1c74967ade3a00c
4
- data.tar.gz: a72ceadbbcd689a290bdf7f85b9dababa7f7ae252bc3886a6fcd42170e561ee7
3
+ metadata.gz: 3305c24f350ec02a3ef81da742a6d108c7aef00623f8b2534f989f06ec4a2ba6
4
+ data.tar.gz: 5f9113235e1623a2012ea5e4c451ed41baeb847e5883951b95b44fd6d7fd40fa
5
5
  SHA512:
6
- metadata.gz: 6d7f1804cf3503fd0fcf5be75ca6c4d3bfebcdccd3baf5aa3937a7c080e8336c15491a3476a9a7aef30060d0a526f4f71c6803d0a8ae8c08b0192e1401e1070c
7
- data.tar.gz: 4f739b5852733e789ab23ce0b96215d023e4483ef32ae055373bfe3083d212fb5f01aa8b45ee55bfbac0a8a734e5ec429eac175a49a0219256d6f8f62151027a
6
+ metadata.gz: d7120d36eb59039dbe36eb728990b951b96b7aa118a0a73af32e18809eaed265d03dec4689b55c3118e14a252418c9c84c4e25f7c25573205c6ef5269e5f456f
7
+ data.tar.gz: 9233969833c74c432b187721a2b35a38e521fce3679bccf64f879b87c899e6202b0f60b047c5aa57aef10665816f3fd7b3d21f7e403b30424b1227017c9991b2
data/README.md CHANGED
@@ -195,7 +195,9 @@ experiment(:pill_color, actor: User.first).run # => "red"
195
195
 
196
196
  ### Exclusion rules
197
197
 
198
- Exclusion rules let us determine if a context should even be considered as something to include in an experiment. If we're excluding something, it means that we don't want to run the experiment in that case. This can be useful if you only want to run experiments on new users for instance.
198
+ Exclusion rules let us determine if a context should even be considered as something to include in an experiment. If
199
+ we're excluding something, it means that we don't want to run the experiment in that case. This can be useful if you
200
+ only want to run experiments on new users for instance.
199
201
 
200
202
  ```ruby
201
203
  class PillColorExperiment < Gitlab::Experiment # OR ApplicationExperiment
@@ -205,15 +207,40 @@ class PillColorExperiment < Gitlab::Experiment # OR ApplicationExperiment
205
207
  end
206
208
  ```
207
209
 
208
- In the previous example, we'll exclude all users named `'Richard'` as well as any account older than 2 weeks old. Not only will they be immediately given the control behavior, but no events will be tracked in these cases either.
210
+ In the previous example, we'll exclude all users named `'Richard'` as well as any account older than 2 weeks old. Not
211
+ only will they be immediately given the control behavior, but no events will be tracked in these cases either.
209
212
 
210
- Exclusion rules are executed in the order they're defined. The first exclusion rule to produce a truthy result will halt execution of further exclusion checks.
213
+ Exclusion rules are executed in the order they're defined. The first exclusion rule to produce a truthy result will halt
214
+ execution of further exclusion checks.
211
215
 
212
- Note: Although tracking calls will be ignored on all exclusions, you may want to check exclusion yourself in expensive custom logic by calling the `should_track?` or `excluded?` methods.
216
+ #### Excluding from within the experiment block
213
217
 
214
- Note: When using exclusion rules it's important to understand that the control assignment is cached, which improves future experiment run performance but can be a gotcha around caching.
218
+ You can also exclude contexts dynamically from within the experiment block using the `exclude!` method. This provides a
219
+ convenient way to include exclusion logic directly within the experiment call:
215
220
 
216
- Note: Exclusion rules aren't the best way to determine if an experiment is enabled. There's an `enabled?` method that can be overridden to have a high-level way of determining if an experiment should be running and tracking at all. This `enabled?` check should be as efficient as possible because it's the first early opt out path an experiment can implement. This can be seen in [How it works](#how-it-works).
221
+ ```ruby
222
+ experiment(:pill_color, actor: current_user) do |e|
223
+ e.exclude! unless can?(current_user, :manage, project)
224
+
225
+ e.control { 'blue' }
226
+ e.candidate { 'red' }
227
+ end
228
+ ```
229
+
230
+ This approach keeps the experiment logic wrapped nicely within the experiment block, rather than requiring you to wrap
231
+ the entire experiment call in conditional logic. When `exclude!` is called, the experiment will be excluded and return
232
+ the control behavior without tracking any events.
233
+
234
+ Note: Although tracking calls will be ignored on all exclusions, you may want to check exclusion yourself in expensive
235
+ custom logic by calling the `should_track?` or `excluded?` methods.
236
+
237
+ Note: When using exclusion rules it's important to understand that the control assignment is cached, which improves
238
+ future experiment run performance but can be a gotcha around caching.
239
+
240
+ Note: Exclusion rules aren't the best way to determine if an experiment is enabled. There's an `enabled?` method that
241
+ can be overridden to have a high-level way of determining if an experiment should be running and tracking at all. This
242
+ `enabled?` check should be as efficient as possible because it's the first early opt out path an experiment can
243
+ implement. This can be seen in [How it works](#how-it-works).
217
244
 
218
245
  ### Segmentation rules
219
246
 
@@ -258,6 +285,73 @@ class PillColorExperiment < Gitlab::Experiment # OR ApplicationExperiment
258
285
  end
259
286
  ```
260
287
 
288
+ ### Checking assignment without assigning
289
+
290
+ Sometimes you may want to check if a user is already assigned to an experiment without assigning them to a variant if
291
+ they're not. This is useful when you want to show experiment-specific content only to users who are already
292
+ participating in the experiment, without expanding the experiment's reach.
293
+
294
+ You can use the `only_assigned` option to achieve this behavior:
295
+
296
+ ```haml
297
+ -# Only show experimental features to users already participating in the experiment
298
+ - experiment(:advanced_search, actor: current_user, only_assigned: true) do |e|
299
+ - e.control do
300
+ = render 'search_filters'
301
+ - e.candidate do
302
+ = render 'advanced_search_filters'
303
+ ```
304
+
305
+ When `only_assigned: true` is used:
306
+
307
+ - If the user has a cached variant assignment, the experiment runs normally and returns that variant
308
+ - If the user has no cached variant assignment, the experiment is excluded and returns the control behavior
309
+ - No new variant assignments are made
310
+ - Tracking is disabled for excluded cases
311
+ - Publishing still works to record the exclusion
312
+
313
+ This is particularly useful for:
314
+
315
+ - **Progressive rollouts**: Show experimental features only to users already in the experiment
316
+ - **Conditional UI**: Display experiment-specific UI elements only for assigned users
317
+ - **Feature gates**: Check experiment participation without expanding the participant pool
318
+ - **Cleanup phases**: Maintain experience for existing participants while preventing new assignments
319
+ - **Post-registration experiences**: When users are assigned to experiments during registration, you can later show
320
+ experimental features throughout their journey without expanding to existing users who weren't initially assigned
321
+
322
+ ```ruby
323
+ # During user registration - assign new users to experiment variants
324
+ class RegistrationsController < ApplicationController
325
+ def create
326
+ # ... user creation logic
327
+
328
+ # Assign new users to the experiment
329
+ experiment(:pill_color, actor: @user)
330
+ end
331
+ end
332
+ ```
333
+
334
+ ```haml
335
+ -# Later throughout the app - only show experimental features to assigned users
336
+ - experiment(:pill_color, actor: current_user, only_assigned: true) do |e|
337
+ - e.control do
338
+ - e.candidate do
339
+ = render 'quick_start_guide'
340
+ ```
341
+
342
+ You can also assign the result of the experiment to a variable:
343
+
344
+ ```ruby
345
+ # In a view helper or directly in the view
346
+ button_class = experiment(:pill_color, actor: current_user, only_assigned: true) do |e|
347
+ e.control { 'btn-default' }
348
+ e.candidate { 'btn-primary' }
349
+ end.run
350
+ ```
351
+
352
+ Note: The `only_assigned` option requires caching to be enabled in your experiment configuration, as it relies on
353
+ checking for cached variant assignments.
354
+
261
355
  ### Rollout strategies
262
356
 
263
357
  While a default rollout strategy can be defined in configuration, it's useful to be able to override this per experiment if needed. You can do this by specifying a specific `default_rollout` override in your experiment class.
@@ -274,7 +368,12 @@ Obviously random assignment might not be the best rollout strategy for you, but
274
368
 
275
369
  ## How it works
276
370
 
277
- The way experiments work is best described using the following decision tree diagram. When an experiment is run, the following logic is executed to resolve what experience should be provided, given how the experiment is defined, and using the context passed to the experiment call.
371
+ The way experiments work is best described using the following decision tree diagram. When an experiment is run, the
372
+ following logic is executed to resolve what experience should be provided, given how the experiment is defined, and
373
+ using the context passed to the experiment call.
374
+
375
+ Note: When using the `only_assigned` option, experiments that have no cached variant will be excluded, preventing new
376
+ variant assignments while maintaining existing ones.
278
377
 
279
378
  ```mermaid
280
379
  graph TD
@@ -292,11 +391,6 @@ graph TD
292
391
  Rollout -->|Cached| VariantB
293
392
  Rollout -->|Cached| VariantN
294
393
 
295
- classDef included fill:#380d75,color:#ffffff,stroke:none
296
- classDef excluded fill:#fca121,stroke:none
297
- classDef cached fill:#2e2e2e,color:#ffffff,stroke:none
298
- classDef default fill:#fff,stroke:#6e49cb
299
-
300
394
  class VariantA,VariantB,VariantN included
301
395
  class Control,Excluded excluded
302
396
  class Cached cached
@@ -514,7 +608,7 @@ class PillColorExperiment < Gitlab::Experiment # OR ApplicationExperiment
514
608
  end
515
609
  ```
516
610
 
517
- Now, enabling or disabling the Flipper feature flag will control if the experiment is enabled or not. If the experiment is enabled, as determined by our custom rollout strategy, the standard resolutuon logic will be executed, and a variant (or control) will be assigned.
611
+ Now, enabling or disabling the Flipper feature flag will control if the experiment is enabled or not. If the experiment is enabled, as determined by our custom rollout strategy, the standard resolution logic will be executed, and a variant (or control) will be assigned.
518
612
 
519
613
  ```ruby
520
614
  experiment(:pill_color).enabled? # => false
@@ -601,6 +695,30 @@ it "stubs experiments while allowing the rollout strategy to assign the variant"
601
695
  end
602
696
  ```
603
697
 
698
+ You can also test the `only_assigned` behavior by stubbing cache states:
699
+
700
+ ```ruby
701
+ it "tests only_assigned behavior with cached variants" do
702
+ # Stub a cached variant to exist
703
+ allow_any_instance_of(Gitlab::Experiment).to receive(:find_variant).and_return('red')
704
+
705
+ experiment_instance = experiment(:pill_color, actor: user, only_assigned: true)
706
+
707
+ expect(experiment_instance).not_to be_excluded
708
+ expect(experiment_instance.run).to eq('red')
709
+ end
710
+
711
+ it "tests only_assigned behavior without cached variants" do
712
+ # Stub no cached variant
713
+ allow_any_instance_of(Gitlab::Experiment).to receive(:find_variant).and_return(nil)
714
+
715
+ experiment_instance = experiment(:pill_color, actor: user, only_assigned: true)
716
+
717
+ expect(experiment_instance).to be_excluded
718
+ expect(experiment_instance.run).to eq('blue') # control behavior
719
+ end
720
+ ```
721
+
604
722
  ### Registered behaviors matcher
605
723
 
606
724
  It's useful to test our registered behaviors, as well as their return values when we implement anything complex in them. The `register_behavior` matcher is useful for this.
@@ -616,7 +734,7 @@ end
616
734
 
617
735
  ### Exclusion and segmentation matchers
618
736
 
619
- You can also easily test your experiment classes using the `exclude`, `segment` metchers.
737
+ You can also easily test your experiment classes using the `exclude`, `segment` matchers.
620
738
 
621
739
  ```ruby
622
740
  let(:excluded) { double(first_name: 'Richard', created_at: Time.current) }
@@ -679,12 +797,21 @@ Each of these approaches could be desirable given the objectives of your experim
679
797
 
680
798
  After cloning the repo, run `bundle install` to install dependencies.
681
799
 
682
- Run `bundle exec rake` to run the tests. You can also run `bundle exec pry` for an interactive prompt that will allow you to experiment.
800
+ ## Running tests
801
+
802
+ The test suite requires Redis to be running. [Install](https://redis.io/docs/latest/operate/oss_and_stack/install/archive/install-redis/) and start Redis (`redis-server`) before running tests.
803
+
804
+ Once Redis is running, execute the tests:
805
+ `bundle exec rake`
806
+
807
+ You can also run `bundle exec pry` for an interactive prompt that will allow you to experiment.
683
808
 
684
809
  ## Contributing
685
810
 
686
811
  Bug reports and merge requests are welcome on GitLab at https://gitlab.com/gitlab-org/ruby/gems/gitlab-experiment. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
687
812
 
813
+ Make sure to include a changelog entry in your commit message and read the [changelog entries section](https://docs.gitlab.com/ee/development/changelog.html).
814
+
688
815
  ## Release process
689
816
 
690
817
  Please refer to the [Release Process](docs/release_process.md).
@@ -31,6 +31,14 @@ Gitlab::Experiment.configure do |config|
31
31
  # nil, :all, or ['www.gitlab.com', '.gitlab.com']
32
32
  config.cookie_domain = :all
33
33
 
34
+ # Mark experiment cookies as secure (HTTPS only).
35
+ #
36
+ # When set to true, cookies will have the secure flag set, meaning they
37
+ # will only be sent over HTTPS connections. Defaults to true.
38
+ #
39
+ # Set to false in development/test environments if needed:
40
+ # config.secure_cookie = Rails.env.production?
41
+
34
42
  # The default rollout strategy.
35
43
  #
36
44
  # The recommended default rollout strategy when not using caching would
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails_helper'
3
+ require 'spec_helper'
4
4
 
5
5
  <% module_namespacing do -%>
6
6
  RSpec.describe <%= class_name %>Experiment do
@@ -46,20 +46,24 @@ module Gitlab
46
46
  def cache_variant(specified = nil, &block)
47
47
  return (specified.presence || yield) unless cache.store
48
48
 
49
- result = migrated_cache_fetch(cache.store, &block)
49
+ result = migrated_cache_fetch(cache.store) || find_variant(&block)
50
50
  return result unless specified.present?
51
51
 
52
52
  cache.write(specified) if result.to_s != specified.to_s
53
53
  specified
54
54
  end
55
55
 
56
+ def find_variant(&block)
57
+ cache.store.fetch(cache_key, &block)
58
+ end
59
+
56
60
  def cache_key(key = nil, suffix: nil)
57
61
  "#{[name, suffix].compact.join('_')}:#{key || context.signature[:key]}"
58
62
  end
59
63
 
60
64
  private
61
65
 
62
- def migrated_cache_fetch(store, &block)
66
+ def migrated_cache_fetch(store)
63
67
  migrations = context.signature[:migration_keys]&.map { |key| cache_key(key) } || []
64
68
  migrations.find do |old_key|
65
69
  value = store.read(old_key)
@@ -69,7 +73,7 @@ module Gitlab
69
73
  store.write(cache_key, value)
70
74
  store.delete(old_key)
71
75
  break value
72
- end || store.fetch(cache_key, &block)
76
+ end
73
77
  end
74
78
  end
75
79
  end
@@ -44,6 +44,15 @@ module Gitlab
44
44
  "#{experiment.name}_id"
45
45
  end
46
46
 
47
+ # Mark experiment cookies as secure (HTTPS only).
48
+ #
49
+ # When set to true, cookies will have the secure flag set, meaning they
50
+ # will only be sent over HTTPS connections. Defaults to true.
51
+ #
52
+ # Set to false in development/test environments if needed:
53
+ # config.secure_cookie = Rails.env.production?
54
+ @secure_cookie = true
55
+
47
56
  # The default rollout strategy.
48
57
  #
49
58
  # The recommended default rollout strategy when not using caching would
@@ -177,6 +186,7 @@ module Gitlab
177
186
  :cache,
178
187
  :cookie_domain,
179
188
  :cookie_name,
189
+ :secure_cookie,
180
190
  :context_key_secret,
181
191
  :context_key_bit_length,
182
192
  :mount_at,
@@ -5,9 +5,9 @@ module Gitlab
5
5
  class Context
6
6
  include Cookies
7
7
 
8
- DNT_REGEXP = /^(true|t|yes|y|1|on)$/i.freeze
8
+ DNT_REGEXP = /^(true|t|yes|y|1|on)$/i
9
9
 
10
- attr_reader :request
10
+ attr_reader :request, :only_assigned
11
11
 
12
12
  def initialize(experiment, **initial_value)
13
13
  @experiment = experiment
@@ -26,6 +26,7 @@ module Gitlab
26
26
  return @value if value.nil?
27
27
 
28
28
  value = value.dup # dup so we don't mutate
29
+ @only_assigned = value.delete(:only_assigned)
29
30
  reinitialize(value.delete(:request))
30
31
  key(value.delete(:sticky_to))
31
32
 
@@ -32,7 +32,7 @@ module Gitlab
32
32
 
33
33
  cookie ||= SecureRandom.uuid
34
34
  cookie_jar.permanent.signed[cookie_name] = {
35
- value: cookie, secure: true, domain: domain, httponly: true
35
+ value: cookie, secure: Configuration.secure_cookie, domain: domain, httponly: true
36
36
  }
37
37
 
38
38
  hash.merge(key => cookie)
@@ -49,9 +49,10 @@ module Gitlab
49
49
 
50
50
  case distribution_rules
51
51
  when Array # run through the rules until finding an acceptable one
52
- behavior_names[distribution_rules.find_index { |percent| crc % 100 <= total += percent }]
52
+ index = distribution_rules.find_index { |percent| crc % 100 < total += percent unless percent == 0 }
53
+ behavior_names[index]
53
54
  when Hash # run through the variant names until finding an acceptable one
54
- distribution_rules.find { |_, percent| crc % 100 <= total += percent }.first
55
+ distribution_rules.find { |_, percent| crc % 100 < total += percent unless percent == 0 }.first
55
56
  else # assume even distribution on no rules
56
57
  behavior_names.empty? ? nil : behavior_names[crc % behavior_names.length]
57
58
  end
@@ -72,6 +73,10 @@ module Gitlab
72
73
  raise InvalidRolloutRules, "the distribution rules don't match the number of behaviors defined"
73
74
  end
74
75
 
76
+ if distributions.any? { |percent| percent < 0 }
77
+ raise InvalidRolloutRules, "the distribution percentage cannot be negative"
78
+ end
79
+
75
80
  return if distributions.sum == 100
76
81
 
77
82
  raise InvalidRolloutRules, 'the distribution percentages should add up to 100'
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Gitlab
4
4
  class Experiment
5
- VERSION = '0.9.1'
5
+ VERSION = '1.1.0'
6
6
  end
7
7
  end
@@ -158,7 +158,11 @@ module Gitlab
158
158
  def excluded?
159
159
  return @_excluded if defined?(@_excluded)
160
160
 
161
- @_excluded = !run_callbacks(exclusion_callback_chain) { :not_excluded }
161
+ @_excluded = !run_callbacks(exclusion_callback_chain) { :not_excluded } || only_assigned?
162
+ end
163
+
164
+ def only_assigned?
165
+ !!context.only_assigned && find_variant.blank?
162
166
  end
163
167
 
164
168
  def should_track?
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.9.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitLab
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date:
11
+ date: 2025-11-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 10.1.0
61
+ version: 12.0.1
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 10.1.0
68
+ version: 12.0.1
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: lefthook
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -100,28 +100,28 @@ dependencies:
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 2.20.2
103
+ version: 2.24.0
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: 2.20.2
110
+ version: 2.24.0
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: rubocop-rspec
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: 2.22.0
117
+ version: 2.27.1
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: 2.22.0
124
+ version: 2.27.1
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: flipper
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -184,14 +184,14 @@ dependencies:
184
184
  requirements:
185
185
  - - "~>"
186
186
  - !ruby/object:Gem::Version
187
- version: 2.1.0
187
+ version: 3.1.0
188
188
  type: :development
189
189
  prerelease: false
190
190
  version_requirements: !ruby/object:Gem::Requirement
191
191
  requirements:
192
192
  - - "~>"
193
193
  - !ruby/object:Gem::Version
194
- version: 2.1.0
194
+ version: 3.1.0
195
195
  description:
196
196
  email:
197
197
  - gitlab_rubygems@gitlab.com
@@ -199,33 +199,23 @@ executables: []
199
199
  extensions: []
200
200
  extra_rdoc_files: []
201
201
  files:
202
- - lib/generators/gitlab
203
- - lib/generators/gitlab/experiment
202
+ - LICENSE.txt
203
+ - README.md
204
204
  - lib/generators/gitlab/experiment/USAGE
205
205
  - lib/generators/gitlab/experiment/experiment_generator.rb
206
- - lib/generators/gitlab/experiment/install
207
206
  - lib/generators/gitlab/experiment/install/install_generator.rb
208
- - lib/generators/gitlab/experiment/install/templates
209
207
  - lib/generators/gitlab/experiment/install/templates/POST_INSTALL
210
208
  - lib/generators/gitlab/experiment/install/templates/application_experiment.rb.tt
211
209
  - lib/generators/gitlab/experiment/install/templates/initializer.rb.tt
212
- - lib/generators/gitlab/experiment/templates
213
210
  - lib/generators/gitlab/experiment/templates/experiment.rb.tt
214
- - lib/generators/rspec
215
- - lib/generators/rspec/experiment
216
211
  - lib/generators/rspec/experiment/experiment_generator.rb
217
- - lib/generators/rspec/experiment/templates
218
212
  - lib/generators/rspec/experiment/templates/experiment_spec.rb.tt
219
- - lib/generators/test_unit
220
- - lib/generators/test_unit/experiment
221
213
  - lib/generators/test_unit/experiment/experiment_generator.rb
222
- - lib/generators/test_unit/experiment/templates
223
214
  - lib/generators/test_unit/experiment/templates/experiment_test.rb.tt
224
- - lib/gitlab/experiment
215
+ - lib/gitlab/experiment.rb
225
216
  - lib/gitlab/experiment/base_interface.rb
226
- - lib/gitlab/experiment/cache
227
- - lib/gitlab/experiment/cache/redis_hash_store.rb
228
217
  - lib/gitlab/experiment/cache.rb
218
+ - lib/gitlab/experiment/cache/redis_hash_store.rb
229
219
  - lib/gitlab/experiment/callbacks.rb
230
220
  - lib/gitlab/experiment/configuration.rb
231
221
  - lib/gitlab/experiment/context.rb
@@ -235,19 +225,14 @@ files:
235
225
  - lib/gitlab/experiment/errors.rb
236
226
  - lib/gitlab/experiment/middleware.rb
237
227
  - lib/gitlab/experiment/nestable.rb
238
- - lib/gitlab/experiment/rollout
228
+ - lib/gitlab/experiment/rollout.rb
239
229
  - lib/gitlab/experiment/rollout/percent.rb
240
230
  - lib/gitlab/experiment/rollout/random.rb
241
231
  - lib/gitlab/experiment/rollout/round_robin.rb
242
- - lib/gitlab/experiment/rollout.rb
243
232
  - lib/gitlab/experiment/rspec.rb
244
- - lib/gitlab/experiment/test_behaviors
245
233
  - lib/gitlab/experiment/test_behaviors/trackable.rb
246
234
  - lib/gitlab/experiment/variant.rb
247
235
  - lib/gitlab/experiment/version.rb
248
- - lib/gitlab/experiment.rb
249
- - LICENSE.txt
250
- - README.md
251
236
  homepage: https://gitlab.com/gitlab-org/ruby/gems/gitlab-experiment
252
237
  licenses:
253
238
  - MIT
@@ -260,14 +245,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
260
245
  requirements:
261
246
  - - ">="
262
247
  - !ruby/object:Gem::Version
263
- version: '2.6'
248
+ version: '3.0'
264
249
  required_rubygems_version: !ruby/object:Gem::Requirement
265
250
  requirements:
266
251
  - - ">="
267
252
  - !ruby/object:Gem::Version
268
253
  version: '0'
269
254
  requirements: []
270
- rubygems_version: 3.3.26
255
+ rubygems_version: 3.5.22
271
256
  signing_key:
272
257
  specification_version: 4
273
258
  summary: GitLab experimentation library.