gitlab-experiment 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/lib/generators/gitlab/experiment/USAGE +17 -0
- data/lib/generators/gitlab/experiment/experiment_generator.rb +33 -0
- data/lib/generators/gitlab/experiment/install/install_generator.rb +41 -0
- data/lib/generators/gitlab/experiment/install/templates/POST_INSTALL +2 -0
- data/lib/generators/gitlab/experiment/install/templates/application_experiment.rb.tt +4 -0
- data/lib/generators/{gitlab_experiment/install/templates/initializer.rb → gitlab/experiment/install/templates/initializer.rb.tt} +9 -0
- data/lib/generators/gitlab/experiment/templates/experiment.rb.tt +15 -0
- data/lib/generators/rspec/experiment/experiment_generator.rb +15 -0
- data/lib/generators/rspec/experiment/templates/experiment_spec.rb.tt +9 -0
- data/lib/generators/test_unit/experiment/experiment_generator.rb +17 -0
- data/lib/generators/test_unit/experiment/templates/experiment_test.rb.tt +11 -0
- data/lib/gitlab/experiment.rb +48 -9
- data/lib/gitlab/experiment/callbacks.rb +39 -0
- data/lib/gitlab/experiment/configuration.rb +14 -4
- data/lib/gitlab/experiment/context.rb +3 -7
- data/lib/gitlab/experiment/cookies.rb +6 -2
- data/lib/gitlab/experiment/version.rb +1 -1
- metadata +28 -5
- data/lib/generators/gitlab_experiment/install/POST_INSTALL +0 -0
- data/lib/generators/gitlab_experiment/install/install_generator.rb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8524dff7f908e481923e3131f3dd67c34f426e31ed10171896ea10a209e0b74a
|
4
|
+
data.tar.gz: 151f2e7f7bbf9692350c02f46f0c4c8cfa6f9c7ec05fdcb0fda50e34324c06fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3f23698aabf3968c77f49cdb924125879663670c12d22c390a47f7c791687f87fb1211adf432777f66012e3be1db2e90d5c33a1dc0a156332bb8926938c4e587
|
7
|
+
data.tar.gz: 51973494136edef02672c9086970fec2ecb1fb3a6716edb10d1cfe758172f8d256ecd6ef1b5cefdd50133898637e022af527fc7e20df2212b1f7210db2dd087e
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
Here at GitLab, we run experiments as A/B/n tests and review the data the experiment generates. From that data, we determine the best performing variant and promote it as the new default code path. Or revert back to the control if no variant outperformed it.
|
6
6
|
|
7
|
-
This library provides a clean and elegant DSL to define, run, and track your GitLab experiment.
|
7
|
+
This library provides a clean and elegant DSL (domain specific language) to define, run, and track your GitLab experiment.
|
8
8
|
|
9
9
|
When we discuss the behavior of this gem, we'll use terms like experiment, context, control, candidate, and variant. It's worth defining these terms so they're more understood.
|
10
10
|
|
@@ -27,7 +27,7 @@ gem 'gitlab-experiment'
|
|
27
27
|
If you're using Rails, you can install the initializer. It provides basic configuration and documentation.
|
28
28
|
|
29
29
|
```shell
|
30
|
-
$ rails generate
|
30
|
+
$ rails generate gitlab:experiment:install
|
31
31
|
```
|
32
32
|
|
33
33
|
## Implementing an experiment
|
@@ -307,7 +307,7 @@ Gitlab::Experiment.configure do |config|
|
|
307
307
|
end
|
308
308
|
```
|
309
309
|
|
310
|
-
More examples for configuration are available in the provided [rails initializer](lib/generators/
|
310
|
+
More examples for configuration are available in the provided [rails initializer](lib/generators/gitlab/experiment/install/templates/initializer.rb).
|
311
311
|
|
312
312
|
### Client layer / JavaScript
|
313
313
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Description:
|
2
|
+
Stubs out a new experiment and its variants. Pass the experiment name,
|
3
|
+
either CamelCased or under_scored, and a list of variants as arguments.
|
4
|
+
|
5
|
+
To create an experiment within a module, specify the experiment name as a
|
6
|
+
path like 'parent_module/experiment_name'.
|
7
|
+
|
8
|
+
This generates an experiment class in app/experiments and invokes feature
|
9
|
+
flag, and test framework generators.
|
10
|
+
|
11
|
+
Example:
|
12
|
+
`rails generate gitlab:experiment NullHypothesis control candidate alt_variant`
|
13
|
+
|
14
|
+
NullHypothesis experiment with default variants.
|
15
|
+
Experiment: app/experiments/null_hypothesis_experiment.rb
|
16
|
+
Feature Flag: config/feature_flags/experiment/null_hypothesis.yaml
|
17
|
+
Test: test/experiments/null_hypothesis_experiment_test.rb
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators'
|
4
|
+
|
5
|
+
module Gitlab
|
6
|
+
module Generators
|
7
|
+
class ExperimentGenerator < Rails::Generators::NamedBase
|
8
|
+
source_root File.expand_path('templates/', __dir__)
|
9
|
+
check_class_collision suffix: 'Experiment'
|
10
|
+
|
11
|
+
argument :variants,
|
12
|
+
type: :array,
|
13
|
+
default: %w[control candidate],
|
14
|
+
banner: 'variant variant'
|
15
|
+
|
16
|
+
def create_experiment
|
17
|
+
template 'experiment.rb', File.join('app/experiments', class_path, "#{file_name}_experiment.rb")
|
18
|
+
end
|
19
|
+
|
20
|
+
hook_for :test_framework
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def file_name
|
25
|
+
@_file_name ||= remove_possible_suffix(super)
|
26
|
+
end
|
27
|
+
|
28
|
+
def remove_possible_suffix(name)
|
29
|
+
name.sub(/_?exp[ei]riment$/i, "") # be somewhat forgiving with spelling
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators'
|
4
|
+
|
5
|
+
module Gitlab
|
6
|
+
module Generators
|
7
|
+
module Experiment
|
8
|
+
class InstallGenerator < Rails::Generators::Base
|
9
|
+
source_root File.expand_path('templates', __dir__)
|
10
|
+
|
11
|
+
desc 'Installs the Gitlab::Experiment initializer and optional ApplicationExperiment into your application.'
|
12
|
+
|
13
|
+
class_option :skip_initializer,
|
14
|
+
type: :boolean,
|
15
|
+
default: false,
|
16
|
+
desc: 'Skip the initializer with default configuration'
|
17
|
+
|
18
|
+
class_option :skip_baseclass,
|
19
|
+
type: :boolean,
|
20
|
+
default: false,
|
21
|
+
desc: 'Skip the ApplicationExperiment base class'
|
22
|
+
|
23
|
+
def create_initializer
|
24
|
+
return if options[:skip_initializer]
|
25
|
+
|
26
|
+
template 'initializer.rb', 'config/initializers/gitlab_experiment.rb'
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_baseclass
|
30
|
+
return if options[:skip_baseclass]
|
31
|
+
|
32
|
+
template 'application_experiment.rb', 'app/experiments/application_experiment.rb'
|
33
|
+
end
|
34
|
+
|
35
|
+
def display_post_install
|
36
|
+
readme 'POST_INSTALL' if behavior == :invoke
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -7,6 +7,9 @@ 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.
|
11
|
+
config.base_class = 'ApplicationExperiment'
|
12
|
+
|
10
13
|
# The caching layer is expected to respond to fetch, like Rails.cache.
|
11
14
|
config.cache = nil
|
12
15
|
|
@@ -84,4 +87,10 @@ Gitlab::Experiment.configure do |config|
|
|
84
87
|
values = context.values.map { |v| (v.respond_to?(:to_global_id) ? v.to_global_id : v).to_s }
|
85
88
|
Digest::MD5.hexdigest((context.keys + values).join('|'))
|
86
89
|
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
|
87
96
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
<% if namespaced? -%>
|
4
|
+
require_dependency "<%= namespaced_path %>/application_experiment"
|
5
|
+
|
6
|
+
<% end -%>
|
7
|
+
<% module_namespacing do -%>
|
8
|
+
class <%= class_name %>Experiment < ApplicationExperiment
|
9
|
+
<% variants.each do |variant| -%>
|
10
|
+
def <%= variant %>_behavior
|
11
|
+
end
|
12
|
+
<%= "\n" unless variant == variants.last -%>
|
13
|
+
<% end -%>
|
14
|
+
end
|
15
|
+
<% end -%>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'generators/rspec'
|
4
|
+
|
5
|
+
module Rspec
|
6
|
+
module Generators
|
7
|
+
class ExperimentGenerator < Rspec::Generators::Base
|
8
|
+
source_root File.expand_path('templates/', __dir__)
|
9
|
+
|
10
|
+
def create_experiment_spec
|
11
|
+
template 'experiment_spec.rb', File.join('spec/experiments', class_path, "#{file_name}_experiment_spec.rb")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators/test_unit'
|
4
|
+
|
5
|
+
module TestUnit # :nodoc:
|
6
|
+
module Generators # :nodoc:
|
7
|
+
class ExperimentGenerator < TestUnit::Generators::Base # :nodoc:
|
8
|
+
source_root File.expand_path('templates/', __dir__)
|
9
|
+
|
10
|
+
check_class_collision suffix: 'Test'
|
11
|
+
|
12
|
+
def create_test_file
|
13
|
+
template 'experiment_test.rb', File.join('test/experiments', class_path, "#{file_name}_experiment_test.rb")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/gitlab/experiment.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'scientist'
|
4
|
+
require 'active_support/callbacks'
|
5
|
+
require 'active_support/core_ext/object/blank'
|
6
|
+
require 'active_support/core_ext/string/inflections'
|
4
7
|
|
5
8
|
require 'gitlab/experiment/caching'
|
9
|
+
require 'gitlab/experiment/callbacks'
|
6
10
|
require 'gitlab/experiment/configuration'
|
7
11
|
require 'gitlab/experiment/cookies'
|
8
12
|
require 'gitlab/experiment/context'
|
@@ -15,28 +19,45 @@ module Gitlab
|
|
15
19
|
class Experiment
|
16
20
|
include Scientist::Experiment
|
17
21
|
include Caching
|
22
|
+
include Callbacks
|
18
23
|
|
19
24
|
class << self
|
20
25
|
def configure
|
21
26
|
yield Configuration
|
22
27
|
end
|
23
28
|
|
24
|
-
def run(name, variant_name = nil, **context, &block)
|
29
|
+
def run(name = nil, variant_name = nil, **context, &block)
|
30
|
+
raise ArgumentError, 'name is required' if name.nil? && base?
|
31
|
+
|
25
32
|
instance = constantize(name).new(name, variant_name, **context, &block)
|
26
33
|
return instance unless block_given?
|
27
34
|
|
28
35
|
instance.context.frozen? ? instance.run : instance.tap(&:run)
|
29
36
|
end
|
30
37
|
|
31
|
-
def
|
32
|
-
name =
|
33
|
-
|
34
|
-
|
38
|
+
def experiment_name(name = nil, suffix: true, suffix_word: 'experiment')
|
39
|
+
name = (name.presence || self.name).to_s.underscore.sub(%r{(?<char>[_/]|)#{suffix_word}$}, '')
|
40
|
+
name = "#{name}#{Regexp.last_match(:char) || '_'}#{suffix_word}"
|
41
|
+
suffix ? name : name.sub(/_#{suffix_word}$/, '')
|
42
|
+
end
|
43
|
+
|
44
|
+
def base?
|
45
|
+
self == Gitlab::Experiment || name == Configuration.base_class
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def constantize(name = nil)
|
51
|
+
return self if name.nil?
|
52
|
+
|
53
|
+
experiment_name(name).classify.safe_constantize || Configuration.base_class.constantize
|
35
54
|
end
|
36
55
|
end
|
37
56
|
|
38
|
-
def initialize(name, variant_name = nil, **context)
|
39
|
-
|
57
|
+
def initialize(name = nil, variant_name = nil, **context)
|
58
|
+
raise ArgumentError, 'name is required' if name.blank? && self.class.base?
|
59
|
+
|
60
|
+
@name = self.class.experiment_name(name, suffix: false)
|
40
61
|
@variant_name = variant_name
|
41
62
|
@excluded = []
|
42
63
|
@context = Context.new(self, context)
|
@@ -48,7 +69,7 @@ module Gitlab
|
|
48
69
|
end
|
49
70
|
|
50
71
|
def context(value = nil)
|
51
|
-
return @context if value.
|
72
|
+
return @context if value.blank?
|
52
73
|
|
53
74
|
@context.value(value)
|
54
75
|
@context
|
@@ -70,7 +91,17 @@ module Gitlab
|
|
70
91
|
@variant_name = variant_name unless variant_name.nil?
|
71
92
|
@variant_name ||= :control if excluded?
|
72
93
|
|
73
|
-
|
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)
|
104
|
+
end
|
74
105
|
end
|
75
106
|
end
|
76
107
|
|
@@ -104,6 +135,10 @@ module Gitlab
|
|
104
135
|
@excluded.any? { |exclude| exclude.call(self) }
|
105
136
|
end
|
106
137
|
|
138
|
+
def variant_assigned?
|
139
|
+
!@variant_name.nil?
|
140
|
+
end
|
141
|
+
|
107
142
|
def id
|
108
143
|
"#{name}:#{signature[:key]}"
|
109
144
|
end
|
@@ -113,6 +148,10 @@ module Gitlab
|
|
113
148
|
"Experiment;#{id}"
|
114
149
|
end
|
115
150
|
|
151
|
+
def key_for(hash)
|
152
|
+
instance_exec(hash, &Configuration.context_hash_strategy)
|
153
|
+
end
|
154
|
+
|
116
155
|
protected
|
117
156
|
|
118
157
|
def generate_result(variant_name)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
class Experiment
|
5
|
+
module Callbacks
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
include ActiveSupport::Callbacks
|
8
|
+
|
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
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
class_methods do
|
26
|
+
def segment(*filter_list, variant:, **options, &block)
|
27
|
+
filters = filter_list.unshift(block).compact.map do |filter|
|
28
|
+
result_lambda = ActiveSupport::Callbacks::CallTemplate.build(filter, self).make_lambda
|
29
|
+
->(target) { target.variant(variant) if result_lambda.call(target, nil) }
|
30
|
+
end
|
31
|
+
|
32
|
+
raise ArgumentError, 'no filters provided' if filters.empty?
|
33
|
+
|
34
|
+
set_callback(:segmented_run, :before, *filters, options, &block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -15,6 +15,9 @@ module Gitlab
|
|
15
15
|
# The logger is used to log various details of the experiments.
|
16
16
|
@logger = Logger.new($stdout)
|
17
17
|
|
18
|
+
# The base class that should be instantiated for basic experiments.
|
19
|
+
@base_class = 'Gitlab::Experiment'
|
20
|
+
|
18
21
|
# Cache layer. Expected to respond to fetch, like Rails.cache.
|
19
22
|
@cache = nil
|
20
23
|
|
@@ -35,20 +38,27 @@ module Gitlab
|
|
35
38
|
end
|
36
39
|
|
37
40
|
# Algorithm that consistently generates a hash key for a given hash map.
|
38
|
-
@context_hash_strategy = lambda do |
|
39
|
-
values =
|
40
|
-
Digest::MD5.hexdigest((
|
41
|
+
@context_hash_strategy = lambda do |hash_map|
|
42
|
+
values = hash_map.values.map { |v| (v.respond_to?(:to_global_id) ? v.to_global_id : v).to_s }
|
43
|
+
Digest::MD5.hexdigest(([name] + hash_map.keys + values).join('|'))
|
41
44
|
end
|
42
45
|
|
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
|
+
|
43
51
|
class << self
|
44
52
|
attr_accessor(
|
45
53
|
:name_prefix,
|
46
54
|
:logger,
|
55
|
+
:base_class,
|
47
56
|
:cache,
|
48
57
|
:variant_resolver,
|
49
58
|
:tracking_behavior,
|
50
59
|
:publishing_behavior,
|
51
|
-
:context_hash_strategy
|
60
|
+
:context_hash_strategy,
|
61
|
+
:cookie_domain
|
52
62
|
)
|
53
63
|
end
|
54
64
|
end
|
@@ -39,7 +39,7 @@ module Gitlab
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def signature
|
42
|
-
@signature ||= { key: key_for(@value), migration_keys: migration_keys }.compact
|
42
|
+
@signature ||= { key: @experiment.key_for(@value), migration_keys: migration_keys }.compact
|
43
43
|
end
|
44
44
|
|
45
45
|
private
|
@@ -60,12 +60,8 @@ module Gitlab
|
|
60
60
|
def migration_keys
|
61
61
|
return nil if @migrations[:unmerged].empty? && @migrations[:merged].empty?
|
62
62
|
|
63
|
-
@migrations[:unmerged].map { |m| key_for(m) } +
|
64
|
-
@migrations[:merged].map { |m| key_for(@value.merge(m)) }
|
65
|
-
end
|
66
|
-
|
67
|
-
def key_for(context)
|
68
|
-
Configuration.context_hash_strategy.call(context)
|
63
|
+
@migrations[:unmerged].map { |m| @experiment.key_for(m) } +
|
64
|
+
@migrations[:merged].map { |m| @experiment.key_for(@value.merge(m)) }
|
69
65
|
end
|
70
66
|
end
|
71
67
|
end
|
@@ -24,7 +24,7 @@ module Gitlab
|
|
24
24
|
return hash.merge(key => cookie) if hash[key].nil?
|
25
25
|
|
26
26
|
add_migration(key => cookie)
|
27
|
-
cookie_jar.delete(cookie_name, domain:
|
27
|
+
cookie_jar.delete(cookie_name, domain: domain)
|
28
28
|
|
29
29
|
hash
|
30
30
|
end
|
@@ -34,11 +34,15 @@ module Gitlab
|
|
34
34
|
|
35
35
|
cookie ||= SecureRandom.uuid
|
36
36
|
cookie_jar.permanent.signed[cookie_name] = {
|
37
|
-
value: cookie, secure: true, domain:
|
37
|
+
value: cookie, secure: true, domain: domain, httponly: true
|
38
38
|
}
|
39
39
|
|
40
40
|
hash.merge(key => cookie)
|
41
41
|
end
|
42
|
+
|
43
|
+
def domain
|
44
|
+
Configuration.cookie_domain
|
45
|
+
end
|
42
46
|
end
|
43
47
|
end
|
44
48
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gitlab-experiment
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
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-
|
11
|
+
date: 2020-11-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: scientist
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -39,11 +53,20 @@ extra_rdoc_files: []
|
|
39
53
|
files:
|
40
54
|
- LICENSE.txt
|
41
55
|
- README.md
|
42
|
-
- lib/generators/
|
43
|
-
- lib/generators/
|
44
|
-
- lib/generators/
|
56
|
+
- lib/generators/gitlab/experiment/USAGE
|
57
|
+
- lib/generators/gitlab/experiment/experiment_generator.rb
|
58
|
+
- lib/generators/gitlab/experiment/install/install_generator.rb
|
59
|
+
- lib/generators/gitlab/experiment/install/templates/POST_INSTALL
|
60
|
+
- lib/generators/gitlab/experiment/install/templates/application_experiment.rb.tt
|
61
|
+
- lib/generators/gitlab/experiment/install/templates/initializer.rb.tt
|
62
|
+
- lib/generators/gitlab/experiment/templates/experiment.rb.tt
|
63
|
+
- lib/generators/rspec/experiment/experiment_generator.rb
|
64
|
+
- lib/generators/rspec/experiment/templates/experiment_spec.rb.tt
|
65
|
+
- lib/generators/test_unit/experiment/experiment_generator.rb
|
66
|
+
- lib/generators/test_unit/experiment/templates/experiment_test.rb.tt
|
45
67
|
- lib/gitlab/experiment.rb
|
46
68
|
- lib/gitlab/experiment/caching.rb
|
69
|
+
- lib/gitlab/experiment/callbacks.rb
|
47
70
|
- lib/gitlab/experiment/configuration.rb
|
48
71
|
- lib/gitlab/experiment/context.rb
|
49
72
|
- lib/gitlab/experiment/cookies.rb
|
File without changes
|
@@ -1,21 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'rails/generators'
|
4
|
-
|
5
|
-
module GitlabExperiment
|
6
|
-
module Generators
|
7
|
-
class InstallGenerator < Rails::Generators::Base
|
8
|
-
source_root File.expand_path(__dir__)
|
9
|
-
|
10
|
-
desc 'Installs the Gitlab Experiment initializer into your application.'
|
11
|
-
|
12
|
-
def copy_initializers
|
13
|
-
copy_file 'templates/initializer.rb', 'config/initializers/gitlab_experiment.rb'
|
14
|
-
end
|
15
|
-
|
16
|
-
def display_post_install
|
17
|
-
readme 'POST_INSTALL' if behavior == :invoke
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|