trailguide 0.1.20 → 0.1.21
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/controllers/trail_guide/admin/application_controller.rb +32 -0
- data/app/views/trail_guide/admin/experiments/_combined_experiment.html.erb +18 -6
- data/app/views/trail_guide/admin/experiments/_experiment.html.erb +18 -6
- data/config/initializers/trailguide.rb +26 -0
- data/lib/trail_guide/config.rb +1 -1
- data/lib/trail_guide/engine.rb +4 -2
- data/lib/trail_guide/experiments/base.rb +18 -0
- data/lib/trail_guide/experiments/config.rb +1 -1
- data/lib/trail_guide/experiments/participant.rb +3 -3
- data/lib/trail_guide/participant.rb +8 -3
- data/lib/trail_guide/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ec80228061f031c01c0eb4d6dbef24d52ca205ccf2e6137ebe3a18886b095258
|
4
|
+
data.tar.gz: 6fa47e1dd04511cbb35d39a737589a34658fcc9d1f0dde64298908a9ca539f27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca6d1439e8632ec16946dcfc50e5201f24714a568a2b99c234d0ed666db6a6149724aeea7190be7669fe06dc232619cce6ead9211f175c1a518d2b6458dff7d4
|
7
|
+
data.tar.gz: 7ab9d62c95a8c3b0a63fd1b09f0b51bea8d021b98a6797acff0658fbe8a718d5b8665458df8b92b31e89c2e606788d1aa677e96a1ce21dfeb2516e780e8bfacb
|
@@ -15,6 +15,38 @@ module TrailGuide
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
helper_method :preview_url
|
18
|
+
|
19
|
+
def experiment_peekable?(experiment)
|
20
|
+
return false unless TrailGuide::Admin.configuration.peek_parameter
|
21
|
+
return false unless experiment.running?
|
22
|
+
return false if experiment.target_sample_size_reached?
|
23
|
+
return true
|
24
|
+
end
|
25
|
+
helper_method :experiment_peekable?
|
26
|
+
|
27
|
+
def experiment_peeking?(experiment)
|
28
|
+
params[TrailGuide::Admin.configuration.peek_parameter] == experiment.experiment_name.to_s
|
29
|
+
end
|
30
|
+
helper_method :experiment_peeking?
|
31
|
+
|
32
|
+
def experiment_metrics_visible?(experiment)
|
33
|
+
return true unless experiment.running?
|
34
|
+
return true if params[TrailGuide::Admin.configuration.peek_parameter] == experiment.experiment_name.to_s
|
35
|
+
return true if experiment.target_sample_size_reached?
|
36
|
+
return false
|
37
|
+
end
|
38
|
+
helper_method :experiment_metrics_visible?
|
39
|
+
|
40
|
+
def experiment_metric(experiment, metric)
|
41
|
+
return metric if experiment_metrics_visible?(experiment)
|
42
|
+
return '?'
|
43
|
+
end
|
44
|
+
helper_method :experiment_metric
|
45
|
+
|
46
|
+
def peek_url(experiment, *args, **opts)
|
47
|
+
trail_guide_admin.experiments_url(*args, opts.merge({TrailGuide::Admin.configuration.peek_parameter => experiment.experiment_name, anchor: experiment.experiment_name}))
|
48
|
+
end
|
49
|
+
helper_method :peek_url
|
18
50
|
end
|
19
51
|
end
|
20
52
|
end
|
@@ -8,6 +8,13 @@
|
|
8
8
|
</div>
|
9
9
|
<div class="col-sm-12 col-md-6 col-lg-4 text-right">
|
10
10
|
<% if combined_experiment.running? %>
|
11
|
+
<% if experiment_peekable?(combined_experiment) %>
|
12
|
+
<% if experiment_peeking?(combined_experiment) %>
|
13
|
+
<%= link_to "hide", trail_guide_admin.experiments_path(anchor: combined_experiment.experiment_name), class: 'btn btn-sm btn-outline-primary', method: :put %>
|
14
|
+
<% else %>
|
15
|
+
<%= link_to "peek", peek_url(combined_experiment), class: 'btn btn-sm btn-primary', method: :put %>
|
16
|
+
<% end %>
|
17
|
+
<% end %>
|
11
18
|
<%= link_to "stop", trail_guide_admin.stop_experiment_path(combined_experiment.experiment_name), class: 'btn btn-sm btn-warning', method: :put %>
|
12
19
|
<%= link_to "restart", trail_guide_admin.restart_experiment_path(combined_experiment.experiment_name), class: 'btn btn-sm btn-danger',method: :put %>
|
13
20
|
<% elsif combined_experiment.started? %>
|
@@ -84,13 +91,13 @@
|
|
84
91
|
<% end %>
|
85
92
|
</th>
|
86
93
|
|
87
|
-
<td><%= variant.participants %></td>
|
94
|
+
<td><%= experiment_metric combined_experiment, variant.participants %></td>
|
88
95
|
|
89
96
|
<% if experiment.goals.empty? %>
|
90
|
-
<td><%= variant.converted %></td>
|
97
|
+
<td><%= experiment_metric combined_experiment, variant.converted %></td>
|
91
98
|
<% else %>
|
92
99
|
<% experiment.goals.each do |goal| %>
|
93
|
-
<td><%= variant.converted(goal) %></td>
|
100
|
+
<td><%= experiment_metric combined_experiment, variant.converted(goal) %></td>
|
94
101
|
<% end %>
|
95
102
|
<% end %>
|
96
103
|
|
@@ -117,12 +124,17 @@
|
|
117
124
|
<tfoot class="thead-light">
|
118
125
|
<tr>
|
119
126
|
<th scope="row"> </th>
|
120
|
-
<th
|
127
|
+
<th>
|
128
|
+
<span><%= combined_experiment.participants %></span>
|
129
|
+
<% if combined_experiment.configuration.target_sample_size %>
|
130
|
+
<span class="text-muted">/ <%= combined_experiment.configuration.target_sample_size %></span>
|
131
|
+
<% end %>
|
132
|
+
</th>
|
121
133
|
<% if combined_experiment.goals.empty? %>
|
122
|
-
<th><%=
|
134
|
+
<th><%= experiment_metric combined_experiment, combined_experiments.sum(&:converted) %></th>
|
123
135
|
<% else %>
|
124
136
|
<% combined_experiment.goals.each do |goal| %>
|
125
|
-
<th><%= combined_experiments.sum { |e| e.
|
137
|
+
<th><%= experiment_metric combined_experiment, combined_experiments.sum { |e| e.converted(goal) } %></th>
|
126
138
|
<% end %>
|
127
139
|
<% end %>
|
128
140
|
<th> </th>
|
@@ -8,6 +8,13 @@
|
|
8
8
|
</div>
|
9
9
|
<div class="col-sm-12 col-md-6 col-lg-4 text-right">
|
10
10
|
<% if experiment.running? %>
|
11
|
+
<% if experiment_peekable?(experiment) %>
|
12
|
+
<% if experiment_peeking?(experiment) %>
|
13
|
+
<%= link_to "hide", trail_guide_admin.experiments_path(anchor: experiment.experiment_name), class: 'btn btn-sm btn-outline-primary', method: :put %>
|
14
|
+
<% else %>
|
15
|
+
<%= link_to "peek", peek_url(experiment), class: 'btn btn-sm btn-primary', method: :put %>
|
16
|
+
<% end %>
|
17
|
+
<% end %>
|
11
18
|
<%= link_to "stop", trail_guide_admin.stop_experiment_path(experiment.experiment_name), class: 'btn btn-sm btn-warning', method: :put %>
|
12
19
|
<%= link_to "restart", trail_guide_admin.restart_experiment_path(experiment.experiment_name), class: 'btn btn-sm btn-danger',method: :put %>
|
13
20
|
<% elsif experiment.started? %>
|
@@ -78,13 +85,13 @@
|
|
78
85
|
<% end %>
|
79
86
|
</th>
|
80
87
|
|
81
|
-
<td><%= variant.participants %></td>
|
88
|
+
<td><%= experiment_metric experiment, variant.participants %></td>
|
82
89
|
|
83
90
|
<% if experiment.goals.empty? %>
|
84
|
-
<td><%= variant.converted %></td>
|
91
|
+
<td><%= experiment_metric experiment, variant.converted %></td>
|
85
92
|
<% else %>
|
86
93
|
<% experiment.goals.each do |goal| %>
|
87
|
-
<td><%= variant.converted(goal) %></td>
|
94
|
+
<td><%= experiment_metric experiment, variant.converted(goal) %></td>
|
88
95
|
<% end %>
|
89
96
|
<% end %>
|
90
97
|
|
@@ -110,12 +117,17 @@
|
|
110
117
|
<tfoot class="thead-light">
|
111
118
|
<tr>
|
112
119
|
<th scope="row"> </th>
|
113
|
-
<th
|
120
|
+
<th>
|
121
|
+
<span><%= experiment.participants %></span>
|
122
|
+
<% if experiment.configuration.target_sample_size %>
|
123
|
+
<span class="text-muted">/ <%= experiment.configuration.target_sample_size %></span>
|
124
|
+
<% end %>
|
125
|
+
</th>
|
114
126
|
<% if experiment.goals.empty? %>
|
115
|
-
<th><%= experiment.
|
127
|
+
<th><%= experiment_metric experiment, experiment.converted %></th>
|
116
128
|
<% else %>
|
117
129
|
<% experiment.goals.each do |goal| %>
|
118
|
-
<th><%= experiment
|
130
|
+
<th><%= experiment_metric experiment, experiment.converted(goal) %></th>
|
119
131
|
<% end %>
|
120
132
|
<% end %>
|
121
133
|
<th> </th>
|
@@ -7,6 +7,13 @@ TrailGuide.configure do |config|
|
|
7
7
|
# globally disable trailguide - returns control everywhere
|
8
8
|
config.disabled = false
|
9
9
|
|
10
|
+
# whether or not to include TrailGuide::Helper into controller and view
|
11
|
+
# contexts
|
12
|
+
#
|
13
|
+
# true the helper module is automatically mixed-in to controllers/views
|
14
|
+
# false you'll need to include the helper module manually where you want
|
15
|
+
config.include_helpers = true
|
16
|
+
|
10
17
|
# request param for overriding/previewing variants - allows previewing
|
11
18
|
# variants with request params
|
12
19
|
# i.e. example.com/somepage/?experiment[my_experiment]=option_b
|
@@ -146,6 +153,12 @@ TrailGuide::Experiment.configure do |config|
|
|
146
153
|
# false default behavior, requests will be filtered based on your config
|
147
154
|
config.skip_request_filter = false
|
148
155
|
|
156
|
+
# set a default target sample size for all experiments - this will prevent
|
157
|
+
# metrics and stats from being displayed in the admin UI until the sample size
|
158
|
+
# is reached or the experiment is stopped
|
159
|
+
#
|
160
|
+
# config.target_sample_size = nil
|
161
|
+
|
149
162
|
# callback when connecting to redis fails and trailguide falls back to always
|
150
163
|
# returning control variants
|
151
164
|
config.on_redis_failover = -> (experiment, error) do
|
@@ -227,6 +240,19 @@ end
|
|
227
240
|
# admin ui configuration
|
228
241
|
#
|
229
242
|
TrailGuide::Admin.configure do |config|
|
243
|
+
# display title for admin UI
|
244
|
+
#
|
230
245
|
config.title = 'TrailGuide'
|
246
|
+
|
247
|
+
# display subtitle for admin UI
|
248
|
+
#
|
231
249
|
config.subtitle = 'Experiments and A/B Tests'
|
250
|
+
|
251
|
+
# request parameter can be used to "peek" at results even before an
|
252
|
+
# experiment's target_sample_size has been hit if one is configured
|
253
|
+
#
|
254
|
+
# if you set this to nil, admins will not be able to peek at experiment
|
255
|
+
# results until the target sample size is hit or the experiment is stopped
|
256
|
+
#
|
257
|
+
config.peek_parameter = nil
|
232
258
|
end
|
data/lib/trail_guide/config.rb
CHANGED
@@ -3,7 +3,7 @@ module TrailGuide
|
|
3
3
|
DEFAULT_KEYS = [
|
4
4
|
:redis, :disabled, :override_parameter, :allow_multiple_experiments,
|
5
5
|
:adapter, :on_adapter_failover, :filtered_ip_addresses,
|
6
|
-
:filtered_user_agents, :request_filter
|
6
|
+
:filtered_user_agents, :request_filter, :include_helpers
|
7
7
|
].freeze
|
8
8
|
|
9
9
|
def initialize(*args, **opts, &block)
|
data/lib/trail_guide/engine.rb
CHANGED
@@ -8,8 +8,10 @@ module TrailGuide
|
|
8
8
|
|
9
9
|
initializer "trailguide" do |app|
|
10
10
|
TrailGuide::Catalog.load_experiments!
|
11
|
-
|
12
|
-
|
11
|
+
if TrailGuide.configuration.include_helpers
|
12
|
+
ActionController::Base.send :include, TrailGuide::Helper
|
13
|
+
ActionController::Base.helper TrailGuide::Helper
|
14
|
+
end
|
13
15
|
end
|
14
16
|
end
|
15
17
|
end
|
@@ -131,6 +131,24 @@ module TrailGuide
|
|
131
131
|
reset
|
132
132
|
end
|
133
133
|
|
134
|
+
def participants
|
135
|
+
variants.sum(&:participants)
|
136
|
+
end
|
137
|
+
|
138
|
+
def converted(checkpoint=nil)
|
139
|
+
variants.sum { |var| var.converted(checkpoint) }
|
140
|
+
end
|
141
|
+
|
142
|
+
def unconverted
|
143
|
+
participants - converted
|
144
|
+
end
|
145
|
+
|
146
|
+
def target_sample_size_reached?
|
147
|
+
return true unless configuration.target_sample_size
|
148
|
+
return true if participants >= configuration.target_sample_size
|
149
|
+
return false
|
150
|
+
end
|
151
|
+
|
134
152
|
def as_json(opts={})
|
135
153
|
{ experiment_name => {
|
136
154
|
configuration: {
|
@@ -5,7 +5,7 @@ module TrailGuide
|
|
5
5
|
:name, :summary, :preview_url, :algorithm, :metric, :variants, :goals,
|
6
6
|
:start_manually, :reset_manually, :store_override, :track_override,
|
7
7
|
:combined, :allow_multiple_conversions, :allow_multiple_goals,
|
8
|
-
:track_winner_conversions, :skip_request_filter
|
8
|
+
:track_winner_conversions, :skip_request_filter, :target_sample_size
|
9
9
|
].freeze
|
10
10
|
|
11
11
|
CALLBACK_KEYS = [
|
@@ -9,15 +9,15 @@ module TrailGuide
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def participating?
|
12
|
-
participant.participating?(experiment)
|
12
|
+
@participating ||= variant.present?#participant.participating?(experiment)
|
13
13
|
end
|
14
14
|
|
15
15
|
def converted?(checkpoint=nil)
|
16
|
-
participant.converted?(experiment, checkpoint)
|
16
|
+
@converted ||= participant.converted?(experiment, checkpoint)
|
17
17
|
end
|
18
18
|
|
19
19
|
def variant
|
20
|
-
participant.variant(experiment)
|
20
|
+
@variant ||= participant.variant(experiment)
|
21
21
|
end
|
22
22
|
|
23
23
|
def method_missing(meth, *args, &block)
|
@@ -1,12 +1,13 @@
|
|
1
1
|
module TrailGuide
|
2
2
|
class Participant
|
3
|
-
attr_reader :context
|
3
|
+
attr_reader :context, :variants
|
4
4
|
delegate :key?, :keys, :[], :[]=, :delete, :destroy!, :to_h, to: :adapter
|
5
5
|
|
6
6
|
def initialize(context, adapter: nil)
|
7
7
|
@context = context
|
8
8
|
@adapter = adapter.new(context) unless adapter.nil?
|
9
|
-
|
9
|
+
@variants = {}
|
10
|
+
#cleanup_inactive_experiments!
|
10
11
|
end
|
11
12
|
|
12
13
|
def adapter
|
@@ -36,6 +37,7 @@ module TrailGuide
|
|
36
37
|
end
|
37
38
|
|
38
39
|
def participating?(experiment, include_control=true)
|
40
|
+
return variants[experiment.storage_key] if variants.key?(experiment.storage_key)
|
39
41
|
return false unless experiment.started?
|
40
42
|
return false unless adapter.key?(experiment.storage_key)
|
41
43
|
varname = adapter[experiment.storage_key]
|
@@ -44,7 +46,10 @@ module TrailGuide
|
|
44
46
|
return false unless variant && adapter.key?(variant.storage_key)
|
45
47
|
|
46
48
|
chosen_at = Time.at(adapter[variant.storage_key].to_i)
|
47
|
-
|
49
|
+
if chosen_at >= experiment.started_at
|
50
|
+
variants[experiment.storage_key] = variant
|
51
|
+
return variant
|
52
|
+
end
|
48
53
|
end
|
49
54
|
|
50
55
|
def converted?(experiment, checkpoint=nil)
|
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.1.
|
4
|
+
version: 0.1.21
|
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-03-
|
11
|
+
date: 2019-03-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|