trailguide 0.1.31 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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>