gitlab-experiment 0.6.1 → 0.6.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +7 -1
- data/lib/generators/gitlab/experiment/install/templates/initializer.rb.tt +10 -0
- data/lib/gitlab/experiment/base_interface.rb +2 -1
- data/lib/gitlab/experiment/callbacks.rb +1 -0
- data/lib/gitlab/experiment/configuration.rb +8 -5
- data/lib/gitlab/experiment/context.rb +9 -6
- data/lib/gitlab/experiment/cookies.rb +1 -1
- data/lib/gitlab/experiment/dsl.rb +4 -0
- data/lib/gitlab/experiment/engine.rb +29 -18
- data/lib/gitlab/experiment/errors.rb +1 -0
- data/lib/gitlab/experiment/middleware.rb +2 -2
- data/lib/gitlab/experiment/nestable.rb +41 -0
- data/lib/gitlab/experiment/rspec.rb +9 -1
- data/lib/gitlab/experiment/test_behaviors/trackable.rb +69 -0
- data/lib/gitlab/experiment/version.rb +1 -1
- data/lib/gitlab/experiment.rb +14 -1
- metadata +38 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9651e4cc4941d879e7c006f5865ff71989babd942bfcc09a86eb5be89f17e38c
|
4
|
+
data.tar.gz: d23055b9c808ae680c05762d60a569306069578a8555f87daf816a717cef0c28
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af40d50563c3938f089d415ebece34be76a79f8b1c13f7f5a4d6c62f035a7fe07147a4ab9ad475f00372d94be7b5c35f6c54ee2f866c3b0875748774cb04f1bb
|
7
|
+
data.tar.gz: fd4b8c69bc49351a7050d5ab6665c42855cf1d26b18e4a4e1983fbbeba2a8487b1523b6d1f824f0862d0fd16d425565eaf4ce179120a2e55bbbbeef53ae03a2f
|
data/README.md
CHANGED
@@ -47,7 +47,7 @@ We'll name our experiment `notification_toggle`. This name is prefixed based on
|
|
47
47
|
|
48
48
|
When you implement an experiment you'll need to provide a name, and a context. The name can show up in tracking calls, and potentially other aspects. The context determines the variant assigned, and should be consistent between calls. We'll discuss migrating context in later examples.
|
49
49
|
|
50
|
-
A context "key" represents the unique id of a context. It allows us to give the same experience between different calls to the experiment and can be used in caching.
|
50
|
+
A context "key" represents the unique id of a context. It allows us to give the same experience between different calls to the experiment and can be used in caching. This is how an experiment remains "sticky" to a given context.
|
51
51
|
|
52
52
|
Now in our experiment we're going to render one of two views: the control will be our current view, and the candidate will be the new toggle button with a confirmation flow.
|
53
53
|
|
@@ -74,6 +74,12 @@ experiment(:notification_toggle, actor: user) do |e|
|
|
74
74
|
end
|
75
75
|
```
|
76
76
|
|
77
|
+
You can specify what the experiment should be "sticky" to by providing a `:sticky_to` option. By default this will be the entire context provided, but this can be overridden manually if needed.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
experiment(:notification_toggle, actor: user, project: project, sticky_to: project) #...
|
81
|
+
```
|
82
|
+
|
77
83
|
Understanding how an experiment can change behavior is important in evaluating its performance.
|
78
84
|
|
79
85
|
To this end, we track events that are important by calling the same experiment elsewhere in code. By using the same context, you'll have consistent behavior and the ability to track events to it.
|
@@ -56,6 +56,16 @@ Gitlab::Experiment.configure do |config|
|
|
56
56
|
# '/-/experiment', '/redirect', nil
|
57
57
|
config.mount_at = '/experiment'
|
58
58
|
|
59
|
+
# When using the middleware, links can be instrumented and redirected
|
60
|
+
# elsewhere. This can be exploited to make a harmful url look innocuous or
|
61
|
+
# that it's a valid url on your domain. To avoid this, you can provide your
|
62
|
+
# own logic for what urls will be considered valid and redirected to.
|
63
|
+
#
|
64
|
+
# Expected to return a boolean value.
|
65
|
+
config.redirect_url_validator = lambda do |redirect_url|
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
59
69
|
# Logic this project uses to determine inclusion in a given experiment.
|
60
70
|
#
|
61
71
|
# Expected to return a boolean value.
|
@@ -39,17 +39,19 @@ module Gitlab
|
|
39
39
|
|
40
40
|
# The default base path that the middleware (or rails engine) will be
|
41
41
|
# mounted.
|
42
|
-
@mount_at =
|
42
|
+
@mount_at = nil
|
43
|
+
|
44
|
+
# The middleware won't redirect to urls that aren't considered valid.
|
45
|
+
# Expected to return a boolean value.
|
46
|
+
@redirect_url_validator = ->(_redirect_url) { true }
|
43
47
|
|
44
48
|
# Logic this project uses to determine inclusion in a given experiment.
|
45
49
|
# Expected to return a boolean value.
|
46
|
-
@inclusion_resolver =
|
47
|
-
false
|
48
|
-
end
|
50
|
+
@inclusion_resolver = ->(_requested_variant) { false }
|
49
51
|
|
50
52
|
# Tracking behavior can be implemented to link an event to an experiment.
|
51
53
|
@tracking_behavior = lambda do |event, args|
|
52
|
-
Configuration.logger.info
|
54
|
+
Configuration.logger.info("#{self.class.name}[#{name}] #{event}: #{args.merge(signature: signature)}")
|
53
55
|
end
|
54
56
|
|
55
57
|
# Called at the end of every experiment run, with the result.
|
@@ -88,6 +90,7 @@ module Gitlab
|
|
88
90
|
:context_key_bit_length,
|
89
91
|
:mount_at,
|
90
92
|
:default_rollout,
|
93
|
+
:redirect_url_validator,
|
91
94
|
:inclusion_resolver,
|
92
95
|
:tracking_behavior,
|
93
96
|
:publishing_behavior
|
@@ -27,6 +27,7 @@ module Gitlab
|
|
27
27
|
|
28
28
|
value = value.dup # dup so we don't mutate
|
29
29
|
reinitialize(value.delete(:request))
|
30
|
+
key(value.delete(:sticky_to))
|
30
31
|
|
31
32
|
@value.merge!(process_migrations(value))
|
32
33
|
end
|
@@ -34,7 +35,7 @@ module Gitlab
|
|
34
35
|
def key(key = nil)
|
35
36
|
return @key || @experiment.key_for(value) if key.nil?
|
36
37
|
|
37
|
-
@key = key
|
38
|
+
@key = @experiment.key_for(key)
|
38
39
|
end
|
39
40
|
|
40
41
|
def trackable?
|
@@ -61,16 +62,18 @@ module Gitlab
|
|
61
62
|
private
|
62
63
|
|
63
64
|
def process_migrations(value)
|
64
|
-
|
65
|
-
|
65
|
+
add_unmerged_migration(value.delete(:migrated_from))
|
66
|
+
add_merged_migration(value.delete(:migrated_with))
|
66
67
|
|
67
68
|
migrate_cookie(value, "#{@experiment.name}_id")
|
68
69
|
end
|
69
70
|
|
70
|
-
def
|
71
|
-
|
71
|
+
def add_unmerged_migration(value = {})
|
72
|
+
@migrations[:unmerged] << value if value.is_a?(Hash)
|
73
|
+
end
|
72
74
|
|
73
|
-
|
75
|
+
def add_merged_migration(value = {})
|
76
|
+
@migrations[:merged] << value if value.is_a?(Hash)
|
74
77
|
end
|
75
78
|
|
76
79
|
def migration_keys
|
@@ -3,6 +3,10 @@
|
|
3
3
|
module Gitlab
|
4
4
|
class Experiment
|
5
5
|
module Dsl
|
6
|
+
def self.include_in(klass, with_helper: false)
|
7
|
+
klass.include(self).tap { |base| base.helper_method(:experiment) if with_helper }
|
8
|
+
end
|
9
|
+
|
6
10
|
def experiment(name, variant_name = nil, **context, &block)
|
7
11
|
raise ArgumentError, 'name is required' if name.nil?
|
8
12
|
|
@@ -1,35 +1,46 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'active_model'
|
4
|
+
|
3
5
|
module Gitlab
|
4
6
|
class Experiment
|
7
|
+
include ActiveModel::Model
|
8
|
+
|
9
|
+
# used for generating routes
|
10
|
+
def self.model_name
|
11
|
+
ActiveModel::Name.new(self, Gitlab)
|
12
|
+
end
|
13
|
+
|
5
14
|
class Engine < ::Rails::Engine
|
6
|
-
|
7
|
-
if defined?(ActionController)
|
8
|
-
ActionController::Base.include(Dsl)
|
9
|
-
ActionController::Base.helper_method(:experiment)
|
10
|
-
end
|
15
|
+
isolate_namespace Experiment
|
11
16
|
|
12
|
-
|
17
|
+
initializer('gitlab_experiment.include_dsl') { include_dsl }
|
18
|
+
initializer('gitlab_experiment.mount_engine') { |app| mount_engine(app, Configuration.mount_at) }
|
13
19
|
|
14
|
-
|
15
|
-
|
20
|
+
private
|
21
|
+
|
22
|
+
def include_dsl
|
23
|
+
Dsl.include_in(ActionController::Base, with_helper: true) if defined?(ActionController)
|
24
|
+
Dsl.include_in(ActionMailer::Base, with_helper: true) if defined?(ActionMailer)
|
16
25
|
end
|
17
26
|
|
18
|
-
def
|
19
|
-
return if
|
27
|
+
def mount_engine(app, mount_at)
|
28
|
+
return if mount_at.blank?
|
29
|
+
|
30
|
+
engine = routes do
|
31
|
+
default_url_options app.routes.default_url_options.clone.without(:script_name)
|
32
|
+
resources :experiments, path: '/', only: :show
|
33
|
+
end
|
20
34
|
|
21
|
-
app.config.middleware.use(Middleware,
|
35
|
+
app.config.middleware.use(Middleware, mount_at)
|
22
36
|
app.routes.append do
|
23
|
-
|
24
|
-
|
37
|
+
mount Engine, at: mount_at, as: :experiment_engine
|
38
|
+
direct(:experiment_redirect) do |ex, options|
|
39
|
+
url = options[:url]
|
40
|
+
"#{engine.url_helpers.experiment_url(ex)}?#{url}"
|
25
41
|
end
|
26
42
|
end
|
27
43
|
end
|
28
|
-
|
29
|
-
config.after_initialize { include_dsl }
|
30
|
-
initializer 'gitlab_experiment.add_middleware' do |app|
|
31
|
-
add_middleware(app, Configuration.mount_at)
|
32
|
-
end
|
33
44
|
end
|
34
45
|
end
|
35
46
|
end
|
@@ -6,8 +6,8 @@ module Gitlab
|
|
6
6
|
def self.redirect(id, url)
|
7
7
|
raise Error, 'no url to redirect to' if url.blank?
|
8
8
|
|
9
|
-
Gitlab::Experiment.from_param(id)
|
10
|
-
[303, { 'Location' => url }, []]
|
9
|
+
experiment = Gitlab::Experiment.from_param(id)
|
10
|
+
[303, { 'Location' => experiment.process_redirect_url(url) || raise(Error, 'not redirecting') }, []]
|
11
11
|
end
|
12
12
|
|
13
13
|
def initialize(app, base_path)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
class Experiment
|
5
|
+
module Nestable
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
set_callback :run, :around, :manage_nested_stack
|
10
|
+
end
|
11
|
+
|
12
|
+
def nest_experiment(other)
|
13
|
+
raise NestingError, "unable to nest the #{other.name} experiment within the #{name} experiment"
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def manage_nested_stack
|
19
|
+
Stack.push(self)
|
20
|
+
yield
|
21
|
+
ensure
|
22
|
+
Stack.pop
|
23
|
+
end
|
24
|
+
|
25
|
+
class Stack
|
26
|
+
include Singleton
|
27
|
+
|
28
|
+
@stack = []
|
29
|
+
|
30
|
+
class << self
|
31
|
+
delegate :pop, :length, :size, :[], to: :@stack
|
32
|
+
|
33
|
+
def push(instance)
|
34
|
+
@stack.last&.nest_experiment(instance)
|
35
|
+
@stack.push(instance)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
module Gitlab
|
4
4
|
class Experiment
|
5
|
+
module TestBehaviors
|
6
|
+
autoload :Trackable, 'gitlab/experiment/test_behaviors/trackable.rb'
|
7
|
+
end
|
8
|
+
|
5
9
|
module RSpecHelpers
|
6
10
|
def stub_experiments(experiments, times = nil)
|
7
11
|
experiments.each { |experiment| wrapped_experiment(experiment, times) }
|
@@ -82,7 +86,7 @@ module Gitlab
|
|
82
86
|
extend RSpec::Matchers::DSL
|
83
87
|
|
84
88
|
def require_experiment(experiment, matcher_name, classes: false)
|
85
|
-
klass = experiment.
|
89
|
+
klass = experiment.instance_of?(Class) ? experiment : experiment.class
|
86
90
|
unless klass <= Gitlab::Experiment
|
87
91
|
raise(
|
88
92
|
ArgumentError,
|
@@ -234,6 +238,10 @@ RSpec.configure do |config|
|
|
234
238
|
|
235
239
|
config.before(:each, :experiment) do
|
236
240
|
RequestStore.clear!
|
241
|
+
|
242
|
+
if defined?(Gitlab::Experiment::TestBehaviors::TrackedStructure)
|
243
|
+
Gitlab::Experiment::TestBehaviors::TrackedStructure.reset!
|
244
|
+
end
|
237
245
|
end
|
238
246
|
|
239
247
|
config.include Gitlab::Experiment::RSpecMatchers, :experiment
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
class Experiment
|
5
|
+
module TestBehaviors
|
6
|
+
module Trackable
|
7
|
+
private
|
8
|
+
|
9
|
+
def manage_nested_stack
|
10
|
+
TrackedStructure.push(self)
|
11
|
+
super
|
12
|
+
ensure
|
13
|
+
TrackedStructure.pop
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class TrackedStructure
|
18
|
+
include Singleton
|
19
|
+
|
20
|
+
# dependency tracking
|
21
|
+
@flat = {}
|
22
|
+
@stack = []
|
23
|
+
|
24
|
+
# structure tracking
|
25
|
+
@tree = { name: nil, count: 0, children: {} }
|
26
|
+
@node = @tree
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def reset!
|
30
|
+
# dependency tracking
|
31
|
+
@flat = {}
|
32
|
+
@stack = []
|
33
|
+
|
34
|
+
# structure tracking
|
35
|
+
@tree = { name: nil, count: 0, children: {} }
|
36
|
+
@node = @tree
|
37
|
+
end
|
38
|
+
|
39
|
+
def hierarchy
|
40
|
+
@tree[:children]
|
41
|
+
end
|
42
|
+
|
43
|
+
def dependencies
|
44
|
+
@flat
|
45
|
+
end
|
46
|
+
|
47
|
+
def push(instance)
|
48
|
+
# dependency tracking
|
49
|
+
@flat[instance.name] = ((@flat[instance.name] || []) + @stack.map(&:name)).uniq
|
50
|
+
@stack.push(instance)
|
51
|
+
|
52
|
+
# structure tracking
|
53
|
+
@last = @node
|
54
|
+
@node = @node[:children][instance.name] ||= { name: instance.name, count: 0, children: {} }
|
55
|
+
@node[:count] += 1
|
56
|
+
end
|
57
|
+
|
58
|
+
def pop
|
59
|
+
# dependency tracking
|
60
|
+
@stack.pop
|
61
|
+
|
62
|
+
# structure tracking
|
63
|
+
@node = @last
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/gitlab/experiment.rb
CHANGED
@@ -19,6 +19,7 @@ require 'gitlab/experiment/cookies'
|
|
19
19
|
require 'gitlab/experiment/context'
|
20
20
|
require 'gitlab/experiment/dsl'
|
21
21
|
require 'gitlab/experiment/middleware'
|
22
|
+
require 'gitlab/experiment/nestable'
|
22
23
|
require 'gitlab/experiment/variant'
|
23
24
|
require 'gitlab/experiment/version'
|
24
25
|
require 'gitlab/experiment/engine' if defined?(Rails::Engine)
|
@@ -28,6 +29,7 @@ module Gitlab
|
|
28
29
|
include BaseInterface
|
29
30
|
include Cache
|
30
31
|
include Callbacks
|
32
|
+
include Nestable
|
31
33
|
|
32
34
|
class << self
|
33
35
|
def default_rollout(rollout = nil, options = {})
|
@@ -103,7 +105,9 @@ module Gitlab
|
|
103
105
|
end
|
104
106
|
|
105
107
|
def run(variant_name = nil)
|
106
|
-
@result
|
108
|
+
return @result if context.frozen?
|
109
|
+
|
110
|
+
@result = run_callbacks(:run) { super(variant(variant_name).name) }
|
107
111
|
rescue Scientist::BehaviorMissing => e
|
108
112
|
raise Error, e
|
109
113
|
end
|
@@ -120,6 +124,13 @@ module Gitlab
|
|
120
124
|
instance_exec(action, event_args, &Configuration.tracking_behavior)
|
121
125
|
end
|
122
126
|
|
127
|
+
def process_redirect_url(url)
|
128
|
+
return unless Configuration.redirect_url_validator&.call(url)
|
129
|
+
|
130
|
+
track('visited', url: url)
|
131
|
+
url # return the url, which allows for mutation
|
132
|
+
end
|
133
|
+
|
123
134
|
def enabled?
|
124
135
|
true
|
125
136
|
end
|
@@ -148,6 +159,8 @@ module Gitlab
|
|
148
159
|
return instance_exec(source, seed, &block)
|
149
160
|
end
|
150
161
|
|
162
|
+
return source if source.is_a?(String)
|
163
|
+
|
151
164
|
source = source.keys + source.values if source.is_a?(Hash)
|
152
165
|
|
153
166
|
ingredients = Array(source).map { |v| identify(v) }
|
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.6.
|
4
|
+
version: 0.6.5
|
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:
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -65,38 +65,55 @@ executables: []
|
|
65
65
|
extensions: []
|
66
66
|
extra_rdoc_files: []
|
67
67
|
files:
|
68
|
-
-
|
69
|
-
-
|
70
|
-
- lib/generators/gitlab/experiment/
|
71
|
-
- lib/generators/gitlab/experiment/experiment_generator.rb
|
68
|
+
- lib/generators/gitlab
|
69
|
+
- lib/generators/gitlab/experiment
|
70
|
+
- lib/generators/gitlab/experiment/install
|
72
71
|
- lib/generators/gitlab/experiment/install/install_generator.rb
|
73
|
-
- lib/generators/gitlab/experiment/install/templates
|
72
|
+
- lib/generators/gitlab/experiment/install/templates
|
74
73
|
- lib/generators/gitlab/experiment/install/templates/application_experiment.rb.tt
|
75
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
|
76
79
|
- lib/generators/gitlab/experiment/templates/experiment.rb.tt
|
77
|
-
- lib/generators/
|
78
|
-
- lib/generators/
|
80
|
+
- lib/generators/test_unit
|
81
|
+
- lib/generators/test_unit/experiment
|
79
82
|
- lib/generators/test_unit/experiment/experiment_generator.rb
|
83
|
+
- lib/generators/test_unit/experiment/templates
|
80
84
|
- lib/generators/test_unit/experiment/templates/experiment_test.rb.tt
|
85
|
+
- lib/generators/rspec
|
86
|
+
- lib/generators/rspec/experiment
|
87
|
+
- lib/generators/rspec/experiment/experiment_generator.rb
|
88
|
+
- lib/generators/rspec/experiment/templates
|
89
|
+
- lib/generators/rspec/experiment/templates/experiment_spec.rb.tt
|
81
90
|
- lib/gitlab/experiment.rb
|
82
|
-
- lib/gitlab/experiment
|
83
|
-
- lib/gitlab/experiment/
|
91
|
+
- 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
|
96
|
+
- lib/gitlab/experiment/cache
|
84
97
|
- lib/gitlab/experiment/cache/redis_hash_store.rb
|
98
|
+
- lib/gitlab/experiment/errors.rb
|
85
99
|
- lib/gitlab/experiment/callbacks.rb
|
86
|
-
- lib/gitlab/experiment/
|
100
|
+
- lib/gitlab/experiment/rollout.rb
|
101
|
+
- lib/gitlab/experiment/base_interface.rb
|
102
|
+
- lib/gitlab/experiment/nestable.rb
|
87
103
|
- lib/gitlab/experiment/context.rb
|
88
|
-
- lib/gitlab/experiment/cookies.rb
|
89
|
-
- lib/gitlab/experiment/dsl.rb
|
90
104
|
- lib/gitlab/experiment/engine.rb
|
91
|
-
- lib/gitlab/experiment/
|
92
|
-
- lib/gitlab/experiment/
|
93
|
-
- lib/gitlab/experiment/rollout.rb
|
94
|
-
- lib/gitlab/experiment/rollout/percent.rb
|
105
|
+
- lib/gitlab/experiment/rspec.rb
|
106
|
+
- lib/gitlab/experiment/rollout
|
95
107
|
- lib/gitlab/experiment/rollout/random.rb
|
96
108
|
- lib/gitlab/experiment/rollout/round_robin.rb
|
97
|
-
- lib/gitlab/experiment/
|
98
|
-
- lib/gitlab/experiment/
|
109
|
+
- lib/gitlab/experiment/rollout/percent.rb
|
110
|
+
- lib/gitlab/experiment/cache.rb
|
99
111
|
- lib/gitlab/experiment/version.rb
|
112
|
+
- lib/gitlab/experiment/cookies.rb
|
113
|
+
- lib/gitlab/experiment/configuration.rb
|
114
|
+
- lib/gitlab/experiment/dsl.rb
|
115
|
+
- LICENSE.txt
|
116
|
+
- README.md
|
100
117
|
homepage: https://gitlab.com/gitlab-org/gitlab-experiment
|
101
118
|
licenses:
|
102
119
|
- MIT
|
@@ -116,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
116
133
|
- !ruby/object:Gem::Version
|
117
134
|
version: '0'
|
118
135
|
requirements: []
|
119
|
-
rubygems_version: 3.
|
136
|
+
rubygems_version: 3.1.6
|
120
137
|
signing_key:
|
121
138
|
specification_version: 4
|
122
139
|
summary: GitLab experiment library built on top of scientist.
|