trailguide 0.1.30 → 0.1.31

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.
@@ -18,7 +18,7 @@ module TrailGuide
18
18
 
19
19
  def experiment_peekable?(experiment)
20
20
  return false unless TrailGuide::Admin.configuration.peek_parameter
21
- return false unless experiment.running?
21
+ return false unless experiment.started? && !experiment.stopped?
22
22
  return false if experiment.target_sample_size_reached?
23
23
  return true
24
24
  end
@@ -30,7 +30,7 @@ module TrailGuide
30
30
  helper_method :experiment_peeking?
31
31
 
32
32
  def experiment_metrics_visible?(experiment)
33
- return true unless experiment.running?
33
+ return true unless experiment.started? && !experiment.stopped?
34
34
  return true if params[TrailGuide::Admin.configuration.peek_parameter] == experiment.experiment_name.to_s
35
35
  return true if experiment.target_sample_size_reached?
36
36
  return false
@@ -38,7 +38,7 @@ module TrailGuide
38
38
  helper_method :experiment_metrics_visible?
39
39
 
40
40
  def experiment_metric(experiment, metric)
41
- return metric if experiment_metrics_visible?(experiment)
41
+ return helpers.number_with_delimiter(metric) if experiment_metrics_visible?(experiment)
42
42
  return helpers.content_tag('span', nil, class: 'fas fa-eye-slash', data: {toggle: 'tooltip'}, title: "metrics are hidden until this experiment reaches it's target sample size")
43
43
  end
44
44
  helper_method :experiment_metric
@@ -5,32 +5,42 @@ module TrailGuide
5
5
  (redirect_to :back rescue redirect_to trail_guide_admin.experiments_path) and return unless experiment.present?
6
6
  end
7
7
 
8
+ before_action :experiments, only: [:index]
9
+
8
10
  def index
9
11
  end
10
12
 
11
13
  def start
12
14
  experiment.start!(self)
13
- redirect_to trail_guide_admin.experiments_path(anchor: experiment.experiment_name)
15
+ redirect_to trail_guide_admin.scoped_experiments_path(scope: :running, anchor: experiment.experiment_name)
16
+ end
17
+
18
+ def pause
19
+ experiment.pause!(self)
20
+ redirect_to trail_guide_admin.scoped_experiments_path(scope: :paused, anchor: experiment.experiment_name)
14
21
  end
15
22
 
16
23
  def stop
17
24
  experiment.stop!(self)
18
- redirect_to trail_guide_admin.experiments_path(anchor: experiment.experiment_name)
25
+ redirect_to trail_guide_admin.scoped_experiments_path(scope: :stopped, anchor: experiment.experiment_name)
19
26
  end
20
27
 
21
28
  def reset
29
+ experiment.stop!(self)
22
30
  experiment.reset!(self)
23
31
  redirect_to trail_guide_admin.experiments_path(anchor: experiment.experiment_name)
24
32
  end
25
33
 
26
34
  def resume
27
35
  experiment.resume!(self)
28
- redirect_to trail_guide_admin.experiments_path(anchor: experiment.experiment_name)
36
+ redirect_to trail_guide_admin.scoped_experiments_path(scope: :running, anchor: experiment.experiment_name)
29
37
  end
30
38
 
31
39
  def restart
32
- experiment.reset!(self) && experiment.start!(self)
33
- redirect_to trail_guide_admin.experiments_path(anchor: experiment.experiment_name)
40
+ experiment.stop!(self)
41
+ experiment.reset!(self)
42
+ experiment.start!(self)
43
+ redirect_to trail_guide_admin.scoped_experiments_path(scope: :running, anchor: experiment.experiment_name)
34
44
  end
35
45
 
36
46
  def join
@@ -58,6 +68,12 @@ module TrailGuide
58
68
 
59
69
  private
60
70
 
71
+ def experiments
72
+ @experiments = TrailGuide.catalog
73
+ @experiments = @experiments.send(params[:scope]) if params[:scope].present?
74
+ @experiments = @experiments.by_started
75
+ end
76
+
61
77
  def experiment
62
78
  @experiment ||= TrailGuide.catalog.find(params[:id])
63
79
  end
@@ -4,11 +4,39 @@
4
4
  <%= TrailGuide::Admin.configuration.title %>
5
5
  <% end %>
6
6
  <div class="col-sm text-center">
7
- <strong class="total"><%= TrailGuide.catalog.count %></strong> experiments
7
+ <%= link_to trail_guide_admin.experiments_path, class: "text-dark" do %>
8
+ <strong class="total">
9
+ <%= TrailGuide.catalog.count %>
10
+ </strong>
11
+ <span>experiments</span>
12
+ <% end %>
13
+
8
14
  <span>/</span>
9
- <strong class="running"><%= TrailGuide.catalog.running.count %></strong> running
15
+
16
+ <%= link_to trail_guide_admin.scoped_experiments_path(scope: :running), class: "text-dark" do %>
17
+ <strong class="total">
18
+ <%= TrailGuide.catalog.running.count %>
19
+ </strong>
20
+ <span>running</span>
21
+ <% end %>
22
+
10
23
  <span>/</span>
11
- <strong class="stopped"><%= TrailGuide.catalog.stopped.count %></strong> stopped
24
+
25
+ <%= link_to trail_guide_admin.scoped_experiments_path(scope: :paused), class: "text-dark" do %>
26
+ <strong class="total">
27
+ <%= TrailGuide.catalog.paused.count %>
28
+ </strong>
29
+ <span>paused</span>
30
+ <% end %>
31
+
32
+ <span>/</span>
33
+
34
+ <%= link_to trail_guide_admin.scoped_experiments_path(scope: :stopped), class: "text-dark" do %>
35
+ <strong class="total">
36
+ <%= TrailGuide.catalog.stopped.count %>
37
+ </strong>
38
+ <span>stopped</span>
39
+ <% end %>
12
40
  </div>
13
41
  <span class="navbar-brand"><small class="text-muted"><%= TrailGuide::Admin.configuration.subtitle %></small></span>
14
42
  </nav>
@@ -19,17 +19,37 @@
19
19
  <% end %>
20
20
  <% end %>
21
21
  <% end %>
22
- <%= link_to trail_guide_admin.stop_experiment_path(combined_experiment.experiment_name), class: 'btn btn-sm btn-warning', method: :put, data: {toggle: :tooltip}, title: 'pause this experiment - you will have the option to resume or reset' do %>
23
- <span class="fas fa-pause" />
22
+
23
+ <% if combined_experiment.configuration.can_resume? %>
24
+ <%= link_to trail_guide_admin.pause_experiment_path(combined_experiment.experiment_name), class: 'btn btn-sm btn-warning', method: :put, data: {toggle: :tooltip}, title: 'pause this experiment - you will have the option to resume or reset' do %>
25
+ <span class="fas fa-pause" />
26
+ <% end %>
27
+ <% end %>
28
+
29
+ <%= link_to trail_guide_admin.stop_experiment_path(combined_experiment.experiment_name), class: 'btn btn-sm btn-danger', method: :put, data: {toggle: :tooltip}, title: 'stop this experiment - once stopped you will need to reset before restarting' do %>
30
+ <span class="fas fa-stop" />
24
31
  <% end %>
32
+
25
33
  <%= link_to trail_guide_admin.restart_experiment_path(combined_experiment.experiment_name), class: 'btn btn-sm btn-danger',method: :put, data: {toggle: :tooltip}, title: 'restart this experiment - will reset all data and restart the experiment' do %>
26
34
  <span class="fas fa-redo" />
27
35
  <% end %>
28
- <% elsif combined_experiment.started? %>
29
- <%= link_to trail_guide_admin.resume_experiment_path(combined_experiment.experiment_name), class: 'btn btn-sm btn-success',method: :put, data: {toggle: :tooltip}, title: 'resume this experiment to start bucketing users and serving variants again' do %>
36
+ <% elsif combined_experiment.paused? %>
37
+ <% if combined_experiment.configuration.can_resume? %>
38
+ <%= link_to trail_guide_admin.resume_experiment_path(combined_experiment.experiment_name), class: 'btn btn-sm btn-success',method: :put, data: {toggle: :tooltip}, title: 'resume this experiment to start bucketing users and serving variants again' do %>
39
+ <span class="fas fa-redo" />
40
+ <% end %>
41
+ <%= link_to trail_guide_admin.stop_experiment_path(combined_experiment.experiment_name), class: 'btn btn-sm btn-danger', method: :put, data: {toggle: :tooltip}, title: 'stop this experiment - once stopped you will need to reset before restarting' do %>
42
+ <span class="fas fa-stop" />
43
+ <% end %>
44
+ <% end %>
45
+ <%= link_to trail_guide_admin.restart_experiment_path(combined_experiment.experiment_name), class: 'btn btn-sm btn-danger',method: :put, data: {toggle: :tooltip}, title: 'restart this experiment - will reset all data and restart the experiment' do %>
30
46
  <span class="fas fa-redo" />
31
47
  <% end %>
32
- <% else %>
48
+ <% elsif combined_experiment.stopped? %>
49
+ <%= link_to trail_guide_admin.restart_experiment_path(combined_experiment.experiment_name), class: 'btn btn-sm btn-danger',method: :put, data: {toggle: :tooltip}, title: 'restart this experiment - will reset all data and restart the experiment' do %>
50
+ <span class="fas fa-redo" />
51
+ <% end %>
52
+ <% elsif !combined_experiment.started? %>
33
53
  <%= link_to trail_guide_admin.start_experiment_path(combined_experiment.experiment_name), class: 'btn btn-sm btn-success',method: :put, data: {toggle: :tooltip}, title: 'start this experiment' do %>
34
54
  <span class="fas fa-play" />
35
55
  <% end %>
@@ -50,6 +70,8 @@
50
70
  <span class="text-muted">&mdash;</span>
51
71
  <% if combined_experiment.stopped? %>
52
72
  <small class="text-muted"><%= combined_experiment.stopped_at.strftime('%b %e %Y @ %l:%M %p') %></small>
73
+ <% elsif combined_experiment.paused? %>
74
+ <small class="text-muted">paused</small>
53
75
  <% else %>
54
76
  <small class="text-muted">running</small>
55
77
  <% end %>
@@ -147,9 +169,9 @@
147
169
  <tr>
148
170
  <th scope="row">&nbsp;</th>
149
171
  <th>
150
- <span><%= combined_experiment.participants %></span>
172
+ <span><%= number_with_delimiter combined_experiment.participants %></span>
151
173
  <% if combined_experiment.configuration.target_sample_size %>
152
- <span class="text-muted">/ <%= combined_experiment.configuration.target_sample_size %></span>
174
+ <span class="text-muted">/ <%= number_with_delimiter combined_experiment.configuration.target_sample_size %></span>
153
175
  <% end %>
154
176
  </th>
155
177
  <% if combined_experiment.goals.empty? %>
@@ -19,17 +19,37 @@
19
19
  <% end %>
20
20
  <% end %>
21
21
  <% end %>
22
- <%= link_to trail_guide_admin.stop_experiment_path(experiment.experiment_name), class: 'btn btn-sm btn-warning', method: :put, data: {toggle: :tooltip}, title: 'pause this experiment - you will have the option to resume or reset' do %>
23
- <span class="fas fa-pause" />
22
+
23
+ <% if experiment.configuration.can_resume? %>
24
+ <%= link_to trail_guide_admin.pause_experiment_path(experiment.experiment_name), class: 'btn btn-sm btn-warning', method: :put, data: {toggle: :tooltip}, title: 'pause this experiment - you will have the option to resume or reset' do %>
25
+ <span class="fas fa-pause" />
26
+ <% end %>
27
+ <% end %>
28
+
29
+ <%= link_to trail_guide_admin.stop_experiment_path(experiment.experiment_name), class: 'btn btn-sm btn-danger', method: :put, data: {toggle: :tooltip}, title: 'stop this experiment - once stopped you will need to reset before restarting' do %>
30
+ <span class="fas fa-stop" />
24
31
  <% end %>
32
+
25
33
  <%= link_to trail_guide_admin.restart_experiment_path(experiment.experiment_name), class: 'btn btn-sm btn-danger',method: :put, data: {toggle: :tooltip}, title: 'restart this experiment - will reset all data and restart the experiment' do %>
26
34
  <span class="fas fa-redo" />
27
35
  <% end %>
28
- <% elsif experiment.started? %>
29
- <%= link_to trail_guide_admin.resume_experiment_path(experiment.experiment_name), class: 'btn btn-sm btn-success',method: :put, data: {toggle: :tooltip}, title: 'resume this experiment to start bucketing users and serving variants again' do %>
36
+ <% elsif experiment.paused? %>
37
+ <% if experiment.configuration.can_resume? %>
38
+ <%= link_to trail_guide_admin.resume_experiment_path(experiment.experiment_name), class: 'btn btn-sm btn-success',method: :put, data: {toggle: :tooltip}, title: 'resume this experiment to start bucketing users and serving variants again' do %>
39
+ <span class="fas fa-redo" />
40
+ <% end %>
41
+ <%= link_to trail_guide_admin.stop_experiment_path(experiment.experiment_name), class: 'btn btn-sm btn-danger', method: :put, data: {toggle: :tooltip}, title: 'stop this experiment - once stopped you will need to reset before restarting' do %>
42
+ <span class="fas fa-stop" />
43
+ <% end %>
44
+ <% end %>
45
+ <%= link_to trail_guide_admin.restart_experiment_path(experiment.experiment_name), class: 'btn btn-sm btn-danger',method: :put, data: {toggle: :tooltip}, title: 'restart this experiment - will reset all data and restart the experiment' do %>
30
46
  <span class="fas fa-redo" />
31
47
  <% end %>
32
- <% else %>
48
+ <% elsif experiment.stopped? %>
49
+ <%= link_to trail_guide_admin.restart_experiment_path(experiment.experiment_name), class: 'btn btn-sm btn-danger',method: :put, data: {toggle: :tooltip}, title: 'restart this experiment - will reset all data and restart the experiment' do %>
50
+ <span class="fas fa-redo" />
51
+ <% end %>
52
+ <% elsif !experiment.started? %>
33
53
  <%= link_to trail_guide_admin.start_experiment_path(experiment.experiment_name), class: 'btn btn-sm btn-success',method: :put, data: {toggle: :tooltip}, title: 'start this experiment' do %>
34
54
  <span class="fas fa-play" />
35
55
  <% end %>
@@ -50,6 +70,8 @@
50
70
  <span class="text-muted">&mdash;</span>
51
71
  <% if experiment.stopped? %>
52
72
  <small class="text-muted"><%= experiment.stopped_at.strftime('%b %e %Y @ %l:%M %p') %></small>
73
+ <% elsif experiment.paused? %>
74
+ <small class="text-muted">paused</small>
53
75
  <% else %>
54
76
  <small class="text-muted">running</small>
55
77
  <% end %>
@@ -140,9 +162,9 @@
140
162
  <tr>
141
163
  <th scope="row">&nbsp;</th>
142
164
  <th>
143
- <span><%= experiment.participants %></span>
165
+ <span><%= number_with_delimiter experiment.participants %></span>
144
166
  <% if experiment.configuration.target_sample_size %>
145
- <span class="text-muted">/ <%= experiment.configuration.target_sample_size %></span>
167
+ <span class="text-muted">/ <%= number_with_delimiter experiment.configuration.target_sample_size %></span>
146
168
  <% end %>
147
169
  </th>
148
170
  <% if experiment.goals.empty? %>
@@ -9,7 +9,7 @@
9
9
  <% end %>
10
10
 
11
11
  <div class="experiments">
12
- <% TrailGuide.catalog.by_started.each do |experiment| %>
12
+ <% @experiments.each do |experiment| %>
13
13
  <% if experiment.combined? %>
14
14
  <%= render 'combined_experiment', combined_experiment: experiment %>
15
15
  <% else %>
@@ -1,6 +1,9 @@
1
1
  # top-level trailguide rails engine configuration
2
2
  #
3
3
  TrailGuide.configure do |config|
4
+ # logger object
5
+ config.logger = Rails.logger
6
+
4
7
  # url string or initialized Redis object
5
8
  config.redis = ENV['REDIS_URL']
6
9
 
@@ -73,7 +76,7 @@ TrailGuide.configure do |config|
73
76
  # callback when your participant adapter fails to initialize, and trailguide
74
77
  # falls back to the anonymous adapter
75
78
  config.on_adapter_failover = -> (adapter, error) do
76
- Rails.logger.error("#{error.class.name}: #{error.message}")
79
+ TrailGuide.logger.error error
77
80
  end
78
81
 
79
82
  # list of user agents used by the default request filter proc below when
@@ -175,6 +178,13 @@ TrailGuide::Experiment.configure do |config|
175
178
  # false default behavior, requests will be filtered based on your config
176
179
  config.skip_request_filter = false
177
180
 
181
+ # whether or not this experiment can be resumed after it's stopped - allows
182
+ # temporarily "pausing" an experiment then resuming it without losing metrics
183
+ #
184
+ # true this experiment can be paused and resumed
185
+ # false this experiment can only be stopped and reset/restarted
186
+ config.can_resume = false
187
+
178
188
  # set a default target sample size for all experiments - this will prevent
179
189
  # metrics and stats from being displayed in the admin UI until the sample size
180
190
  # is reached or the experiment is stopped
@@ -184,7 +194,7 @@ TrailGuide::Experiment.configure do |config|
184
194
  # callback when connecting to redis fails and trailguide falls back to always
185
195
  # returning control variants
186
196
  config.on_redis_failover = -> (experiment, error) do
187
- Rails.logger.error("#{error.class.name}: #{error.message}")
197
+ TrailGuide.logger.error error
188
198
  end
189
199
 
190
200
  # callback on experiment start, either manually via UI/console or
@@ -208,6 +218,16 @@ TrailGuide::Experiment.configure do |config|
208
218
  #
209
219
  # config.on_stop = -> (experiment, context) { ... }
210
220
 
221
+ # callback on experiment pause manually via UI/console, can be used for
222
+ # logging, tracking, etc.
223
+ #
224
+ # context may or may not be present depending on how you're triggering the
225
+ # action - if you're using the admin, this will be the admin controller
226
+ # context, if you're in a console you have the option to pass a context to
227
+ # `experiment.pause!` or not
228
+ #
229
+ # config.on_pause = -> (experiment, context) { ... }
230
+
211
231
  # callback on experiment resume manually via UI/console, can be used for
212
232
  # logging, tracking, etc.
213
233
  #
@@ -218,8 +238,18 @@ TrailGuide::Experiment.configure do |config|
218
238
  #
219
239
  # config.on_resume = -> (experiment, context) { ... }
220
240
 
241
+ # callback on experiment delete manually via UI/console, can be used for
242
+ # logging, tracking, etc. - will also be triggered by a reset
243
+ #
244
+ # context may or may not be present depending on how you're triggering the
245
+ # action - if you're using the admin, this will be the admin controller
246
+ # context, if you're in a console you have the option to pass a context to
247
+ # `experiment.delete!` or not
248
+ #
249
+ # config.on_delete = -> (experiment, context) { ... }
250
+
221
251
  # callback on experiment reset manually via UI/console, can be used for
222
- # logging, tracking, etc.
252
+ # logging, tracking, etc. - will also trigger any on_delete callbacks
223
253
  #
224
254
  # context may or may not be present depending on how you're triggering the
225
255
  # action - if you're using the admin, this will be the admin controller
@@ -0,0 +1,22 @@
1
+ TrailGuide::Admin::Engine.routes.draw do
2
+ resources :experiments, path: '/', only: [:index] do
3
+ member do
4
+ match :start, via: [:put, :post, :get]
5
+ match :pause, via: [:put, :post, :get]
6
+ match :stop, via: [:put, :post, :get]
7
+ match :reset, via: [:put, :post, :get]
8
+ match :resume, via: [:put, :post, :get]
9
+ match :restart, via: [:put, :post, :get]
10
+
11
+ match :join, via: [:put, :post, :get], path: 'join/:variant'
12
+ match :leave, via: [:put, :post, :get]
13
+
14
+ match :winner, via: [:put, :post, :get], path: 'winner/:variant'
15
+ match :clear, via: [:put, :post, :get]
16
+ end
17
+
18
+ collection do
19
+ get '/:scope', action: :index, as: :scoped
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ TrailGuide::Engine.routes.draw do
2
+ get '/' => 'experiments#index',
3
+ defaults: { format: :json }
4
+ match '/:experiment_name' => 'experiments#choose',
5
+ defaults: { format: :json },
6
+ via: [:get, :post]
7
+ match '/:experiment_name' => 'experiments#convert',
8
+ defaults: { format: :json },
9
+ via: [:put]
10
+ match '/:experiment_name/:checkpoint' => 'experiments#convert',
11
+ defaults: { format: :json },
12
+ via: [:put]
13
+ end
data/config/routes.rb CHANGED
@@ -1,31 +1,5 @@
1
- TrailGuide::Engine.routes.draw do
2
- get '/' => 'experiments#index',
3
- defaults: { format: :json }
4
- match '/:experiment_name' => 'experiments#choose',
5
- defaults: { format: :json },
6
- via: [:get, :post]
7
- match '/:experiment_name' => 'experiments#convert',
8
- defaults: { format: :json },
9
- via: [:put]
10
- match '/:experiment_name/:checkpoint' => 'experiments#convert',
11
- defaults: { format: :json },
12
- via: [:put]
13
- end
14
-
15
- TrailGuide::Admin::Engine.routes.draw do
16
- resources :experiments, path: '/', only: [:index] do
17
- member do
18
- match :start, via: [:put, :post, :get]
19
- match :stop, via: [:put, :post, :get]
20
- match :reset, via: [:put, :post, :get]
21
- match :resume, via: [:put, :post, :get]
22
- match :restart, via: [:put, :post, :get]
23
-
24
- match :join, via: [:put, :post, :get], path: 'join/:variant'
25
- match :leave, via: [:put, :post, :get]
26
-
27
- match :winner, via: [:put, :post, :get], path: 'winner/:variant'
28
- match :clear, via: [:put, :post, :get]
29
- end
30
- end
31
- end
1
+ # this not only organizes the routes, but also ensures they're only included
2
+ # once (via ruby require behavior), because having two separate engines in the
3
+ # gem can cause routes to be parsed twice and double up
4
+ require_relative 'routes/engine'
5
+ require_relative 'routes/admin'
@@ -6,7 +6,8 @@ module TrailGuide
6
6
  def initialize(&block)
7
7
  configure do |config|
8
8
  config.adapter = -> (context) do
9
- if context.respond_to?(:current_user, true) && context.send(:current_user).present?
9
+ if (context.respond_to?(:trailguide_user, true) && context.send(:trailguide_user).present?) ||
10
+ (context.respond_to?(:current_user, true) && context.send(:current_user).present?)
10
11
  TrailGuide::Adapters::Participants::Redis
11
12
  elsif context.respond_to?(:cookies, true)
12
13
  TrailGuide::Adapters::Participants::Cookie
@@ -6,7 +6,10 @@ module TrailGuide
6
6
  def initialize(&block)
7
7
  configure do |config|
8
8
  config.namespace = :participants
9
- config.lookup = -> (context) { context.current_user.id }
9
+ config.lookup = -> (context) {
10
+ context.try(:trailguide_user).try(:id) ||
11
+ context.try(:current_user).try(:id)
12
+ }
10
13
  config.expiration = nil
11
14
 
12
15
  yield(config) if block_given?
@@ -99,10 +99,22 @@ module TrailGuide
99
99
  self.class.new(to_a.select(&:running?))
100
100
  end
101
101
 
102
+ def paused
103
+ self.class.new(to_a.select(&:paused?))
104
+ end
105
+
102
106
  def stopped
103
107
  self.class.new(to_a.select(&:stopped?))
104
108
  end
105
109
 
110
+ def ended
111
+ self.class.new(to_a.select(&:winner?))
112
+ end
113
+
114
+ def not_running
115
+ self.class.new(to_a.select { |e| !e.running? })
116
+ end
117
+
106
118
  def by_started
107
119
  scoped = to_a.sort do |a,b|
108
120
  if a.running? && !b.running?
@@ -19,6 +19,10 @@ module TrailGuide
19
19
  parent.start!
20
20
  end
21
21
 
22
+ def pause!
23
+ parent.pause!
24
+ end
25
+
22
26
  def stop!
23
27
  parent.stop!
24
28
  end
@@ -31,6 +35,10 @@ module TrailGuide
31
35
  parent.started_at
32
36
  end
33
37
 
38
+ def paused_at
39
+ parent.paused_at
40
+ end
41
+
34
42
  def stopped_at
35
43
  parent.stopped_at
36
44
  end
@@ -41,7 +49,7 @@ module TrailGuide
41
49
 
42
50
  # use the parent experiment as the algorithm and map to the matching variant
43
51
  def algorithm_choose!(metadata: nil)
44
- variant = parent.new(participant).choose!(metadata: metadata)
52
+ variant = parent.new(participant.participant).choose!(metadata: metadata)
45
53
  variants.find { |var| var == variant.name }
46
54
  end
47
55
  end
@@ -1,10 +1,10 @@
1
1
  module TrailGuide
2
2
  class Config < Canfig::Config
3
3
  DEFAULT_KEYS = [
4
- :redis, :disabled, :override_parameter, :allow_multiple_experiments,
5
- :adapter, :on_adapter_failover, :filtered_ip_addresses,
6
- :filtered_user_agents, :request_filter, :include_helpers,
7
- :cleanup_participant_experiments, :unity_ttl
4
+ :logger, :redis, :disabled, :override_parameter,
5
+ :allow_multiple_experiments, :adapter, :on_adapter_failover,
6
+ :filtered_ip_addresses, :filtered_user_agents, :request_filter,
7
+ :include_helpers, :cleanup_participant_experiments, :unity_ttl
8
8
  ].freeze
9
9
 
10
10
  def initialize(*args, **opts, &block)
@@ -52,18 +52,25 @@ module TrailGuide
52
52
  started
53
53
  end
54
54
 
55
+ def pause!(context=nil)
56
+ return false unless running? && configuration.can_resume?
57
+ paused = TrailGuide.redis.hset(storage_key, 'paused_at', Time.now.to_i)
58
+ run_callbacks(:on_pause, context)
59
+ paused
60
+ end
61
+
55
62
  def stop!(context=nil)
56
- return false unless running?
63
+ return false unless started? && !stopped?
57
64
  stopped = TrailGuide.redis.hset(storage_key, 'stopped_at', Time.now.to_i)
58
65
  run_callbacks(:on_stop, context)
59
66
  stopped
60
67
  end
61
68
 
62
69
  def resume!(context=nil)
63
- return false unless started? && stopped?
64
- restarted = TrailGuide.redis.hdel(storage_key, 'stopped_at')
70
+ return false unless paused? && configuration.can_resume?
71
+ resumed = TrailGuide.redis.hdel(storage_key, 'paused_at')
65
72
  run_callbacks(:on_resume, context)
66
- restarted
73
+ resumed
67
74
  end
68
75
 
69
76
  def started_at
@@ -71,6 +78,11 @@ module TrailGuide
71
78
  return Time.at(started.to_i) if started
72
79
  end
73
80
 
81
+ def paused_at
82
+ paused = TrailGuide.redis.hget(storage_key, 'paused_at')
83
+ return Time.at(paused.to_i) if paused
84
+ end
85
+
74
86
  def stopped_at
75
87
  stopped = TrailGuide.redis.hget(storage_key, 'stopped_at')
76
88
  return Time.at(stopped.to_i) if stopped
@@ -80,12 +92,16 @@ module TrailGuide
80
92
  started_at && started_at <= Time.now
81
93
  end
82
94
 
95
+ def paused?
96
+ paused_at && paused_at <= Time.now
97
+ end
98
+
83
99
  def stopped?
84
100
  stopped_at && stopped_at <= Time.now
85
101
  end
86
102
 
87
103
  def running?
88
- started? && !stopped?
104
+ started? && !paused? && !stopped?
89
105
  end
90
106
 
91
107
  def declare_winner!(variant, context=nil)
@@ -5,13 +5,13 @@ 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, :target_sample_size
8
+ :track_winner_conversions, :skip_request_filter, :target_sample_size,
9
+ :can_resume
9
10
  ].freeze
10
11
 
11
12
  CALLBACK_KEYS = [
12
- :on_start, :on_stop, :on_resume, :on_winner, :on_reset, :on_delete,
13
- :on_choose, :on_use, :on_convert,
14
- :on_redis_failover,
13
+ :on_start, :on_stop, :on_pause, :on_resume, :on_winner, :on_reset,
14
+ :on_delete, :on_choose, :on_use, :on_convert, :on_redis_failover,
15
15
  :allow_participation, :allow_conversion, :rollout_winner
16
16
  ].freeze
17
17
 
@@ -73,6 +73,10 @@ module TrailGuide
73
73
  !!skip_request_filter
74
74
  end
75
75
 
76
+ def can_resume?
77
+ !!can_resume
78
+ end
79
+
76
80
  def name
77
81
  @name ||= (self[:name] || experiment.name).try(:to_s).try(:underscore).try(:to_sym)
78
82
  end
@@ -160,6 +164,11 @@ module TrailGuide
160
164
  self[:on_stop] << (meth || block)
161
165
  end
162
166
 
167
+ def on_pause(meth=nil, &block)
168
+ self[:on_pause] ||= []
169
+ self[:on_pause] << (meth || block)
170
+ end
171
+
163
172
  def on_resume(meth=nil, &block)
164
173
  self[:on_resume] ||= []
165
174
  self[:on_resume] << (meth || block)