trailguide 0.1.31 → 0.2.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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +73 -8
  3. data/app/assets/javascripts/trail_guide/admin/application.js +20 -1
  4. data/app/assets/stylesheets/trail_guide/admin/application.css +22 -0
  5. data/app/assets/stylesheets/trail_guide/admin/experiments.css +36 -3
  6. data/app/controllers/trail_guide/admin/application_controller.rb +59 -8
  7. data/app/controllers/trail_guide/admin/experiments_controller.rb +209 -16
  8. data/app/controllers/trail_guide/admin/groups_controller.rb +34 -0
  9. data/app/controllers/trail_guide/experiments_controller.rb +1 -1
  10. data/app/views/layouts/trail_guide/admin/_calculator.erb +24 -0
  11. data/app/views/layouts/trail_guide/admin/_footer.html.erb +27 -0
  12. data/app/views/layouts/trail_guide/admin/_header.html.erb +147 -0
  13. data/app/views/layouts/trail_guide/admin/_import_modal.html.erb +45 -0
  14. data/app/views/layouts/trail_guide/admin/application.html.erb +17 -3
  15. data/app/views/trail_guide/admin/experiments/_alert_peek.html.erb +19 -0
  16. data/app/views/trail_guide/admin/experiments/_alert_state.html.erb +49 -0
  17. data/app/views/trail_guide/admin/experiments/_btn_analyze.html.erb +11 -0
  18. data/app/views/trail_guide/admin/experiments/_btn_analyze_goal.html.erb +5 -0
  19. data/app/views/trail_guide/admin/experiments/_btn_convert.html.erb +33 -0
  20. data/app/views/trail_guide/admin/experiments/_btn_enroll.html.erb +3 -0
  21. data/app/views/trail_guide/admin/experiments/_btn_join.html.erb +11 -0
  22. data/app/views/trail_guide/admin/experiments/_btn_leave.html.erb +7 -0
  23. data/app/views/trail_guide/admin/experiments/_btn_pause.html.erb +5 -0
  24. data/app/views/trail_guide/admin/experiments/_btn_peek.html.erb +13 -0
  25. data/app/views/trail_guide/admin/experiments/_btn_reset.html.erb +5 -0
  26. data/app/views/trail_guide/admin/experiments/_btn_restart.html.erb +5 -0
  27. data/app/views/trail_guide/admin/experiments/_btn_resume.html.erb +5 -0
  28. data/app/views/trail_guide/admin/experiments/_btn_schedule.html.erb +6 -0
  29. data/app/views/trail_guide/admin/experiments/_btn_start.html.erb +5 -0
  30. data/app/views/trail_guide/admin/experiments/_btn_stop.html.erb +5 -0
  31. data/app/views/trail_guide/admin/experiments/_experiment.html.erb +68 -172
  32. data/app/views/trail_guide/admin/experiments/_header.html.erb +87 -0
  33. data/app/views/trail_guide/admin/experiments/_start_modal.html.erb +57 -0
  34. data/app/views/trail_guide/admin/experiments/_tbody.html.erb +112 -0
  35. data/app/views/trail_guide/admin/experiments/_thead.html.erb +38 -0
  36. data/app/views/trail_guide/admin/experiments/index.html.erb +8 -16
  37. data/app/views/trail_guide/admin/experiments/show.html.erb +78 -0
  38. data/app/views/trail_guide/admin/groups/index.html.erb +40 -0
  39. data/app/views/trail_guide/admin/groups/show.html.erb +9 -0
  40. data/app/views/trail_guide/admin/orphans/_alert.html.erb +44 -0
  41. data/config/initializers/trailguide.rb +72 -9
  42. data/config/routes/admin.rb +19 -12
  43. data/lib/trail_guide/admin/engine.rb +2 -0
  44. data/lib/trail_guide/admin.rb +2 -0
  45. data/lib/trail_guide/calculators/bayesian.rb +98 -0
  46. data/lib/trail_guide/calculators/calculator.rb +58 -0
  47. data/lib/trail_guide/calculators/score.rb +68 -0
  48. data/lib/trail_guide/calculators.rb +8 -0
  49. data/lib/trail_guide/catalog.rb +134 -19
  50. data/lib/trail_guide/combined_experiment.rb +8 -3
  51. data/lib/trail_guide/config.rb +6 -1
  52. data/lib/trail_guide/engine.rb +2 -0
  53. data/lib/trail_guide/errors.rb +30 -1
  54. data/lib/trail_guide/experiment.rb +0 -1
  55. data/lib/trail_guide/experiments/base.rb +189 -53
  56. data/lib/trail_guide/experiments/config.rb +82 -13
  57. data/lib/trail_guide/experiments/participant.rb +21 -1
  58. data/lib/trail_guide/helper.rb +59 -32
  59. data/lib/trail_guide/metrics/checkpoint.rb +24 -0
  60. data/lib/trail_guide/metrics/config.rb +45 -0
  61. data/lib/trail_guide/metrics/funnel.rb +24 -0
  62. data/lib/trail_guide/metrics/goal.rb +89 -0
  63. data/lib/trail_guide/metrics.rb +9 -0
  64. data/lib/trail_guide/participant.rb +54 -21
  65. data/lib/trail_guide/variant.rb +34 -12
  66. data/lib/trail_guide/version.rb +2 -2
  67. data/lib/trailguide.rb +4 -0
  68. metadata +112 -7
  69. data/app/views/layouts/trail_guide/admin/_footer.erb +0 -10
  70. data/app/views/layouts/trail_guide/admin/_header.erb +0 -42
  71. data/app/views/trail_guide/admin/experiments/_combined_experiment.html.erb +0 -189
  72. data/config/routes.rb +0 -5
@@ -1,182 +1,78 @@
1
- <div class="experiment row justify-content-center">
2
- <div class="col-sm-12 col-md-10 col-lg-8">
3
- <div class="row">
4
- <div class="col-sm-12 col-md-6 col-lg-8">
5
- <h4 id="<%= experiment.experiment_name %>">
6
- <%= link_to experiment.experiment_name.to_s.humanize.titleize, trail_guide_admin.experiments_path(anchor: experiment.experiment_name), class: "text-dark" %>
7
- </h4>
8
- </div>
9
- <div class="col-sm-12 col-md-6 col-lg-4 text-right">
10
- <% if experiment.running? %>
11
- <% if experiment_peekable?(experiment) %>
12
- <% if experiment_peeking?(experiment) %>
13
- <%= link_to trail_guide_admin.experiments_path(anchor: experiment.experiment_name), class: 'btn btn-sm btn-outline-primary', method: :put, data: {toggle: :tooltip}, title: "hide this experiment's metrics" do %>
14
- <span class="fas fa-eye-slash" />
15
- <% end %>
16
- <% else %>
17
- <%= link_to peek_url(experiment), class: 'btn btn-sm btn-primary', method: :put, data: {toggle: :tooltip}, title: "peek at this experiment's metrics" do %>
18
- <span class="fas fa-eye" />
19
- <% end %>
20
- <% end %>
21
- <% end %>
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" />
31
- <% end %>
32
-
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 %>
34
- <span class="fas fa-redo" />
35
- <% end %>
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 %>
46
- <span class="fas fa-redo" />
47
- <% end %>
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? %>
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 %>
54
- <span class="fas fa-play" />
55
- <% end %>
56
- <% end %>
57
- <%= link_to trail_guide_admin.reset_experiment_path(experiment.experiment_name), class: 'btn btn-sm btn-outline-danger',method: :put, data: {toggle: :tooltip}, title: "stop this experiment if it's running and reset all data" do %>
58
- <span class="fas fa-ban" />
59
- <% end %>
60
- </div>
61
- </div>
62
-
63
- <div class="row">
64
- <div class="col-sm-12 col-md-6 col-lg-8">
65
- <p><%= experiment.configuration.summary %></p>
66
- </div>
67
- <div class="col-sm-12 col-md-6 col-lg-4 text-right">
68
- <% if experiment.started? %>
69
- <small class="text-muted"><%= experiment.started_at.strftime('%b %e %Y @ %l:%M %p') %></small>
70
- <span class="text-muted">&mdash;</span>
71
- <% if experiment.stopped? %>
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>
75
- <% else %>
76
- <small class="text-muted">running</small>
77
- <% end %>
78
- <% else %>
79
- <small class="text-muted">not running</small>
80
- <% end %>
81
- </div>
1
+ <button href="<%= trail_guide_admin.experiment_path(experiment.experiment_name) %>" class="experiment btn btn-outline-<%= experiment_color(experiment) %> rounded-lg text-left">
2
+ <div class="row align-items-center">
3
+ <div class="col-11 col-md-5 col-lg-5 experiment-text">
4
+ <h4 id="<%= experiment.experiment_name %>">
5
+ <%= experiment.experiment_name.to_s.humanize.titleize %>
6
+ </h4>
7
+ <p style="margin: 0; padding: 0;"><%= experiment.configuration.summary %></p>
82
8
  </div>
9
+ <div class="col-10 col-md-6 col-lg-6 text-center text-md-right">
10
+ <div class="row align-items-center">
11
+ <div class="col-4 experiment-text text-center">
12
+ <h5>
13
+ <span class="fas fa-vials" style="width: 25px;"></span>
14
+ <span class="text-muted" style="display: inline-block; width: 25px; text-align: center;">&times;</span>
15
+ <span><%= number_with_delimiter experiment.variants.count %></span>
16
+ </h5>
17
+ <h5>
18
+ <span class="fas fa-fill" style="width: 25px;"></span>
19
+ <span class="text-muted" style="display: inline-block; width: 25px; text-align: center;">&times;</span>
20
+ <span><%= number_with_delimiter experiment.goals.count %></span>
21
+ </h5>
22
+ </div>
83
23
 
84
- <table class="table table-hover">
85
- <thead class="thead-light">
86
- <tr>
87
- <th scope="col">&nbsp;</th>
88
-
89
- <th scope="col">Participants</th>
90
-
91
- <% if experiment.goals.empty? %>
92
- <th scope="col">Converted</th>
93
- <% else %>
94
- <% experiment.goals.each do |goal| %>
95
- <th scope="col"><%= goal.to_s.humanize.titleize %></th>
96
- <% end %>
97
- <% end %>
98
-
99
- <th>&nbsp;</th>
100
- </tr>
101
- </thead>
24
+ <div class="col-4 experiment-text text-center">
25
+ <h5>
26
+ <span class="fas fa-users"></span>
27
+ </h5>
28
+ <h5>
29
+ <span><%= number_with_delimiter experiment.participants %></span>
30
+ </h5>
31
+ </div>
102
32
 
103
- <tbody>
104
- <% experiment.variants.each do |variant| %>
105
- <tr>
106
- <th scope="row">
107
- <% if experiment.configuration.preview_url? %>
108
- <%= link_to variant.name.to_s.humanize.titleize, preview_url(variant), target: :blank, class: "text-dark" %>
33
+ <div class="col-4 experiment-text text-center">
34
+ <h5>
35
+ <span class="fas fa-fill-drip"></span>
36
+ </h5>
37
+ <h5>
38
+ <%= experiment_metric experiment do %>
39
+ <% if experiment.combined? %>
40
+ <% if experiment.goals.empty? %>
41
+ <%= number_with_delimiter experiment.combined_experiments.sum(&:converted) %>
42
+ <% else %>
43
+ <%= number_with_delimiter experiment.goals.sum { |goal| experiment.combined_experiments.sum { |e| e.converted(goal) } } %>
44
+ <% end %>
109
45
  <% else %>
110
- <%= variant.name.to_s.humanize.titleize %>
111
- <% end %>
112
-
113
- <% if experiment.running? && !experiment.winner? && participant.variant(experiment) == variant %>
114
- <span class="fas fa-user text-muted" data-toggle="tooltip" title="you are currently in this cohort"></span>
115
- <% end %>
116
- <% if experiment.winner? && variant == experiment.winner %>
117
- <span class="fas fa-star text-primary" data-toggle="tooltip" title="this variant has been promoted as the winner of the experiment"></span>
118
- <% end %>
119
- <% if variant.control? %>
120
- <span class="fas fa-cog text-muted" data-toggle="tooltip" title="this variant is the control group for this experiment"></span>
121
- <% end %>
122
- </th>
123
-
124
- <td><%= experiment_metric experiment, variant.participants %></td>
125
-
126
- <% if experiment.goals.empty? %>
127
- <td><%= experiment_metric experiment, variant.converted %></td>
128
- <% else %>
129
- <% experiment.goals.each do |goal| %>
130
- <td><%= experiment_metric experiment, variant.converted(goal) %></td>
131
- <% end %>
132
- <% end %>
133
-
134
- <td class="text-right">
135
- <% if experiment.running? && !experiment.winner? %>
136
- <% if participant.variant(experiment) == variant %>
137
- <%= link_to trail_guide_admin.leave_experiment_path(experiment.experiment_name), class: "btn btn-sm btn-outline-secondary", method: :put, data: {toggle: :tooltip}, title: 'leave this cohort' do %>
138
- <span class="fas fa-sign-out-alt" />
139
- <% end %>
46
+ <% if experiment.goals.empty? %>
47
+ <%= number_with_delimiter experiment.converted %>
140
48
  <% else %>
141
- <%= link_to trail_guide_admin.join_experiment_path(experiment.experiment_name, variant.name), class: "btn btn-sm btn-secondary", method: :put, data: {toggle: :tooltip}, title: 'join this cohort' do %>
142
- <span class="fas fa-sign-in-alt" />
143
- <% end %>
49
+ <%= number_with_delimiter experiment.goals.sum { |goal| experiment.converted(goal) } %>
144
50
  <% end %>
145
51
  <% end %>
52
+ <% end %>
53
+ </h5>
54
+ </div>
55
+ </div>
56
+ </div>
146
57
 
147
- <% if !experiment.winner? || variant != experiment.winner %>
148
- <%= link_to trail_guide_admin.winner_experiment_path(experiment.experiment_name, variant.name), class: "btn btn-sm btn-#{experiment.winner? ? "outline-" : ""}primary", method: :put, data: {toggle: :tooltip}, title: 'rollout this variant as the winner for this experiment' do %>
149
- <span class="fas fa-star" />
150
- <% end %>
151
- <% elsif experiment.winner? && variant == experiment.winner %>
152
- <%= link_to trail_guide_admin.clear_experiment_path(experiment.experiment_name), class: "btn btn-sm btn-warning", method: :put, data: {toggle: :tooltip}, title: 'remove this variant as the selected winner' do %>
153
- <span class="fas fa-window-close" />
154
- <% end %>
155
- <% end %>
156
- </td>
157
- </tr>
58
+ <div class="col-2 col-md-1">
59
+ <% if experiment.winner? %>
60
+ <span class="fas fa-flag-checkered fa-2x" data-toggle="tooltip" title="ended"></span>
61
+ <% elsif experiment.calibrating? %>
62
+ <span class="fas fa-cogs fa-2x" data-toggle="tooltip" title="calibrating"></span>
63
+ <% elsif experiment.started? %>
64
+ <% if experiment.stopped? %>
65
+ <span class="fas fa-stop fa-2x" data-toggle="tooltip" title="stopped"></span>
66
+ <% elsif experiment.paused? %>
67
+ <span class="fas fa-pause fa-2x" data-toggle="tooltip" title="paused"></span>
68
+ <% else %>
69
+ <span class="fas fa-play fa-2x" data-toggle="tooltip" title="running"></span>
158
70
  <% end %>
159
- </tbody>
160
-
161
- <tfoot class="thead-light">
162
- <tr>
163
- <th scope="row">&nbsp;</th>
164
- <th>
165
- <span><%= number_with_delimiter experiment.participants %></span>
166
- <% if experiment.configuration.target_sample_size %>
167
- <span class="text-muted">/ <%= number_with_delimiter experiment.configuration.target_sample_size %></span>
168
- <% end %>
169
- </th>
170
- <% if experiment.goals.empty? %>
171
- <th><%= experiment_metric experiment, experiment.converted %></th>
172
- <% else %>
173
- <% experiment.goals.each do |goal| %>
174
- <th><%= experiment_metric experiment, experiment.converted(goal) %></th>
175
- <% end %>
176
- <% end %>
177
- <th>&nbsp;</th>
178
- </tr>
179
- </tfoot>
180
- </table>
71
+ <% elsif experiment.scheduled? %>
72
+ <span class="fas fa-clock fa-2x" data-toggle="tooltip" title="scheduled"></span>
73
+ <% else %>
74
+ <span class="fas fa-flask fa-2x" data-toggle="tooltip" title="not started"></span>
75
+ <% end %>
76
+ </div>
181
77
  </div>
182
- </div>
78
+ </button>
@@ -0,0 +1,87 @@
1
+ <div class="row align-items-center">
2
+ <div class="col-8 col-lg-10">
3
+ <h1 id="<%= experiment.experiment_name %>">
4
+ <%= experiment.experiment_name.to_s.humanize.titleize %>
5
+ </h1>
6
+ </div>
7
+
8
+ <div class="col-4 col-lg-2 text-right">
9
+ <% unless experiment.is_combined? %>
10
+ <div class="btn-group">
11
+ <% if experiment.combined? %>
12
+ <%= render partial: 'btn_leave', locals: { experiment: experiment } %>
13
+ <%= render partial: 'btn_enroll', locals: { experiment: experiment } %>
14
+ <%= render partial: 'btn_peek', locals: { experiment: experiment } %>
15
+ <% end %>
16
+ <%= render partial: 'btn_analyze', locals: { experiment: experiment } %>
17
+ <%= render partial: 'btn_schedule', locals: { experiment: experiment } %>
18
+ <%= render partial: 'btn_start', locals: { experiment: experiment } %>
19
+ <%= render partial: 'btn_pause', locals: { experiment: experiment } %>
20
+ <%= render partial: 'btn_resume', locals: { experiment: experiment } %>
21
+ <%= render partial: 'btn_stop', locals: { experiment: experiment } %>
22
+ <%= render partial: 'btn_restart', locals: { experiment: experiment } %>
23
+ <%= render partial: 'btn_reset', locals: { experiment: experiment } %>
24
+ </div>
25
+ <% end %>
26
+ </div>
27
+ </div>
28
+
29
+ <div class="row">
30
+ <div class="col-6">
31
+ <% if experiment.groups.count > 0 %>
32
+ <p class="text-muted" style="padding: 0; margin: 0;">
33
+ <span class="fas fa-th-large" data-toggle="tooltip" title="experiment groups"></span>
34
+ <% experiment.groups.each do |group| %>
35
+ <%= link_to group.to_s.humanize.titleize, trail_guide_admin.group_path(group), class: "text-muted" %>
36
+ <% end %>
37
+ </p>
38
+ <% end %>
39
+ </div>
40
+
41
+ <div class="col-6 text-right">
42
+ <p class="text-muted" style="padding: 0; margin: 0;">
43
+ <%= experiment.algorithm.name.demodulize.underscore.humanize.titleize %> Algorithm
44
+ <span class="fas fa-dice" data-toggle="tooltip" title="configured algorithm"></span>
45
+ </p>
46
+ </div>
47
+ </div>
48
+
49
+ <div class="row">
50
+ <div class="col-12">
51
+ <p><%= experiment.configuration.summary %></p>
52
+ </div>
53
+ </div>
54
+
55
+ <div class="row">
56
+ <div class="col-12">
57
+ <%= render partial: 'alert_state', locals: {experiment: experiment} %>
58
+
59
+ <% if experiment.calibrating? %>
60
+ <div class="alert alert-dark">
61
+ <h6>
62
+ <span class="fas fa-cogs"></span>
63
+ Calibrating
64
+ </h6>
65
+ <p><small>This experiment is configured with <code>enable_calibration: true</code> and will track participation and conversion against your control variant until it is started to help you determine a baseline conversion rate.</small></p>
66
+ </div>
67
+ <% end %>
68
+
69
+ <% if experiment.is_combined? %>
70
+ <div class="alert alert-dark">
71
+ <h6>
72
+ <span class="fas fa-link"></span>
73
+ Combined Experiment
74
+ </h6>
75
+ <p><small>This experiment is part of a combined experiment, and some features must be managed via it's parent experiment <strong><%= link_to experiment.parent.experiment_name.to_s.humanize.titleize, trail_guide_admin.experiment_path(experiment.parent.experiment_name), class: 'text-dark' %></strong></small></p>
76
+ </div>
77
+ <% elsif experiment.combined? %>
78
+ <div class="alert alert-dark">
79
+ <h6>
80
+ <span class="fas fa-link"></span>
81
+ Combined Experiment
82
+ </h6>
83
+ <p><small>This experiment is configured as a combined experiment, which means it defines multiple individual experiments. These experiments share a <em>lifecycle and enrollment</em> via the parent, but track <em>independent participation, conversion and winner selection</em>. This means that when you're enrolled in one experiment you'll also be enrolled in the parent, and any sibling experiments you encounter thereafter will always select that same variant.</small></p>
84
+ </div>
85
+ <% end %>
86
+ </div>
87
+ </div>
@@ -0,0 +1,57 @@
1
+ <div id="start-experiment-modal-<%= experiment.experiment_name %>" class="modal start-experiment-modal" tabindex="-1" role="dialog">
2
+ <div class="modal-dialog" role="document">
3
+ <div class="modal-content">
4
+ <%= form_tag trail_guide_admin.schedule_experiment_path(experiment.experiment_name) do %>
5
+ <div class="modal-header">
6
+ <h5 class="modal-title"><%= experiment.experiment_name.to_s.humanize.titleize %></h5>
7
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
8
+ <span aria-hidden="true">&times;</span>
9
+ </button>
10
+ </div>
11
+ <div class="modal-body text-left">
12
+ <p class="text-muted">Schedule your experiment to start at a future date and time or click <span class="text-success"><span class="fas fa-play"></span> Start Now</span> to kick it off immediately.</p>
13
+
14
+ <div class="form-group row">
15
+ <label for="start-experiment-at-<%= experiment.experiment_name %>" class="col-sm-3 col-form-label"><strong>Start</strong></label>
16
+ <div class="col-sm-9">
17
+ <div class="input-group date datepicker" id="start-experiment-at-<%= experiment.experiment_name %>" data-target-input="nearest">
18
+ <input type="text" name="experiment[start_at]" value="<%= (experiment.started_at ? Time.at(experiment.started_at).strftime(TrailGuide::SCHEDULE_DATE_FORMAT) : nil) %>" class="form-control datetimepicker-input" data-target="#start-experiment-at-<%= experiment.experiment_name %>"/>
19
+ <div class="input-group-append" data-target="#start-experiment-at-<%= experiment.experiment_name %>" data-toggle="datetimepicker">
20
+ <div class="input-group-text"><i class="fa fa-calendar"></i></div>
21
+ </div>
22
+ </div>
23
+ </div>
24
+ </div>
25
+
26
+ <div class="form-group row">
27
+ <label for="stop-experiment-at-<%= experiment.experiment_name %>" class="col-sm-3 col-form-label"><strong>Stop</strong></label>
28
+ <div class="col-sm-9">
29
+ <div class="input-group date datepicker" id="stop-experiment-at-<%= experiment.experiment_name %>" data-target-input="nearest">
30
+ <input type="text" name="experiment[stop_at]" value="<%= (experiment.stopped_at ? Time.at(experiment.stopped_at).strftime(TrailGuide::SCHEDULE_DATE_FORMAT) : nil) %>" class="form-control datetimepicker-input" data-target="#stop-experiment-at-<%= experiment.experiment_name %>"/>
31
+ <div class="input-group-append" data-target="#stop-experiment-at-<%= experiment.experiment_name %>" data-toggle="datetimepicker">
32
+ <div class="input-group-text"><i class="fa fa-calendar"></i></div>
33
+ </div>
34
+ </div>
35
+ <small class="text-muted">You may also optionally schedule your experiment to stop automatically at a date and time after it has started.</small>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ <div class="modal-footer">
40
+ <div class="col-sm-4">
41
+ <%= link_to trail_guide_admin.start_experiment_path(experiment.experiment_name), class: 'btn btn-success', method: :put, data: {toggle: :tooltip}, title: 'start this experiment immediately' do %>
42
+ <span class="fas fa-play"></span>
43
+ Start Now
44
+ <% end %>
45
+ </div>
46
+ <div class="col-sm-8">
47
+ <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
48
+ <button type="submit" class="btn btn-info">
49
+ <span class="fas fa-clock"></span>
50
+ Schedule Experiment
51
+ </button>
52
+ </div>
53
+ </div>
54
+ <% end %>
55
+ </div>
56
+ </div>
57
+ </div>
@@ -0,0 +1,112 @@
1
+ <tbody>
2
+ <% experiment.variants.sort { |a,b| b.measure(calculator.goal) <=> a.measure(calculator.goal) }.each do |variant| %>
3
+ <% vcalc = calculator.variants.find { |v| variant == v.name } %>
4
+ <tr>
5
+ <td class="text-left btn-col">
6
+ <%= render partial: 'btn_join', locals: { experiment: experiment, variant: variant } %>
7
+ </td>
8
+
9
+ <th scope="row" class="text-left">
10
+ <% if variant.control? %>
11
+ <span class="fas fa-cog text-muted" data-toggle="tooltip" title="this variant is the control group for this experiment"></span>
12
+ <% else %>
13
+ <span class="fas fa-vial text-muted" data-toggle="tooltip" title="this variant is an alternate group for this experiment"></span>
14
+ <% end %>
15
+
16
+ <% if experiment.configuration.preview_url? %>
17
+ <%= link_to preview_url(variant), target: :blank, class: "text-#{variant_analysis_color(vcalc, calculator)}", data: {toggle: :tooltip}, title: "preview this variant (without affecting metrics)" do %>
18
+ <span><%= variant.name.to_s.humanize.titleize %></span>
19
+ <small><span class="fas fa-external-link-alt"></span></small>
20
+ <% end %>
21
+ <% else %>
22
+ <span class="text-<%= variant_analysis_color(vcalc, calculator) %>"><%= variant.name.to_s.humanize.titleize %></span>
23
+ <% end %>
24
+
25
+ <% if (experiment.running? || experiment.calibrating?) && participant.variant(experiment) == variant %>
26
+ <span class="fas fa-user text-secondary" data-toggle="tooltip" title="you are currently in this cohort"></span>
27
+ <% end %>
28
+ <% if experiment.winner? && variant == experiment.winner %>
29
+ <span class="fas fa-flag-checkered text-primary" data-toggle="tooltip" title="this variant has been promoted as the winner of the experiment"></span>
30
+ <% end %>
31
+ </th>
32
+
33
+ <td>
34
+ <strong class="text-<%= variant_analysis_color(vcalc, calculator) %>"><%= experiment_metric experiment, variant.participants %></strong>
35
+ </td>
36
+
37
+ <% if experiment.goals.empty? %>
38
+ <td>
39
+ <%= experiment_metric experiment do %>
40
+ <% if @analyzing %>
41
+ <div class="row">
42
+ <div class="col-4 text-right">
43
+ <strong class="text-<%= variant_analysis_color(vcalc, calculator) %>"><%= number_with_delimiter variant.converted %></strong>
44
+ </div>
45
+ <div class="col-8 text-left">
46
+ <small class="text-light">&nbsp;</small>
47
+ <small class="text-<%= variant_analysis_color(vcalc, calculator) %>" data-toggle="tooltip" title="conversion rate vs all participants">
48
+ <span class="fas fa-<%= vcalc.difference == 0 ? "minus" : (vcalc.difference > 0 ? "long-arrow-alt-up" : "long-arrow-alt-down") %>"></span>
49
+ <%= number_with_delimiter (variant.measure * 100).round(1) %>%
50
+ </small>
51
+ <small class="text-light">&nbsp;</small>
52
+ <small class="text-<%= variant_analysis_color(vcalc, calculator) %>" data-toggle="tooltip" title="performance vs control">
53
+ <%= number_with_delimiter vcalc.difference.round(2) %>%
54
+ </small>
55
+ <small class="text-light">&nbsp;</small>
56
+ <small class="text-<%= variant_analysis_color(vcalc, calculator) %>" data-toggle="tooltip" title="probability of winning">
57
+ <%= number_with_delimiter vcalc.probability.round(2) %>%
58
+ </small>
59
+ </div>
60
+ </div>
61
+ <% else %>
62
+ <strong class="text-<%= variant_analysis_color(vcalc, calculator) %>"><%= number_with_delimiter variant.converted %></strong>
63
+ <% end %>
64
+ <% end %>
65
+ </td>
66
+ <% else %>
67
+ <% experiment.goals.each_with_index do |goal, g| %>
68
+ <td>
69
+ <%= experiment_metric experiment do %>
70
+ <% if @analyzing && goal == calculator.goal %>
71
+ <div class="row">
72
+ <div class="col-4 text-right">
73
+ <strong class="text-<%= variant_analysis_color(vcalc, calculator) %>"><%= number_with_delimiter variant.converted(goal) %></strong>
74
+ </div>
75
+ <div class="col-8 text-left">
76
+ <small class="text-light">&nbsp;</small>
77
+ <small class="text-<%= variant_analysis_color(vcalc, calculator) %>" data-toggle="tooltip" title="conversion rate vs all participants">
78
+ <span class="fas fa-<%= vcalc.difference == 0 ? "minus" : (vcalc.difference > 0 ? "long-arrow-alt-up" : "long-arrow-alt-down") %>"></span>
79
+ <%= number_with_delimiter (variant.measure(goal) * 100).round(1) %>%
80
+ </small>
81
+ <small class="text-light">&nbsp;</small>
82
+ <small class="text-<%= variant_analysis_color(vcalc, calculator) %>" data-toggle="tooltip" title="performance vs control">
83
+ <%= number_with_delimiter vcalc.difference.round(2) %>%
84
+ </small>
85
+ <small class="text-light">&nbsp;</small>
86
+ <small class="text-<%= variant_analysis_color(vcalc, calculator) %>" data-toggle="tooltip" title="probability of winning">
87
+ <%= number_with_delimiter vcalc.probability.round(2) %>%
88
+ </small>
89
+ </div>
90
+ </div>
91
+ <% else %>
92
+ <strong class="text-<%= variant_analysis_color(vcalc, calculator) %>"><%= number_with_delimiter variant.converted(goal) %></strong>
93
+ <% end %>
94
+ <% end %>
95
+ </td>
96
+ <% end %>
97
+ <% end %>
98
+
99
+ <td class="text-right btn-col">
100
+ <% if !experiment.winner? || variant != experiment.winner %>
101
+ <%= link_to trail_guide_admin.winner_experiment_path(experiment.experiment_name, variant.name), class: "btn btn-sm btn-#{experiment.winner? ? "outline-" : ""}primary", method: :put, data: {toggle: :tooltip}, title: 'rollout this variant as the winner for this experiment' do %>
102
+ <span class="fas fa-flag-checkered" />
103
+ <% end %>
104
+ <% elsif experiment.winner? && variant == experiment.winner %>
105
+ <%= link_to trail_guide_admin.clear_experiment_path(experiment.experiment_name), class: "btn btn-sm btn-outline-warning", method: :put, data: {toggle: :tooltip}, title: 'remove this variant as the selected winner' do %>
106
+ <span class="fas fa-minus-circle" />
107
+ <% end %>
108
+ <% end %>
109
+ </td>
110
+ </tr>
111
+ <% end %>
112
+ </tbody>
@@ -0,0 +1,38 @@
1
+ <thead class="thead-light">
2
+ <tr>
3
+ <th class="text-left btn-col">
4
+ <%= render partial: 'btn_enroll', locals: { experiment: experiment, classname: "btn-sm" } %>
5
+ </th>
6
+
7
+ <th scope="col" class="text-left">
8
+ <% if experiment <= TrailGuide::CombinedExperiment && (!@experiment || @experiment.combined?) %>
9
+ <h5 id="<%= experiment.experiment_name %>">
10
+ <%= link_to experiment.experiment_name.to_s.humanize.titleize, trail_guide_admin.experiment_path(experiment.experiment_name), class: "text-dark" %>
11
+ </h5>
12
+ <% else %>
13
+ &nbsp;
14
+ <% end %>
15
+ </th>
16
+
17
+ <th scope="col">Participants</th>
18
+
19
+ <% if experiment.goals.empty? %>
20
+ <th scope="col">
21
+ Converted
22
+ <%= render partial: 'btn_convert', locals: { experiment: experiment, goal: nil } %>
23
+ </th>
24
+ <% else %>
25
+ <% experiment.goals.each do |goal| %>
26
+ <th scope="col">
27
+ <%= render partial: 'btn_analyze_goal', locals: { experiment: experiment, goal: goal } %>
28
+ <%= goal.to_s.humanize.titleize %>
29
+ <%= render partial: 'btn_convert', locals: { experiment: experiment, goal: goal } %>
30
+ </th>
31
+ <% end %>
32
+ <% end %>
33
+
34
+ <th class="text-right btn-col">
35
+ <%= render partial: 'btn_peek', locals: { experiment: experiment, classname: "btn-sm" } %>
36
+ </th>
37
+ </tr>
38
+ </thead>
@@ -1,19 +1,11 @@
1
- <% if TrailGuide.configuration.disabled %>
2
- <div class="row justify-content-center">
3
- <div class="col-sm-12 col-md-10 col-lg-8">
4
- <div class="alert alert-danger">
5
- TrailGuide is globally disabled. Your experiments are not running, and control variants will be returned for all users until it is enabled again.
6
- </div>
1
+ <%= render partial: 'trail_guide/admin/orphans/alert' %>
2
+
3
+ <div class="row justify-content-center">
4
+ <div class="col-12 col-md-10 col-lg-8">
5
+ <div class="experiments">
6
+ <% @experiments.each do |experiment| %>
7
+ <%= render 'experiment', experiment: experiment %>
8
+ <% end %>
7
9
  </div>
8
10
  </div>
9
- <% end %>
10
-
11
- <div class="experiments">
12
- <% @experiments.each do |experiment| %>
13
- <% if experiment.combined? %>
14
- <%= render 'combined_experiment', combined_experiment: experiment %>
15
- <% else %>
16
- <%= render 'experiment', experiment: experiment %>
17
- <% end %>
18
- <% end %>
19
11
  </div>