trailguide 0.1.13 → 0.1.14
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 +4 -4
- data/app/controllers/trail_guide/admin/application_controller.rb +1 -1
- data/app/controllers/trail_guide/admin/experiments_controller.rb +29 -6
- data/app/views/trail_guide/admin/experiments/_combined_experiment.html.erb +129 -0
- data/app/views/trail_guide/admin/experiments/_experiment.html.erb +35 -10
- data/app/views/trail_guide/admin/experiments/index.html.erb +6 -2
- data/config/routes.rb +5 -0
- data/lib/trail_guide/catalog.rb +42 -2
- data/lib/trail_guide/combined_experiment.rb +48 -0
- data/lib/trail_guide/engine.rb +1 -0
- data/lib/trail_guide/experiment.rb +4 -237
- data/lib/trail_guide/experiments/base.rb +268 -0
- data/lib/trail_guide/experiments/combined_config.rb +12 -0
- data/lib/trail_guide/experiments/config.rb +158 -0
- data/lib/trail_guide/helper.rb +1 -1
- data/lib/trail_guide/participant.rb +19 -4
- data/lib/trail_guide/version.rb +1 -1
- data/lib/trailguide.rb +5 -0
- metadata +7 -3
- data/lib/trail_guide/experiment_config.rb +0 -137
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: e92b5781f0ec93e65f9538268fd8fa00b5be79008ed83337de2b7091020de719
         | 
| 4 | 
            +
              data.tar.gz: 547d8afc88e203a7ce6d0995d0efaa0e582b643c80cc0ffdfc7b2743c31aa18b
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 96d7e94e72fb228b63cac5331d74b8ec5440ad234e4350b47191343b797c83ad50d35a0c9d87131dfc8157fa99355df7e36cff159ce05ab88e70ae5b6315e02d
         | 
| 7 | 
            +
              data.tar.gz: 1323352dda2d506d4153c9b70bffd90250077b4b9728fa732f878ca181670921758a4508754e5d38484ced8b8cd684eaa06d0489631b02a15f8261a3b7438efa
         | 
| @@ -10,32 +10,50 @@ module TrailGuide | |
| 10 10 |  | 
| 11 11 | 
             
                  def start
         | 
| 12 12 | 
             
                    experiment.start!
         | 
| 13 | 
            -
                    redirect_to : | 
| 13 | 
            +
                    redirect_to trail_guide_admin.experiments_path(anchor: experiment.experiment_name)
         | 
| 14 14 | 
             
                  end
         | 
| 15 15 |  | 
| 16 16 | 
             
                  def stop
         | 
| 17 17 | 
             
                    experiment.stop!
         | 
| 18 | 
            -
                    redirect_to : | 
| 18 | 
            +
                    redirect_to trail_guide_admin.experiments_path(anchor: experiment.experiment_name)
         | 
| 19 19 | 
             
                  end
         | 
| 20 20 |  | 
| 21 21 | 
             
                  def reset
         | 
| 22 22 | 
             
                    experiment.reset!
         | 
| 23 | 
            -
                    redirect_to : | 
| 23 | 
            +
                    redirect_to trail_guide_admin.experiments_path(anchor: experiment.experiment_name)
         | 
| 24 24 | 
             
                  end
         | 
| 25 25 |  | 
| 26 26 | 
             
                  def resume
         | 
| 27 27 | 
             
                    experiment.resume!
         | 
| 28 | 
            -
                    redirect_to : | 
| 28 | 
            +
                    redirect_to trail_guide_admin.experiments_path(anchor: experiment.experiment_name)
         | 
| 29 29 | 
             
                  end
         | 
| 30 30 |  | 
| 31 31 | 
             
                  def restart
         | 
| 32 32 | 
             
                    experiment.reset! && experiment.start!
         | 
| 33 | 
            -
                    redirect_to : | 
| 33 | 
            +
                    redirect_to trail_guide_admin.experiments_path(anchor: experiment.experiment_name)
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  def join
         | 
| 37 | 
            +
                    participant.exit!(experiment)
         | 
| 38 | 
            +
                    variant = experiment.variants.find { |var| var == params[:variant] }
         | 
| 39 | 
            +
                    variant.increment_participation!
         | 
| 40 | 
            +
                    participant.participating!(variant)
         | 
| 41 | 
            +
                    redirect_to trail_guide_admin.experiments_path(anchor: experiment.experiment_name)
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  def leave
         | 
| 45 | 
            +
                    participant.exit!(experiment)
         | 
| 46 | 
            +
                    redirect_to trail_guide_admin.experiments_path(anchor: experiment.experiment_name)
         | 
| 34 47 | 
             
                  end
         | 
| 35 48 |  | 
| 36 49 | 
             
                  def winner
         | 
| 37 50 | 
             
                    experiment.declare_winner!(params[:variant])
         | 
| 38 | 
            -
                    redirect_to : | 
| 51 | 
            +
                    redirect_to trail_guide_admin.experiments_path(anchor: experiment.experiment_name)
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  def clear
         | 
| 55 | 
            +
                    experiment.clear_winner!
         | 
| 56 | 
            +
                    redirect_to trail_guide_admin.experiments_path(anchor: experiment.experiment_name)
         | 
| 39 57 | 
             
                  end
         | 
| 40 58 |  | 
| 41 59 | 
             
                  private
         | 
| @@ -43,6 +61,11 @@ module TrailGuide | |
| 43 61 | 
             
                  def experiment
         | 
| 44 62 | 
             
                    @experiment ||= TrailGuide.catalog.find(params[:id])
         | 
| 45 63 | 
             
                  end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  def participant
         | 
| 66 | 
            +
                    @participant ||= TrailGuide::Participant.new(self)
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
                  helper_method :participant
         | 
| 46 69 | 
             
                end
         | 
| 47 70 | 
             
              end
         | 
| 48 71 | 
             
            end
         | 
| @@ -0,0 +1,129 @@ | |
| 1 | 
            +
            <div class="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 | 
            +
                    <h3 style="margin: 0; padding: 0;" id="<%= combined_experiment.experiment_name %>">
         | 
| 6 | 
            +
                      <%= link_to combined_experiment.experiment_name.to_s.humanize.titleize, trail_guide_admin.experiments_path(anchor: combined_experiment.experiment_name), class: "text-dark" %>
         | 
| 7 | 
            +
                    </h3>
         | 
| 8 | 
            +
                  </div>
         | 
| 9 | 
            +
                  <div class="col-sm-12 col-md-6 col-lg-4 text-right">
         | 
| 10 | 
            +
                    <% if combined_experiment.running? %>
         | 
| 11 | 
            +
                      <%= link_to "stop", trail_guide_admin.stop_experiment_path(combined_experiment.experiment_name), class: 'btn btn-sm btn-warning', method: :put %>
         | 
| 12 | 
            +
                      <%= link_to "restart", trail_guide_admin.restart_experiment_path(combined_experiment.experiment_name), class: 'btn btn-sm btn-danger',method: :put %>
         | 
| 13 | 
            +
                    <% elsif combined_experiment.started? %>
         | 
| 14 | 
            +
                      <%= link_to "resume", trail_guide_admin.resume_experiment_path(combined_experiment.experiment_name), class: 'btn btn-sm btn-primary',method: :put %>
         | 
| 15 | 
            +
                    <% else %>
         | 
| 16 | 
            +
                      <%= link_to "start", trail_guide_admin.start_experiment_path(combined_experiment.experiment_name), class: 'btn btn-sm btn-success',method: :put %>
         | 
| 17 | 
            +
                    <% end %>
         | 
| 18 | 
            +
                    <%= link_to "reset", trail_guide_admin.reset_experiment_path(combined_experiment.experiment_name), class: 'btn btn-sm btn-outline-danger',method: :put %>
         | 
| 19 | 
            +
                  </div>
         | 
| 20 | 
            +
                </div>
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                <div class="row">
         | 
| 23 | 
            +
                  <div class="col-sm-12 text-right">
         | 
| 24 | 
            +
                    <% if combined_experiment.started? %>
         | 
| 25 | 
            +
                      <small class="text-muted"><%= combined_experiment.started_at.strftime('%b %e %Y @ %l:%M %p') %></small>
         | 
| 26 | 
            +
                      <span class="text-muted">—</span>
         | 
| 27 | 
            +
                      <% if combined_experiment.stopped? %>
         | 
| 28 | 
            +
                        <small class="text-muted"><%= combined_experiment.stopped_at.strftime('%b %e %Y @ %l:%M %p') %></small>
         | 
| 29 | 
            +
                      <% else %>
         | 
| 30 | 
            +
                        <small class="text-muted">running</small>
         | 
| 31 | 
            +
                      <% end %>
         | 
| 32 | 
            +
                    <% else %>
         | 
| 33 | 
            +
                      <small class="text-muted">not running</small>
         | 
| 34 | 
            +
                    <% end %>
         | 
| 35 | 
            +
                  </div>
         | 
| 36 | 
            +
                </div>
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                <table class="table table-hover">
         | 
| 39 | 
            +
                  <% combined_experiments = combined_experiment.combined.map { |e| TrailGuide.catalog.find(e) } %>
         | 
| 40 | 
            +
                  <% combined_experiments.each do |experiment| %>
         | 
| 41 | 
            +
                    <thead class="thead-light">
         | 
| 42 | 
            +
                      <tr>
         | 
| 43 | 
            +
                        <th scope="col">
         | 
| 44 | 
            +
                          <h4 style="margin: 0; padding: 0" id="<%= experiment.experiment_name %>">
         | 
| 45 | 
            +
                            <%= link_to experiment.experiment_name.to_s.humanize.titleize, trail_guide_admin.experiments_path(anchor: experiment.experiment_name), class: "text-dark" %>
         | 
| 46 | 
            +
                          </h4>
         | 
| 47 | 
            +
                        </th>
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                        <th scope="col">Participants</th>
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                        <% if experiment.goals.empty? %>
         | 
| 52 | 
            +
                          <th scope="col">Converted</th>
         | 
| 53 | 
            +
                        <% else %>
         | 
| 54 | 
            +
                          <% experiment.goals.each do |goal| %>
         | 
| 55 | 
            +
                            <th scope="col"><%= goal.to_s.humanize.titleize %></th>
         | 
| 56 | 
            +
                          <% end %>
         | 
| 57 | 
            +
                        <% end %>
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                        <th> </th>
         | 
| 60 | 
            +
                      </tr>
         | 
| 61 | 
            +
                    </thead>
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                    <tbody>
         | 
| 64 | 
            +
                      <% experiment.variants.each do |variant| %>
         | 
| 65 | 
            +
                        <tr class="<%= "table-secondary" if variant.control? %>">
         | 
| 66 | 
            +
                          <th scope="row">
         | 
| 67 | 
            +
                            <%= variant.name.to_s.humanize.titleize %>
         | 
| 68 | 
            +
                            <% if experiment.running? && !experiment.winner? && participant.variant(experiment) == variant %>
         | 
| 69 | 
            +
                              <span class="badge badge-secondary">joined</span>
         | 
| 70 | 
            +
                            <% end %>
         | 
| 71 | 
            +
                            <% if experiment.winner? && variant == experiment.winner %>
         | 
| 72 | 
            +
                              <span class="badge badge-primary">winner</span>
         | 
| 73 | 
            +
                            <% end %>
         | 
| 74 | 
            +
                            <% if variant.control? %>
         | 
| 75 | 
            +
                              <small class="text-muted">control</small>
         | 
| 76 | 
            +
                            <% end %>
         | 
| 77 | 
            +
                          </th>
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                          <td><%= variant.participants %></td>
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                          <% if experiment.goals.empty? %>
         | 
| 82 | 
            +
                            <td><%= variant.converted %></td>
         | 
| 83 | 
            +
                          <% else %>
         | 
| 84 | 
            +
                            <% experiment.goals.each do |goal| %>
         | 
| 85 | 
            +
                              <td><%= variant.converted(goal) %></td>
         | 
| 86 | 
            +
                            <% end %>
         | 
| 87 | 
            +
                          <% end %>
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                          <td class="text-right">
         | 
| 90 | 
            +
                            <% if experiment.running? && !experiment.winner? %>
         | 
| 91 | 
            +
                              <% if participant.variant(experiment) == variant %>
         | 
| 92 | 
            +
                                <%= link_to "leave group", trail_guide_admin.leave_experiment_path(experiment.experiment_name), class: "btn btn-sm btn-outline-secondary", method: :put %>
         | 
| 93 | 
            +
                              <% else %>
         | 
| 94 | 
            +
                                <%= link_to "enter group", trail_guide_admin.join_experiment_path(experiment.experiment_name, variant.name), class: "btn btn-sm btn-secondary", method: :put %>
         | 
| 95 | 
            +
                              <% end %>
         | 
| 96 | 
            +
                            <% end %>
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                            <% if !experiment.winner? || variant != experiment.winner %>
         | 
| 99 | 
            +
                              <%= link_to "select winner", trail_guide_admin.winner_experiment_path(experiment.experiment_name, variant.name), class: "btn btn-sm btn-#{experiment.winner? ? "outline-" : ""}primary", method: :put %>
         | 
| 100 | 
            +
                            <% elsif experiment.winner? && variant == experiment.winner %>
         | 
| 101 | 
            +
                              <%= link_to "remove winner", trail_guide_admin.clear_experiment_path(experiment.experiment_name), class: "btn btn-sm btn-warning", method: :put %>
         | 
| 102 | 
            +
                            <% end %>
         | 
| 103 | 
            +
                          </td>
         | 
| 104 | 
            +
                        </tr>
         | 
| 105 | 
            +
                      <% end %>
         | 
| 106 | 
            +
                    </tbody>
         | 
| 107 | 
            +
                  <% end %>
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  <tfoot class="thead-light">
         | 
| 110 | 
            +
                    <tr>
         | 
| 111 | 
            +
                      <th scope="row">Totals</th>
         | 
| 112 | 
            +
                      <th><%= combined_experiment.variants.sum(&:participants) %></th>
         | 
| 113 | 
            +
                      <% if combined_experiment.goals.empty? %>
         | 
| 114 | 
            +
                        <th><%= combined_experiments.sum { |e| e.variants.sum(&:converted) } %></th>
         | 
| 115 | 
            +
                      <% else %>
         | 
| 116 | 
            +
                        <% combined_experiment.goals.each do |goal| %>
         | 
| 117 | 
            +
                          <th><%= combined_experiments.sum { |e| e.variants.sum { |v| v.converted(goal) } } %></th>
         | 
| 118 | 
            +
                        <% end %>
         | 
| 119 | 
            +
                      <% end %>
         | 
| 120 | 
            +
                      <th> </th>
         | 
| 121 | 
            +
                    </tr>
         | 
| 122 | 
            +
                  </tfoot>
         | 
| 123 | 
            +
                </table>
         | 
| 124 | 
            +
              </div>
         | 
| 125 | 
            +
            </div>
         | 
| 126 | 
            +
             | 
| 127 | 
            +
            <br />
         | 
| 128 | 
            +
            <br />
         | 
| 129 | 
            +
            <br />
         | 
| @@ -2,11 +2,8 @@ | |
| 2 2 | 
             
              <div class="col-sm-12 col-md-10 col-lg-8">
         | 
| 3 3 | 
             
                <div class="row">
         | 
| 4 4 | 
             
                  <div class="col-sm-12 col-md-6 col-lg-8">
         | 
| 5 | 
            -
                    <h3>
         | 
| 6 | 
            -
                      <%= experiment.experiment_name.to_s.humanize.titleize %>
         | 
| 7 | 
            -
                      <% if experiment.started? %>
         | 
| 8 | 
            -
                        <small class="text-muted"><%= experiment.started_at.strftime('%b %e %Y @ %l:%M %p') %></small>
         | 
| 9 | 
            -
                      <% end %>
         | 
| 5 | 
            +
                    <h3 style="margin: 0; padding: 0;" 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" %>
         | 
| 10 7 | 
             
                    </h3>
         | 
| 11 8 | 
             
                  </div>
         | 
| 12 9 | 
             
                  <div class="col-sm-12 col-md-6 col-lg-4 text-right">
         | 
| @@ -22,6 +19,22 @@ | |
| 22 19 | 
             
                  </div>
         | 
| 23 20 | 
             
                </div>
         | 
| 24 21 |  | 
| 22 | 
            +
                <div class="row">
         | 
| 23 | 
            +
                  <div class="col-sm-12 text-right">
         | 
| 24 | 
            +
                    <% if experiment.started? %>
         | 
| 25 | 
            +
                      <small class="text-muted"><%= experiment.started_at.strftime('%b %e %Y @ %l:%M %p') %></small>
         | 
| 26 | 
            +
                      <span class="text-muted">—</span>
         | 
| 27 | 
            +
                      <% if experiment.stopped? %>
         | 
| 28 | 
            +
                        <small class="text-muted"><%= experiment.stopped_at.strftime('%b %e %Y @ %l:%M %p') %></small>
         | 
| 29 | 
            +
                      <% else %>
         | 
| 30 | 
            +
                        <small class="text-muted">running</small>
         | 
| 31 | 
            +
                      <% end %>
         | 
| 32 | 
            +
                    <% else %>
         | 
| 33 | 
            +
                      <small class="text-muted">not running</small>
         | 
| 34 | 
            +
                    <% end %>
         | 
| 35 | 
            +
                  </div>
         | 
| 36 | 
            +
                </div>
         | 
| 37 | 
            +
             | 
| 25 38 | 
             
                <table class="table table-hover">
         | 
| 26 39 | 
             
                  <thead class="thead-light">
         | 
| 27 40 | 
             
                    <tr>
         | 
| @@ -46,6 +59,12 @@ | |
| 46 59 | 
             
                      <tr class="<%= "table-secondary" if variant.control? %>">
         | 
| 47 60 | 
             
                        <th scope="row">
         | 
| 48 61 | 
             
                          <%= variant.name.to_s.humanize.titleize %>
         | 
| 62 | 
            +
                          <% if experiment.running? && !experiment.winner? && participant.variant(experiment) == variant %>
         | 
| 63 | 
            +
                            <span class="badge badge-secondary">joined</span>
         | 
| 64 | 
            +
                          <% end %>
         | 
| 65 | 
            +
                          <% if experiment.winner? && variant == experiment.winner %>
         | 
| 66 | 
            +
                            <span class="badge badge-primary">winner</span>
         | 
| 67 | 
            +
                          <% end %>
         | 
| 49 68 | 
             
                          <% if variant.control? %>
         | 
| 50 69 | 
             
                            <small class="text-muted">control</small>
         | 
| 51 70 | 
             
                          <% end %>
         | 
| @@ -62,12 +81,18 @@ | |
| 62 81 | 
             
                        <% end %>
         | 
| 63 82 |  | 
| 64 83 | 
             
                        <td class="text-right">
         | 
| 65 | 
            -
                          <% if experiment.winner? %>
         | 
| 66 | 
            -
                            <% if variant ==  | 
| 67 | 
            -
                               | 
| 84 | 
            +
                          <% if experiment.running? && !experiment.winner? %>
         | 
| 85 | 
            +
                            <% if participant.variant(experiment) == variant %>
         | 
| 86 | 
            +
                              <%= link_to "leave group", trail_guide_admin.leave_experiment_path(experiment.experiment_name), class: "btn btn-sm btn-outline-secondary", method: :put %>
         | 
| 87 | 
            +
                            <% else %>
         | 
| 88 | 
            +
                              <%= link_to "enter group", trail_guide_admin.join_experiment_path(experiment.experiment_name, variant.name), class: "btn btn-sm btn-secondary", method: :put %>
         | 
| 68 89 | 
             
                            <% end %>
         | 
| 69 | 
            -
                          <%  | 
| 70 | 
            -
             | 
| 90 | 
            +
                          <% end %>
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                          <% if !experiment.winner? || variant != experiment.winner %>
         | 
| 93 | 
            +
                            <%= link_to "select winner", trail_guide_admin.winner_experiment_path(experiment.experiment_name, variant.name), class: "btn btn-sm btn-#{experiment.winner? ? "outline-" : ""}primary", method: :put %>
         | 
| 94 | 
            +
                          <% elsif experiment.winner? && variant == experiment.winner %>
         | 
| 95 | 
            +
                            <%= link_to "remove winner", trail_guide_admin.clear_experiment_path(experiment.experiment_name), class: "btn btn-sm btn-warning", method: :put %>
         | 
| 71 96 | 
             
                          <% end %>
         | 
| 72 97 | 
             
                        </td>
         | 
| 73 98 | 
             
                      </tr>
         | 
| @@ -1,13 +1,17 @@ | |
| 1 1 | 
             
            <div class="container-fluid">
         | 
| 2 2 | 
             
              <div class="row justify-content-center">
         | 
| 3 3 | 
             
                <div class="col-sm-12 col-md-10 col-lg-8">
         | 
| 4 | 
            -
                  <h1 | 
| 4 | 
            +
                  <h1><%= link_to "Experiments", trail_guide_admin.experiments_path, class: "text-dark" %></h1>
         | 
| 5 5 | 
             
                </div>
         | 
| 6 6 | 
             
              </div>
         | 
| 7 7 |  | 
| 8 8 | 
             
              <hr />
         | 
| 9 9 |  | 
| 10 10 | 
             
              <% TrailGuide.catalog.each do |experiment| %>
         | 
| 11 | 
            -
                 | 
| 11 | 
            +
                <% if experiment.combined? %>
         | 
| 12 | 
            +
                  <%= render 'combined_experiment', combined_experiment: experiment %>
         | 
| 13 | 
            +
                <% else %>
         | 
| 14 | 
            +
                  <%= render 'experiment', experiment: experiment %>
         | 
| 15 | 
            +
                <% end %>
         | 
| 12 16 | 
             
              <% end %>
         | 
| 13 17 | 
             
            </div>
         | 
    
        data/config/routes.rb
    CHANGED
    
    | @@ -21,7 +21,12 @@ if defined?(TrailGuide::Admin::Engine) | |
| 21 21 | 
             
                    match :reset,   via: [:put, :post, :get]
         | 
| 22 22 | 
             
                    match :resume,  via: [:put, :post, :get]
         | 
| 23 23 | 
             
                    match :restart, via: [:put, :post, :get]
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    match :join,    via: [:put, :post, :get], path: 'join/:variant'
         | 
| 26 | 
            +
                    match :leave,   via: [:put, :post, :get]
         | 
| 27 | 
            +
             | 
| 24 28 | 
             
                    match :winner,  via: [:put, :post, :get], path: 'winner/:variant'
         | 
| 29 | 
            +
                    match :clear,   via: [:put, :post, :get]
         | 
| 25 30 | 
             
                  end
         | 
| 26 31 | 
             
                end
         | 
| 27 32 | 
             
              end
         | 
    
        data/lib/trail_guide/catalog.rb
    CHANGED
    
    | @@ -18,8 +18,21 @@ module TrailGuide | |
| 18 18 | 
             
                  def select(name)
         | 
| 19 19 | 
             
                    catalog.select(name)
         | 
| 20 20 | 
             
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def combined_experiment(combined, name)
         | 
| 23 | 
            +
                    experiment = Class.new(TrailGuide::CombinedExperiment)
         | 
| 24 | 
            +
                    experiment.configure combined.configuration.to_h.merge({
         | 
| 25 | 
            +
                      name: name.to_s.underscore.to_sym,
         | 
| 26 | 
            +
                      parent: combined,
         | 
| 27 | 
            +
                      combined: [],
         | 
| 28 | 
            +
                      variants: combined.configuration.variants.map { |var| Variant.new(experiment, var.name, metadata: var.metadata, weight: var.weight, control: var.control?) },
         | 
| 29 | 
            +
                      # TODO also map goals once they're separate classes
         | 
| 30 | 
            +
                    })
         | 
| 31 | 
            +
                    experiment
         | 
| 32 | 
            +
                  end
         | 
| 21 33 | 
             
                end
         | 
| 22 34 |  | 
| 35 | 
            +
                delegate :combined_experiment, to: :class
         | 
| 23 36 | 
             
                attr_reader :experiments
         | 
| 24 37 |  | 
| 25 38 | 
             
                def initialize(experiments=[])
         | 
| @@ -30,15 +43,34 @@ module TrailGuide | |
| 30 43 | 
             
                  experiments.each(&block)
         | 
| 31 44 | 
             
                end
         | 
| 32 45 |  | 
| 46 | 
            +
                def all
         | 
| 47 | 
            +
                  experiments.map do |exp|
         | 
| 48 | 
            +
                    if exp.combined?
         | 
| 49 | 
            +
                      exp.combined.map { |name| combined_experiment(exp, name) }
         | 
| 50 | 
            +
                    else
         | 
| 51 | 
            +
                      exp
         | 
| 52 | 
            +
                    end
         | 
| 53 | 
            +
                  end.flatten
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 33 56 | 
             
                def find(name)
         | 
| 34 57 | 
             
                  if name.is_a?(Class)
         | 
| 35 58 | 
             
                    experiments.find { |exp| exp == name }
         | 
| 36 59 | 
             
                  else
         | 
| 37 | 
            -
                    experiments.find do |exp|
         | 
| 60 | 
            +
                    experiment = experiments.find do |exp|
         | 
| 38 61 | 
             
                      exp.experiment_name == name.to_s.underscore.to_sym ||
         | 
| 39 62 | 
             
                        exp.metric == name.to_s.underscore.to_sym ||
         | 
| 40 63 | 
             
                        exp.name == name.to_s.classify
         | 
| 41 64 | 
             
                    end
         | 
| 65 | 
            +
                    return experiment if experiment.present?
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                    combined = experiments.find do |exp|
         | 
| 68 | 
            +
                      next unless exp.combined?
         | 
| 69 | 
            +
                      exp.combined.any? { |combo| combo.to_s.underscore.to_sym == name.to_s.underscore.to_sym }
         | 
| 70 | 
            +
                    end
         | 
| 71 | 
            +
                    return nil unless combined.present?
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    return combined_experiment(combined, name)
         | 
| 42 74 | 
             
                  end
         | 
| 43 75 | 
             
                end
         | 
| 44 76 |  | 
| @@ -46,10 +78,18 @@ module TrailGuide | |
| 46 78 | 
             
                  if name.is_a?(Class)
         | 
| 47 79 | 
             
                    experiments.select { |exp| exp == name }
         | 
| 48 80 | 
             
                  else
         | 
| 81 | 
            +
                    # TODO we can be more efficient than mapping twice here
         | 
| 49 82 | 
             
                    experiments.select do |exp|
         | 
| 50 83 | 
             
                      exp.experiment_name == name.to_s.underscore.to_sym ||
         | 
| 51 84 | 
             
                        exp.metric == name.to_s.underscore.to_sym ||
         | 
| 52 | 
            -
                        exp.name == name.to_s.classify
         | 
| 85 | 
            +
                        exp.name == name.to_s.classify ||
         | 
| 86 | 
            +
                        (exp.combined? && exp.combined.any? { |combo| combo.to_s.underscore.to_sym == name.to_s.underscore.to_sym })
         | 
| 87 | 
            +
                    end.map do |exp|
         | 
| 88 | 
            +
                      if exp.combined? && exp.combined.any? { |combo| combo.to_s.underscore.to_sym == name.to_s.underscore.to_sym }
         | 
| 89 | 
            +
                        combined_experiment(exp, name)
         | 
| 90 | 
            +
                      else
         | 
| 91 | 
            +
                        exp
         | 
| 92 | 
            +
                      end
         | 
| 53 93 | 
             
                    end
         | 
| 54 94 | 
             
                  end
         | 
| 55 95 | 
             
                end
         | 
| @@ -0,0 +1,48 @@ | |
| 1 | 
            +
            require "trail_guide/experiments/base"
         | 
| 2 | 
            +
            require "trail_guide/experiments/combined_config"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module TrailGuide
         | 
| 5 | 
            +
              class CombinedExperiment < Experiments::Base
         | 
| 6 | 
            +
                class << self
         | 
| 7 | 
            +
                  delegate :parent, to: :configuration
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def configuration
         | 
| 10 | 
            +
                    @configuration ||= Experiments::CombinedConfig.new(self)
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  # TODO if just I delegate on this inheriting class, will that override the 
         | 
| 14 | 
            +
                  # defined methods on the base class? and will they interplay nicely? like
         | 
| 15 | 
            +
                  # with `started?` calling `started_at`, etc.?
         | 
| 16 | 
            +
                  #
         | 
| 17 | 
            +
                  # really wishing i'd written some specs right about now :-P
         | 
| 18 | 
            +
                  def start!
         | 
| 19 | 
            +
                    parent.start!
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def stop!
         | 
| 23 | 
            +
                    parent.stop!
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def resume!
         | 
| 27 | 
            +
                    parent.resume!
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def started_at
         | 
| 31 | 
            +
                    parent.started_at
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def stopped_at
         | 
| 35 | 
            +
                    parent.stopped_at
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                delegate :parent, to: :class
         | 
| 40 | 
            +
                delegate :running?, :started?, :started_at, :start!, to: :parent
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                # use the parent experiment as the algorithm and map to the matching variant
         | 
| 43 | 
            +
                def algorithm_choose!(metadata: nil)
         | 
| 44 | 
            +
                  variant = parent.new(participant).choose!(metadata: metadata)
         | 
| 45 | 
            +
                  variants.find { |var| var == variant.name }
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
            end
         | 
    
        data/lib/trail_guide/engine.rb
    CHANGED
    
    | @@ -46,6 +46,7 @@ module TrailGuide | |
| 46 46 | 
             
                      config.metric                     = options[:metric] if options[:metric]
         | 
| 47 47 | 
             
                      config.algorithm                  = options[:algorithm] if options[:algorithm]
         | 
| 48 48 | 
             
                      config.goals                      = options[:goals] if options[:goals]
         | 
| 49 | 
            +
                      config.combined                   = options[:combined] if options[:combined]
         | 
| 49 50 | 
             
                      config.reset_manually             = options[:reset_manually] if options.key?(:reset_manually)
         | 
| 50 51 | 
             
                      config.start_manually             = options[:start_manually] if options.key?(:start_manually)
         | 
| 51 52 | 
             
                      config.store_override             = options[:store_override] if options.key?(:store_override)
         |