gitlab-experiment 0.6.4 → 0.6.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed3c0028563936ce8a3fbdfd1de9d64e499102c4ec71a21946b63c56cc2bebd5
4
- data.tar.gz: 43389ef9b7b2a4b3e60b82b5ffefbe3635739cb9fafec21dd5e9d59586f942c2
3
+ metadata.gz: 9651e4cc4941d879e7c006f5865ff71989babd942bfcc09a86eb5be89f17e38c
4
+ data.tar.gz: d23055b9c808ae680c05762d60a569306069578a8555f87daf816a717cef0c28
5
5
  SHA512:
6
- metadata.gz: 30d0988d199f34eb60e3d56d8e7aec3a00222a5de482410c0294e15ce37a2a9dccbb2fb35b372c90b4425879938bec0d7e5b0ffadf5d842ee31abb0ea673aaeb
7
- data.tar.gz: 3665443e1d937b77e12debaf3195423b3fad171b651573e58a328dfb5c1f618eb9a282eedcfde68376ae0e7207837b74ae60e4fda616c046caa329b4cc681e68
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.
@@ -9,6 +9,7 @@ module Gitlab
9
9
  include ActiveSupport::Callbacks
10
10
 
11
11
  included do
12
+ define_callbacks(:run)
12
13
  define_callbacks(:unsegmented)
13
14
  define_callbacks(:segmentation_check)
14
15
  define_callbacks(:exclusion_check, skip_after_callbacks_if_terminated: true)
@@ -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?
@@ -4,5 +4,6 @@ module Gitlab
4
4
  class Experiment
5
5
  Error = Class.new(StandardError)
6
6
  InvalidRolloutRules = Class.new(Error)
7
+ NestingError = Class.new(Error)
7
8
  end
8
9
  end
@@ -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) }
@@ -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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Gitlab
4
4
  class Experiment
5
- VERSION = '0.6.4'
5
+ VERSION = '0.6.5'
6
6
  end
7
7
  end
@@ -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 ||= super(variant(variant_name).name)
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
@@ -155,6 +159,8 @@ module Gitlab
155
159
  return instance_exec(source, seed, &block)
156
160
  end
157
161
 
162
+ return source if source.is_a?(String)
163
+
158
164
  source = source.keys + source.values if source.is_a?(Hash)
159
165
 
160
166
  ingredients = Array(source).map { |v| identify(v) }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-experiment
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.4
4
+ version: 0.6.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitLab
@@ -91,12 +91,15 @@ files:
91
91
  - lib/gitlab/experiment
92
92
  - lib/gitlab/experiment/variant.rb
93
93
  - lib/gitlab/experiment/middleware.rb
94
+ - lib/gitlab/experiment/test_behaviors
95
+ - lib/gitlab/experiment/test_behaviors/trackable.rb
94
96
  - lib/gitlab/experiment/cache
95
97
  - lib/gitlab/experiment/cache/redis_hash_store.rb
96
98
  - lib/gitlab/experiment/errors.rb
97
99
  - lib/gitlab/experiment/callbacks.rb
98
100
  - lib/gitlab/experiment/rollout.rb
99
101
  - lib/gitlab/experiment/base_interface.rb
102
+ - lib/gitlab/experiment/nestable.rb
100
103
  - lib/gitlab/experiment/context.rb
101
104
  - lib/gitlab/experiment/engine.rb
102
105
  - lib/gitlab/experiment/rspec.rb
@@ -130,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
130
133
  - !ruby/object:Gem::Version
131
134
  version: '0'
132
135
  requirements: []
133
- rubygems_version: 3.1.4
136
+ rubygems_version: 3.1.6
134
137
  signing_key:
135
138
  specification_version: 4
136
139
  summary: GitLab experiment library built on top of scientist.