trailguide 0.2.1 → 0.3.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 +191 -293
- data/app/views/trail_guide/admin/experiments/_btn_join.html.erb +1 -1
- data/app/views/trail_guide/admin/experiments/_header.html.erb +3 -3
- data/config/initializers/admin.rb +19 -0
- data/config/initializers/experiment.rb +261 -0
- data/config/initializers/trailguide.rb +6 -279
- data/lib/trail_guide/adapters.rb +2 -0
- data/lib/trail_guide/adapters/experiments.rb +8 -0
- data/lib/trail_guide/adapters/experiments/redis.rb +48 -0
- data/lib/trail_guide/adapters/participants/cookie.rb +1 -0
- data/lib/trail_guide/adapters/participants/unity.rb +9 -1
- data/lib/trail_guide/adapters/variants.rb +8 -0
- data/lib/trail_guide/adapters/variants/redis.rb +52 -0
- data/lib/trail_guide/admin/engine.rb +1 -0
- data/lib/trail_guide/algorithms.rb +4 -0
- data/lib/trail_guide/algorithms/algorithm.rb +29 -0
- data/lib/trail_guide/algorithms/bandit.rb +9 -18
- data/lib/trail_guide/algorithms/distributed.rb +8 -15
- data/lib/trail_guide/algorithms/random.rb +2 -12
- data/lib/trail_guide/algorithms/static.rb +34 -0
- data/lib/trail_guide/algorithms/weighted.rb +5 -17
- data/lib/trail_guide/catalog.rb +79 -35
- data/lib/trail_guide/config.rb +2 -4
- data/lib/trail_guide/engine.rb +2 -1
- data/lib/trail_guide/experiments/base.rb +41 -24
- data/lib/trail_guide/experiments/combined_config.rb +4 -0
- data/lib/trail_guide/experiments/config.rb +59 -30
- data/lib/trail_guide/experiments/participant.rb +4 -2
- data/lib/trail_guide/helper.rb +4 -216
- data/lib/trail_guide/helper/experiment_proxy.rb +160 -0
- data/lib/trail_guide/helper/helper_proxy.rb +62 -0
- data/lib/trail_guide/metrics/config.rb +2 -0
- data/lib/trail_guide/metrics/goal.rb +17 -15
- data/lib/trail_guide/participant.rb +10 -2
- data/lib/trail_guide/unity.rb +17 -8
- data/lib/trail_guide/variant.rb +15 -11
- data/lib/trail_guide/version.rb +2 -2
- metadata +13 -3
@@ -32,11 +32,13 @@ module TrailGuide
|
|
32
32
|
|
33
33
|
# TODO do we allow a method here? do we call it on the experiment?
|
34
34
|
def allow_conversion(meth=nil, &block)
|
35
|
+
raise ArgumentError if meth.nil? && !block_given?
|
35
36
|
self[:allow_conversion] ||= []
|
36
37
|
self[:allow_conversion] << (meth || block)
|
37
38
|
end
|
38
39
|
|
39
40
|
def on_convert(meth=nil, &block)
|
41
|
+
raise ArgumentError if meth.nil? && !block_given?
|
40
42
|
self[:on_convert] ||= []
|
41
43
|
self[:on_convert] << (meth || block)
|
42
44
|
end
|
@@ -30,11 +30,12 @@ module TrailGuide
|
|
30
30
|
elsif other.is_a?(String) || other.is_a?(Symbol)
|
31
31
|
other = other.to_s.underscore
|
32
32
|
return name == other.to_sym || to_s == other
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
33
|
+
# Currently unused placeholder for future functionality
|
34
|
+
#elsif other.is_a?(Array)
|
35
|
+
# return to_s == other.flatten.map { |o| o.to_s.underscore }.join('/')
|
36
|
+
#elsif other.is_a?(Hash)
|
37
|
+
# # TODO "flatten" it out and compare it to_s
|
38
|
+
# return false
|
38
39
|
end
|
39
40
|
end
|
40
41
|
|
@@ -58,16 +59,17 @@ module TrailGuide
|
|
58
59
|
trial.send(callback, trial, result, self, *args)
|
59
60
|
end
|
60
61
|
end
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
62
|
+
# Currently unused placeholder for future functionality
|
63
|
+
#else
|
64
|
+
# args.unshift(self)
|
65
|
+
# args.unshift(trial)
|
66
|
+
# callbacks[hook].each do |callback|
|
67
|
+
# if callback.respond_to?(:call)
|
68
|
+
# callback.call(*args)
|
69
|
+
# else
|
70
|
+
# trial.send(callback, *args)
|
71
|
+
# end
|
72
|
+
# end
|
71
73
|
end
|
72
74
|
end
|
73
75
|
|
@@ -6,10 +6,12 @@ module TrailGuide
|
|
6
6
|
def initialize(context, adapter: nil)
|
7
7
|
@context = context
|
8
8
|
@adapter = adapter.new(context) if adapter.present?
|
9
|
+
|
9
10
|
cleanup_inactive_experiments! if TrailGuide.configuration.cleanup_participant_experiments == true
|
10
11
|
end
|
11
12
|
|
12
13
|
def adapter
|
14
|
+
# TODO move this case selection to Adapters::Participant (like Algorithms)?
|
13
15
|
@adapter ||= begin
|
14
16
|
config_adapter = TrailGuide.configuration.adapter
|
15
17
|
case config_adapter
|
@@ -37,6 +39,7 @@ module TrailGuide
|
|
37
39
|
|
38
40
|
def variant(experiment)
|
39
41
|
return nil unless experiment.calibrating? || experiment.started?
|
42
|
+
# TODO more efficient to stop checking if keys exist, and just return if the value is blank??
|
40
43
|
return nil unless adapter.key?(experiment.storage_key)
|
41
44
|
varname = adapter[experiment.storage_key]
|
42
45
|
variant = experiment.variants.find { |var| var == varname }
|
@@ -126,6 +129,7 @@ module TrailGuide
|
|
126
129
|
active = adapter.keys.map { |key| key.to_s.split(":").first.to_sym }.uniq.map do |key|
|
127
130
|
experiment = TrailGuide.catalog.find(key)
|
128
131
|
next unless experiment
|
132
|
+
next unless experiment.configuration.sticky_assignment?
|
129
133
|
|
130
134
|
if !experiment.started? && !experiment.calibrating?
|
131
135
|
inactive << key
|
@@ -142,17 +146,21 @@ module TrailGuide
|
|
142
146
|
end.each { |key| adapter.delete(key) }
|
143
147
|
end
|
144
148
|
|
149
|
+
return false if active.empty?
|
145
150
|
return active
|
146
151
|
end
|
147
152
|
|
148
153
|
def calibrating_experiments
|
149
154
|
return false if adapter.keys.empty?
|
150
155
|
|
151
|
-
adapter.keys.map { |key| key.to_s.split(":").first.to_sym }.uniq.map do |key|
|
156
|
+
calibrating = adapter.keys.map { |key| key.to_s.split(":").first.to_sym }.uniq.map do |key|
|
152
157
|
experiment = TrailGuide.catalog.find(key)
|
153
158
|
next unless experiment && experiment.calibrating?
|
154
159
|
[ experiment.experiment_name, adapter[experiment.storage_key] ]
|
155
160
|
end.compact.to_h
|
161
|
+
|
162
|
+
return false if calibrating.empty?
|
163
|
+
return calibrating
|
156
164
|
end
|
157
165
|
|
158
166
|
def participating_in_active_experiments?(include_control=true)
|
@@ -161,7 +169,7 @@ module TrailGuide
|
|
161
169
|
adapter.keys.any? do |key|
|
162
170
|
experiment_name = key.to_s.split(":").first.to_sym
|
163
171
|
experiment = TrailGuide.catalog.find(experiment_name)
|
164
|
-
experiment && !experiment.combined? && experiment.running? && participating?(experiment, include_control)
|
172
|
+
experiment && experiment.configuration.sticky_assignment? && !experiment.combined? && experiment.running? && participating?(experiment, include_control)
|
165
173
|
end
|
166
174
|
end
|
167
175
|
|
data/lib/trail_guide/unity.rb
CHANGED
@@ -1,13 +1,22 @@
|
|
1
1
|
module TrailGuide
|
2
2
|
class Unity
|
3
|
-
|
3
|
+
class << self
|
4
|
+
def configuration
|
5
|
+
@configuration ||= Canfig::Config.new(namespace: :unity)
|
6
|
+
end
|
7
|
+
|
8
|
+
def configure(*args, &block)
|
9
|
+
configuration.configure(*args, &block)
|
10
|
+
end
|
4
11
|
|
5
|
-
|
6
|
-
|
7
|
-
|
12
|
+
def clear!
|
13
|
+
keys = TrailGuide.redis.keys("#{configuration.namespace}:*")
|
14
|
+
TrailGuide.redis.del *keys unless keys.empty?
|
15
|
+
end
|
8
16
|
end
|
9
17
|
|
10
18
|
attr_reader :visitor_id, :user_id
|
19
|
+
delegate :configuration, to: :class
|
11
20
|
|
12
21
|
def initialize(user_id: nil, visitor_id: nil)
|
13
22
|
@user_id = user_id.to_s if user_id.present?
|
@@ -74,19 +83,19 @@ module TrailGuide
|
|
74
83
|
protected
|
75
84
|
|
76
85
|
def user_key
|
77
|
-
"#{
|
86
|
+
"#{configuration.namespace}:uids:#{user_id}"
|
78
87
|
end
|
79
88
|
|
80
89
|
def visitor_key
|
81
|
-
"#{
|
90
|
+
"#{configuration.namespace}:vids:#{visitor_id}"
|
82
91
|
end
|
83
92
|
|
84
93
|
def stored_user_key
|
85
|
-
"#{
|
94
|
+
"#{configuration.namespace}:uids:#{stored_user_id}"
|
86
95
|
end
|
87
96
|
|
88
97
|
def stored_visitor_key
|
89
|
-
"#{
|
98
|
+
"#{configuration.namespace}:vids:#{stored_visitor_id}"
|
90
99
|
end
|
91
100
|
end
|
92
101
|
end
|
data/lib/trail_guide/variant.rb
CHANGED
@@ -14,6 +14,10 @@ module TrailGuide
|
|
14
14
|
@control = control
|
15
15
|
end
|
16
16
|
|
17
|
+
def adapter
|
18
|
+
@adapter ||= TrailGuide::Adapters::Variants::Redis.new(self)
|
19
|
+
end
|
20
|
+
|
17
21
|
def ==(other)
|
18
22
|
if other.is_a?(self.class)
|
19
23
|
# TODO eventually remove the experiment requirement here once we start
|
@@ -48,15 +52,15 @@ module TrailGuide
|
|
48
52
|
end
|
49
53
|
|
50
54
|
def persisted?
|
51
|
-
|
55
|
+
adapter.persisted?
|
52
56
|
end
|
53
57
|
|
54
58
|
def save!
|
55
|
-
|
59
|
+
adapter.setnx(:name, name)
|
56
60
|
end
|
57
61
|
|
58
62
|
def delete!
|
59
|
-
|
63
|
+
adapter.destroy
|
60
64
|
end
|
61
65
|
|
62
66
|
def reset!
|
@@ -65,20 +69,20 @@ module TrailGuide
|
|
65
69
|
end
|
66
70
|
|
67
71
|
def participants
|
68
|
-
(
|
72
|
+
(adapter.get(:participants) || 0).to_i
|
69
73
|
end
|
70
74
|
|
71
75
|
def converted(checkpoint=nil)
|
72
76
|
if experiment.goals.empty?
|
73
77
|
raise InvalidGoalError, "You provided the checkpoint `#{checkpoint}` but the experiment `#{experiment.experiment_name}` does not have any goals defined." unless checkpoint.nil?
|
74
|
-
(
|
78
|
+
(adapter.get(:converted) || 0).to_i
|
75
79
|
elsif !checkpoint.nil?
|
76
80
|
goal = experiment.goals.find { |g| g == checkpoint }
|
77
81
|
raise InvalidGoalError, "Invalid goal checkpoint `#{checkpoint}` for experiment `#{experiment.experiment_name}`." if goal.nil?
|
78
|
-
(
|
82
|
+
(adapter.get(goal.name) || 0).to_i
|
79
83
|
else
|
80
84
|
experiment.goals.sum do |goal|
|
81
|
-
(
|
85
|
+
(adapter.get(goal.name) || 0).to_i
|
82
86
|
end
|
83
87
|
end
|
84
88
|
end
|
@@ -95,16 +99,16 @@ module TrailGuide
|
|
95
99
|
end
|
96
100
|
|
97
101
|
def increment_participation!
|
98
|
-
|
102
|
+
adapter.increment(:participants)
|
99
103
|
end
|
100
104
|
|
101
105
|
def increment_conversion!(checkpoint=nil)
|
102
106
|
if checkpoint.nil?
|
103
|
-
checkpoint =
|
107
|
+
checkpoint = :converted
|
104
108
|
else
|
105
|
-
checkpoint = experiment.goals.find { |g| g == checkpoint }.
|
109
|
+
checkpoint = experiment.goals.find { |g| g == checkpoint }.name
|
106
110
|
end
|
107
|
-
|
111
|
+
adapter.increment(checkpoint)
|
108
112
|
end
|
109
113
|
|
110
114
|
# export the variant state (not config) as json
|
data/lib/trail_guide/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trailguide
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mark Rebec
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-08-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -228,11 +228,15 @@ files:
|
|
228
228
|
- app/views/trail_guide/admin/groups/index.html.erb
|
229
229
|
- app/views/trail_guide/admin/groups/show.html.erb
|
230
230
|
- app/views/trail_guide/admin/orphans/_alert.html.erb
|
231
|
+
- config/initializers/admin.rb
|
231
232
|
- config/initializers/assets.rb
|
233
|
+
- config/initializers/experiment.rb
|
232
234
|
- config/initializers/trailguide.rb
|
233
235
|
- config/routes/admin.rb
|
234
236
|
- config/routes/engine.rb
|
235
237
|
- lib/trail_guide/adapters.rb
|
238
|
+
- lib/trail_guide/adapters/experiments.rb
|
239
|
+
- lib/trail_guide/adapters/experiments/redis.rb
|
236
240
|
- lib/trail_guide/adapters/participants.rb
|
237
241
|
- lib/trail_guide/adapters/participants/anonymous.rb
|
238
242
|
- lib/trail_guide/adapters/participants/base.rb
|
@@ -241,12 +245,16 @@ files:
|
|
241
245
|
- lib/trail_guide/adapters/participants/redis.rb
|
242
246
|
- lib/trail_guide/adapters/participants/session.rb
|
243
247
|
- lib/trail_guide/adapters/participants/unity.rb
|
248
|
+
- lib/trail_guide/adapters/variants.rb
|
249
|
+
- lib/trail_guide/adapters/variants/redis.rb
|
244
250
|
- lib/trail_guide/admin.rb
|
245
251
|
- lib/trail_guide/admin/engine.rb
|
246
252
|
- lib/trail_guide/algorithms.rb
|
253
|
+
- lib/trail_guide/algorithms/algorithm.rb
|
247
254
|
- lib/trail_guide/algorithms/bandit.rb
|
248
255
|
- lib/trail_guide/algorithms/distributed.rb
|
249
256
|
- lib/trail_guide/algorithms/random.rb
|
257
|
+
- lib/trail_guide/algorithms/static.rb
|
250
258
|
- lib/trail_guide/algorithms/weighted.rb
|
251
259
|
- lib/trail_guide/calculators.rb
|
252
260
|
- lib/trail_guide/calculators/bayesian.rb
|
@@ -263,6 +271,8 @@ files:
|
|
263
271
|
- lib/trail_guide/experiments/config.rb
|
264
272
|
- lib/trail_guide/experiments/participant.rb
|
265
273
|
- lib/trail_guide/helper.rb
|
274
|
+
- lib/trail_guide/helper/experiment_proxy.rb
|
275
|
+
- lib/trail_guide/helper/helper_proxy.rb
|
266
276
|
- lib/trail_guide/metrics.rb
|
267
277
|
- lib/trail_guide/metrics/checkpoint.rb
|
268
278
|
- lib/trail_guide/metrics/config.rb
|
@@ -294,7 +304,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
294
304
|
version: '0'
|
295
305
|
requirements: []
|
296
306
|
rubyforge_project:
|
297
|
-
rubygems_version: 2.7.
|
307
|
+
rubygems_version: 2.7.6
|
298
308
|
signing_key:
|
299
309
|
specification_version: 4
|
300
310
|
summary: User experiments for rails
|