trailguide 0.1.20 → 0.1.21

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c45506f04eec39f23d00fce31f84d1d1e24f4289e2870af413f8928d54cceef8
4
- data.tar.gz: 1b6cb001e0fa544f6390e4a82e265922f9ab75b602f4406a2cddf337704fd076
3
+ metadata.gz: ec80228061f031c01c0eb4d6dbef24d52ca205ccf2e6137ebe3a18886b095258
4
+ data.tar.gz: 6fa47e1dd04511cbb35d39a737589a34658fcc9d1f0dde64298908a9ca539f27
5
5
  SHA512:
6
- metadata.gz: 757e5c2ad76a1143db739638fc60ccaf263d047c2ea3f41172c74368c8c3a2744ba760b0255c6393623ba2266df5e6ec4bff3f3c4e6898370eeefb536b7b5e0b
7
- data.tar.gz: 820c4664c60c161dfad8e81dcab8cb61e4137d9961c97e5b0da3f9dfdb5e496692d3bc1bd7f9784645095dfb4be38c75bb73604b80e6e855d42ec2bdac187de0
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">&nbsp;</th>
120
- <th><%= combined_experiment.variants.sum(&:participants) %></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><%= combined_experiments.sum { |e| e.variants.sum(&:converted) } %></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.variants.sum { |v| v.converted(goal) } } %></th>
137
+ <th><%= experiment_metric combined_experiment, combined_experiments.sum { |e| e.converted(goal) } %></th>
126
138
  <% end %>
127
139
  <% end %>
128
140
  <th>&nbsp;</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">&nbsp;</th>
113
- <th><%= experiment.variants.sum(&:participants) %></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.variants.sum(&:converted) %></th>
127
+ <th><%= experiment_metric experiment, experiment.converted %></th>
116
128
  <% else %>
117
129
  <% experiment.goals.each do |goal| %>
118
- <th><%= experiment.variants.sum { |var| var.converted(goal) } %></th>
130
+ <th><%= experiment_metric experiment, experiment.converted(goal) %></th>
119
131
  <% end %>
120
132
  <% end %>
121
133
  <th>&nbsp;</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
@@ -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)
@@ -8,8 +8,10 @@ module TrailGuide
8
8
 
9
9
  initializer "trailguide" do |app|
10
10
  TrailGuide::Catalog.load_experiments!
11
- ActionController::Base.send :include, TrailGuide::Helper
12
- ActionController::Base.helper TrailGuide::Helper
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
- cleanup_inactive_experiments!
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
- return variant if chosen_at >= experiment.started_at
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)
@@ -2,7 +2,7 @@ module TrailGuide
2
2
  module Version
3
3
  MAJOR = 0
4
4
  MINOR = 1
5
- PATCH = 20
5
+ PATCH = 21
6
6
  VERSION = "#{MAJOR}.#{MINOR}.#{PATCH}"
7
7
 
8
8
  class << self
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.20
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-12 00:00:00.000000000 Z
11
+ date: 2019-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails