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 +4 -4
- data/README.md +142 -15
- data/lib/generators/gitlab/experiment/install/templates/initializer.rb.tt +8 -0
- data/lib/generators/rspec/experiment/templates/experiment_spec.rb.tt +1 -1
- data/lib/gitlab/experiment/cache.rb +7 -3
- data/lib/gitlab/experiment/configuration.rb +10 -0
- data/lib/gitlab/experiment/context.rb +3 -2
- data/lib/gitlab/experiment/cookies.rb +1 -1
- data/lib/gitlab/experiment/rollout/percent.rb +7 -2
- data/lib/gitlab/experiment/version.rb +1 -1
- data/lib/gitlab/experiment.rb +5 -1
- metadata +17 -32
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3305c24f350ec02a3ef81da742a6d108c7aef00623f8b2534f989f06ec4a2ba6
|
|
4
|
+
data.tar.gz: 5f9113235e1623a2012ea5e4c451ed41baeb847e5883951b95b44fd6d7fd40fa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
216
|
+
#### Excluding from within the experiment block
|
|
213
217
|
|
|
214
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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`
|
|
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
|
-
|
|
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
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
|
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'
|
data/lib/gitlab/experiment.rb
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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:
|
|
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
|
-
-
|
|
203
|
-
-
|
|
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: '
|
|
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.
|
|
255
|
+
rubygems_version: 3.5.22
|
|
271
256
|
signing_key:
|
|
272
257
|
specification_version: 4
|
|
273
258
|
summary: GitLab experimentation library.
|