trailguide 0.1.15 → 0.1.16

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f3fe6f7f5a63ba2a075cb5ce550410ab6daed98a35ee565468bd45205ea784b
4
- data.tar.gz: fd1fe329b9c64e4a1da9acaeb47b5601a76311123104d1bd9b4e8dec21076c0b
3
+ metadata.gz: 5bee61db15d9f3cfea6ecfe502be220772f82c34a256a8bdcde1f093ab637cd3
4
+ data.tar.gz: b2f3a245b0643fb631c38f8dffb245a28a8456391fd06ac4d9c12b6f44e1aac3
5
5
  SHA512:
6
- metadata.gz: cd34a9c7628dcb95be218548167eac9fc59e4188ad90b3c528f0cab28bc87b08019736c87d210760b1a47d57344691c3a06561e3a8606030e82c3c29ddb15214
7
- data.tar.gz: fc2afd4f9335ce021eac70b65546a99f1f954928ab2cd8c6e3c442fb221e6e41c640ea2563873d640cb268b95a2de442b2f488bedb1608a8f5ceb98022f6b321
6
+ metadata.gz: 0357c34c58aa5ff3d8d5fab56a5b08f137cf686b44f563d3cb72eea422fb2f60d04a01732ff0f57926a3a36811e8dc82f5fdf3b643705219e75b9d91c0efad15
7
+ data.tar.gz: 8179d27ac67509e1b4ed26c67995d446994fdfec8fe21893c1b2042203de2128fb35714f6683b85bec0ce9f1beb67eca41c3fad3051194d232a8489fd0f882a2
@@ -13,3 +13,16 @@
13
13
  *= require_tree .
14
14
  *= require_self
15
15
  */
16
+
17
+ .navbar-brand img {
18
+ width: 30px;
19
+ height: 30px;
20
+ }
21
+
22
+ main {
23
+ padding-top: 40px;
24
+ }
25
+
26
+ .footer {
27
+ padding: 10px 15px;
28
+ }
@@ -0,0 +1,13 @@
1
+ .experiment {
2
+ margin-bottom: 40px;
3
+ }
4
+
5
+ .experiment table th, .experiment table td {
6
+ line-height: 31px;
7
+ }
8
+
9
+ .experiment table th h5 {
10
+ margin: 0;
11
+ padding: 0;
12
+ line-height: 31px;
13
+ }
@@ -2,6 +2,19 @@ module TrailGuide
2
2
  module Admin
3
3
  class ApplicationController < ::ApplicationController
4
4
  protect_from_forgery with: :exception
5
+
6
+ def preview_url(variant, *args, **opts)
7
+ config_url = variant.experiment.configuration.preview_url
8
+ opts = opts.merge({experiment: {variant.experiment.experiment_name => variant.name}})
9
+ if config_url.respond_to?(:call)
10
+ main_app.instance_exec *args, **opts, &config_url
11
+ elsif config_url.is_a?(Symbol)
12
+ main_app.send(config_url, *args, **opts)
13
+ else
14
+ config_url.to_s + "?experiment[#{variant.experiment.experiment_name}]=#{variant.name}"
15
+ end
16
+ end
17
+ helper_method :preview_url
5
18
  end
6
19
  end
7
20
  end
@@ -0,0 +1,5 @@
1
+ <div class="footer bg-light">
2
+ <div class="text-right">
3
+ <%= link_to "TrailGuide v#{TrailGuide::Version::VERSION}", "https://github.com/markrebec/trailguide", target: :blank, class: "text-muted" %>
4
+ </div>
5
+ </div>
@@ -0,0 +1,8 @@
1
+ <nav class="navbar navbar-expanded-sm navbar-light bg-light">
2
+ <%= link_to trail_guide_admin.experiments_path, class: "navbar-brand" do %>
3
+ <%= image_tag "trail_guide/trailguide.png" %>
4
+ <%= TrailGuide::Admin.configuration.title %>
5
+ <% end %>
6
+ <span class="navbar-brand"><small class="text-muted"><%= TrailGuide::Admin.configuration.subtitle %></small></span>
7
+ </nav>
8
+
@@ -11,8 +11,15 @@
11
11
  <%= stylesheet_link_tag "trail_guide/admin/application", media: "all" %>
12
12
  </head>
13
13
  <body>
14
+ <%= render 'layouts/trail_guide/admin/header' %>
14
15
 
15
- <%= yield %>
16
+ <main>
17
+ <div class="container-fluid">
18
+ <%= yield %>
19
+ </div>
20
+ </main>
21
+
22
+ <%= render 'layouts/trail_guide/admin/footer' %>
16
23
 
17
24
  <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
18
25
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
@@ -1,10 +1,10 @@
1
- <div class="row justify-content-center">
1
+ <div class="experiment row justify-content-center">
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 style="margin: 0; padding: 0;" id="<%= combined_experiment.experiment_name %>">
5
+ <h4 id="<%= combined_experiment.experiment_name %>">
6
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>
7
+ </h4>
8
8
  </div>
9
9
  <div class="col-sm-12 col-md-6 col-lg-4 text-right">
10
10
  <% if combined_experiment.running? %>
@@ -20,7 +20,10 @@
20
20
  </div>
21
21
 
22
22
  <div class="row">
23
- <div class="col-sm-12 text-right">
23
+ <div class="col-sm-12 col-md-6 col-lg-8">
24
+ <p><%= combined_experiment.configuration.summary %></p>
25
+ </div>
26
+ <div class="col-sm-12 col-md-6 col-lg-4 text-right">
24
27
  <% if combined_experiment.started? %>
25
28
  <small class="text-muted"><%= combined_experiment.started_at.strftime('%b %e %Y @ %l:%M %p') %></small>
26
29
  <span class="text-muted">&mdash;</span>
@@ -41,9 +44,9 @@
41
44
  <thead class="thead-light">
42
45
  <tr>
43
46
  <th scope="col">
44
- <h4 style="margin: 0; padding: 0" id="<%= experiment.experiment_name %>">
47
+ <h5 id="<%= experiment.experiment_name %>">
45
48
  <%= link_to experiment.experiment_name.to_s.humanize.titleize, trail_guide_admin.experiments_path(anchor: experiment.experiment_name), class: "text-dark" %>
46
- </h4>
49
+ </h5>
47
50
  </th>
48
51
 
49
52
  <th scope="col">Participants</th>
@@ -62,9 +65,14 @@
62
65
 
63
66
  <tbody>
64
67
  <% experiment.variants.each do |variant| %>
65
- <tr class="<%= "table-secondary" if variant.control? %>">
68
+ <tr>
66
69
  <th scope="row">
67
- <%= variant.name.to_s.humanize.titleize %>
70
+ <% if experiment.configuration.preview_url? %>
71
+ <%= link_to variant.name.to_s.humanize.titleize, preview_url(variant), target: :blank, class: "text-dark" %>
72
+ <% else %>
73
+ <%= variant.name.to_s.humanize.titleize %>
74
+ <% end %>
75
+
68
76
  <% if experiment.running? && !experiment.winner? && participant.variant(experiment) == variant %>
69
77
  <span class="badge badge-secondary">joined</span>
70
78
  <% end %>
@@ -108,7 +116,7 @@
108
116
 
109
117
  <tfoot class="thead-light">
110
118
  <tr>
111
- <th scope="row">Totals</th>
119
+ <th scope="row">&nbsp;</th>
112
120
  <th><%= combined_experiment.variants.sum(&:participants) %></th>
113
121
  <% if combined_experiment.goals.empty? %>
114
122
  <th><%= combined_experiments.sum { |e| e.variants.sum(&:converted) } %></th>
@@ -123,7 +131,3 @@
123
131
  </table>
124
132
  </div>
125
133
  </div>
126
-
127
- <br />
128
- <br />
129
- <br />
@@ -1,10 +1,10 @@
1
- <div class="row justify-content-center">
1
+ <div class="experiment row justify-content-center">
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 style="margin: 0; padding: 0;" id="<%= experiment.experiment_name %>">
5
+ <h4 id="<%= experiment.experiment_name %>">
6
6
  <%= link_to experiment.experiment_name.to_s.humanize.titleize, trail_guide_admin.experiments_path(anchor: experiment.experiment_name), class: "text-dark" %>
7
- </h3>
7
+ </h4>
8
8
  </div>
9
9
  <div class="col-sm-12 col-md-6 col-lg-4 text-right">
10
10
  <% if experiment.running? %>
@@ -20,7 +20,10 @@
20
20
  </div>
21
21
 
22
22
  <div class="row">
23
- <div class="col-sm-12 text-right">
23
+ <div class="col-sm-12 col-md-6 col-lg-8">
24
+ <p><%= experiment.configuration.summary %></p>
25
+ </div>
26
+ <div class="col-sm-12 col-md-6 col-lg-4 text-right">
24
27
  <% if experiment.started? %>
25
28
  <small class="text-muted"><%= experiment.started_at.strftime('%b %e %Y @ %l:%M %p') %></small>
26
29
  <span class="text-muted">&mdash;</span>
@@ -38,7 +41,7 @@
38
41
  <table class="table table-hover">
39
42
  <thead class="thead-light">
40
43
  <tr>
41
- <th scope="col">Group</th>
44
+ <th scope="col">&nbsp;</th>
42
45
 
43
46
  <th scope="col">Participants</th>
44
47
 
@@ -56,9 +59,14 @@
56
59
 
57
60
  <tbody>
58
61
  <% experiment.variants.each do |variant| %>
59
- <tr class="<%= "table-secondary" if variant.control? %>">
62
+ <tr>
60
63
  <th scope="row">
61
- <%= variant.name.to_s.humanize.titleize %>
64
+ <% if experiment.configuration.preview_url? %>
65
+ <%= link_to variant.name.to_s.humanize.titleize, preview_url(variant), target: :blank, class: "text-dark" %>
66
+ <% else %>
67
+ <%= variant.name.to_s.humanize.titleize %>
68
+ <% end %>
69
+
62
70
  <% if experiment.running? && !experiment.winner? && participant.variant(experiment) == variant %>
63
71
  <span class="badge badge-secondary">joined</span>
64
72
  <% end %>
@@ -101,7 +109,7 @@
101
109
 
102
110
  <tfoot class="thead-light">
103
111
  <tr>
104
- <th scope="row">Totals</th>
112
+ <th scope="row">&nbsp;</th>
105
113
  <th><%= experiment.variants.sum(&:participants) %></th>
106
114
  <% if experiment.goals.empty? %>
107
115
  <th><%= experiment.variants.sum(&:converted) %></th>
@@ -116,7 +124,3 @@
116
124
  </table>
117
125
  </div>
118
126
  </div>
119
-
120
- <br />
121
- <br />
122
- <br />
@@ -1,12 +1,4 @@
1
- <div class="container-fluid">
2
- <div class="row justify-content-center">
3
- <div class="col-sm-12 col-md-10 col-lg-8">
4
- <h1><%= link_to "Experiments", trail_guide_admin.experiments_path, class: "text-dark" %></h1>
5
- </div>
6
- </div>
7
-
8
- <hr />
9
-
1
+ <div class="experiments">
10
2
  <% TrailGuide.catalog.each do |experiment| %>
11
3
  <% if experiment.combined? %>
12
4
  <%= render 'combined_experiment', combined_experiment: experiment %>
@@ -0,0 +1 @@
1
+ Rails.application.config.assets.precompile += %w( trail_guide/trailguide.png )
@@ -2,5 +2,11 @@ require "trail_guide/admin/engine"
2
2
 
3
3
  module TrailGuide
4
4
  module Admin
5
+ include Canfig::Module
6
+
7
+ configure do |config|
8
+ config.title = 'TrailGuide'
9
+ config.subtitle = 'Experiments and A/B Tests'
10
+ end
5
11
  end
6
12
  end
@@ -1,5 +1,14 @@
1
1
  module TrailGuide
2
2
  class Catalog
3
+ class DSL
4
+ def self.experiment(name, &block)
5
+ Class.new(TrailGuide::Experiment) do
6
+ configure name: name
7
+ configure &block
8
+ end
9
+ end
10
+ end
11
+
3
12
  include Enumerable
4
13
 
5
14
  class << self
@@ -7,16 +16,52 @@ module TrailGuide
7
16
  @catalog ||= new
8
17
  end
9
18
 
10
- def register(klass)
11
- catalog.register(klass)
12
- end
19
+ def load_experiments!
20
+ @catalog = nil
21
+
22
+ # Load experiments from YAML configs if any exists
23
+ load_yaml_experiments(Rails.root.join("config/experiments.yml"))
24
+ Dir[Rails.root.join("config/experiments/**/*.yml")].each { |f| load_yaml_experiments(f) }
13
25
 
14
- def find(name)
15
- catalog.find(name)
26
+ # Load experiments from ruby configs if any exist
27
+ DSL.instance_eval(File.read(Rails.root.join("config/experiments.rb"))) if File.exists?(Rails.root.join("config/experiments.rb"))
28
+ Dir[Rails.root.join("config/experiments/**/*.rb")].each { |f| DSL.instance_eval(File.read(f)) }
29
+
30
+ # Load any experiment classes defined in the app
31
+ Dir[Rails.root.join("app/experiments/**/*.rb")].each { |f| load f }
16
32
  end
17
33
 
18
- def select(name)
19
- catalog.select(name)
34
+ def load_yaml_experiments(file)
35
+ experiments = (YAML.load_file(file) || {} rescue {})
36
+ .symbolize_keys.map { |k,v| [k, v.symbolize_keys] }.to_h
37
+
38
+ experiments.each do |name, options|
39
+ expvars = options[:variants].map do |var|
40
+ if var.is_a?(Array)
41
+ [var[0], var[1].symbolize_keys]
42
+ else
43
+ [var]
44
+ end
45
+ end
46
+
47
+ DSL.experiment(name) do |config|
48
+ expvars.each do |expvar|
49
+ variant *expvar
50
+ end
51
+ # TODO also map goals once they're real classes
52
+ config.control = options[:control] if options[:control]
53
+ config.metric = options[:metric] if options[:metric]
54
+ config.algorithm = options[:algorithm] if options[:algorithm]
55
+ config.goals = options[:goals] if options[:goals]
56
+ config.combined = options[:combined] if options[:combined]
57
+ config.reset_manually = options[:reset_manually] if options.key?(:reset_manually)
58
+ config.start_manually = options[:start_manually] if options.key?(:start_manually)
59
+ config.store_override = options[:store_override] if options.key?(:store_override)
60
+ config.track_override = options[:track_override] if options.key?(:track_override)
61
+ config.allow_multiple_conversions = options[:allow_multiple_conversions] if options.key?(:allow_multiple_conversions)
62
+ config.allow_multiple_goals = options[:allow_multiple_goals] if options.key?(:allow_multiple_goals)
63
+ end
64
+ end
20
65
  end
21
66
 
22
67
  def combined_experiment(combined, name)
@@ -7,63 +7,9 @@ module TrailGuide
7
7
  end
8
8
 
9
9
  initializer "trailguide" do |app|
10
- TrailGuide::Engine.load_experiments
10
+ TrailGuide::Catalog.load_experiments!
11
11
  ActionController::Base.send :include, TrailGuide::Helper
12
12
  ActionController::Base.helper TrailGuide::Helper
13
13
  end
14
-
15
- def self.load_experiments
16
- # Load experiments from YAML configs if any exists
17
- load_yaml_experiments(Rails.root.join("config/experiments.yml"))
18
- Dir[Rails.root.join("config/experiments/**/*.yml")].each { |f| load_yaml_experiments(f) }
19
-
20
- # Load experiments from ruby configs if any exist
21
- DSL.instance_eval(File.read(Rails.root.join("config/experiments.rb"))) if File.exists?(Rails.root.join("config/experiments.rb"))
22
- Dir[Rails.root.join("config/experiments/**/*.rb")].each { |f| DSL.instance_eval(File.read(f)) }
23
-
24
- # Load any experiment classes defined in the app
25
- Dir[Rails.root.join("app/experiments/**/*.rb")].each { |f| load f }
26
- end
27
-
28
- def self.load_yaml_experiments(file)
29
- experiments = (YAML.load_file(file) || {} rescue {})
30
- .symbolize_keys.map { |k,v| [k, v.symbolize_keys] }.to_h
31
-
32
- experiments.each do |name, options|
33
- expvars = options[:variants].map do |var|
34
- if var.is_a?(Array)
35
- [var[0], var[1].symbolize_keys]
36
- else
37
- [var]
38
- end
39
- end
40
-
41
- DSL.experiment(name) do |config|
42
- expvars.each do |expvar|
43
- variant *expvar
44
- end
45
- config.control = options[:control] if options[:control]
46
- config.metric = options[:metric] if options[:metric]
47
- config.algorithm = options[:algorithm] if options[:algorithm]
48
- config.goals = options[:goals] if options[:goals]
49
- config.combined = options[:combined] if options[:combined]
50
- config.reset_manually = options[:reset_manually] if options.key?(:reset_manually)
51
- config.start_manually = options[:start_manually] if options.key?(:start_manually)
52
- config.store_override = options[:store_override] if options.key?(:store_override)
53
- config.track_override = options[:track_override] if options.key?(:track_override)
54
- config.allow_multiple_conversions = options[:allow_multiple_conversions] if options.key?(:allow_multiple_conversions)
55
- config.allow_multiple_goals = options[:allow_multiple_goals] if options.key?(:allow_multiple_goals)
56
- end
57
- end
58
- end
59
-
60
- class DSL
61
- def self.experiment(name, &block)
62
- Class.new(TrailGuide::Experiment) do
63
- configure name: name
64
- configure &block
65
- end
66
- end
67
- end
68
14
  end
69
15
  end
@@ -158,7 +158,7 @@ module TrailGuide
158
158
  end
159
159
 
160
160
  attr_reader :participant
161
- delegate :configuration, :experiment_name, :variants, :control, :funnels,
161
+ delegate :configuration, :experiment_name, :variants, :control, :goals,
162
162
  :storage_key, :running?, :started?, :started_at, :start!, :resettable?,
163
163
  :winner?, :allow_multiple_conversions?, :allow_multiple_goals?,
164
164
  :track_winner_conversions?, :callbacks, to: :class
@@ -221,8 +221,8 @@ module TrailGuide
221
221
  def convert!(checkpoint=nil, metadata: nil)
222
222
  return false if !running? || (winner? && !track_winner_conversions?)
223
223
  return false unless participating?
224
- raise InvalidGoalError, "Invalid goal checkpoint `#{checkpoint}` for `#{experiment_name}`." unless checkpoint.present? || funnels.empty?
225
- raise InvalidGoalError, "Invalid goal checkpoint `#{checkpoint}` for `#{experiment_name}`." unless checkpoint.nil? || funnels.any? { |funnel| funnel == checkpoint.to_s.underscore.to_sym }
224
+ raise InvalidGoalError, "Invalid goal checkpoint `#{checkpoint}` for `#{experiment_name}`." unless checkpoint.present? || goals.empty?
225
+ raise InvalidGoalError, "Invalid goal checkpoint `#{checkpoint}` for `#{experiment_name}`." unless checkpoint.nil? || goals.any? { |goal| goal == checkpoint.to_s.underscore.to_sym }
226
226
  # TODO eventually allow progressing through funnel checkpoints towards goals
227
227
  if converted?(checkpoint)
228
228
  return false unless allow_multiple_conversions?
@@ -14,7 +14,14 @@ module TrailGuide
14
14
  end
15
15
 
16
16
  def self.default_config
17
- { name: nil, metric: nil, variants: [], goals: [], combined: [] }
17
+ { name: nil,
18
+ metric: nil,
19
+ variants: [],
20
+ goals: [],
21
+ combined: [],
22
+ summary: nil,
23
+ preview_url: nil,
24
+ }
18
25
  end
19
26
 
20
27
  def self.callbacks_config
@@ -115,6 +122,10 @@ module TrailGuide
115
122
  !combined.empty?
116
123
  end
117
124
 
125
+ def preview_url?
126
+ !!preview_url
127
+ end
128
+
118
129
  def on_choose(meth=nil, &block)
119
130
  callbacks[:on_choose] << (meth || block)
120
131
  end
@@ -48,7 +48,7 @@ module TrailGuide
48
48
 
49
49
  def converted?(experiment, checkpoint=nil)
50
50
  return false unless experiment.started?
51
- if experiment.funnels.empty?
51
+ if experiment.goals.empty?
52
52
  raise InvalidGoalError, "You provided the checkpoint `#{checkpoint}` but the experiment `#{experiment.experiment_name}` does not have any goals defined." unless checkpoint.nil?
53
53
  storage_key = "#{experiment.storage_key}:converted"
54
54
  return false unless adapter.key?(storage_key)
@@ -56,15 +56,15 @@ module TrailGuide
56
56
  converted_at = Time.at(adapter[storage_key].to_i)
57
57
  converted_at >= experiment.started_at
58
58
  elsif !checkpoint.nil?
59
- raise InvalidGoalError, "Invalid goal checkpoint `#{checkpoint}` for experiment `#{experiment.experiment_name}`." unless experiment.funnels.any? { |funnel| funnel == checkpoint.to_s.underscore.to_sym }
59
+ raise InvalidGoalError, "Invalid goal checkpoint `#{checkpoint}` for experiment `#{experiment.experiment_name}`." unless experiment.goals.any? { |goal| goal == checkpoint.to_s.underscore.to_sym }
60
60
  storage_key = "#{experiment.storage_key}:#{checkpoint.to_s.underscore}"
61
61
  return false unless adapter.key?(storage_key)
62
62
 
63
63
  converted_at = Time.at(adapter[storage_key].to_i)
64
64
  converted_at >= experiment.started_at
65
65
  else
66
- experiment.funnels.each do |funnel|
67
- storage_key = "#{experiment.storage_key}:#{funnel.to_s}"
66
+ experiment.goals.each do |goal|
67
+ storage_key = "#{experiment.storage_key}:#{goal.to_s}"
68
68
  next unless adapter.key?(storage_key)
69
69
  converted_at = Time.at(adapter[storage_key].to_i)
70
70
  return true if converted_at >= experiment.started_at
@@ -90,9 +90,9 @@ module TrailGuide
90
90
  adapter.delete(variant.experiment.storage_key)
91
91
  adapter.delete(variant.storage_key)
92
92
  adapter.delete(storage_key)
93
- variant.experiment.funnels.each do |funnel|
94
- funnel_key = "#{variant.experiment.storage_key}:#{funnel.to_s}"
95
- adapter.delete(funnel_key)
93
+ variant.experiment.goals.each do |goal|
94
+ goal_key = "#{variant.experiment.storage_key}:#{goal.to_s}"
95
+ adapter.delete(goal_key)
96
96
  end
97
97
  else
98
98
  adapter[storage_key] = Time.now.to_i
@@ -57,14 +57,14 @@ module TrailGuide
57
57
  end
58
58
 
59
59
  def converted(checkpoint=nil)
60
- if experiment.funnels.empty?
60
+ if experiment.goals.empty?
61
61
  raise InvalidGoalError, "You provided the checkpoint `#{checkpoint}` but the experiment `#{experiment.experiment_name}` does not have any goals defined." unless checkpoint.nil?
62
62
  (TrailGuide.redis.hget(storage_key, 'converted') || 0).to_i
63
63
  elsif !checkpoint.nil?
64
- raise InvalidGoalError, "Invalid goal checkpoint `#{checkpoint}` for experiment `#{experiment.experiment_name}`." unless experiment.funnels.any? { |funnel| funnel == checkpoint.to_s.underscore.to_sym }
64
+ raise InvalidGoalError, "Invalid goal checkpoint `#{checkpoint}` for experiment `#{experiment.experiment_name}`." unless experiment.goals.any? { |goal| goal == checkpoint.to_s.underscore.to_sym }
65
65
  (TrailGuide.redis.hget(storage_key, checkpoint.to_s.underscore) || 0).to_i
66
66
  else
67
- experiment.funnels.sum do |checkpoint|
67
+ experiment.goals.sum do |checkpoint|
68
68
  (TrailGuide.redis.hget(storage_key, checkpoint.to_s.underscore) || 0).to_i
69
69
  end
70
70
  end
@@ -2,7 +2,7 @@ module TrailGuide
2
2
  module Version
3
3
  MAJOR = 0
4
4
  MINOR = 1
5
- PATCH = 15
5
+ PATCH = 16
6
6
  VERSION = "#{MAJOR}.#{MINOR}.#{PATCH}"
7
7
 
8
8
  class << self
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trailguide
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.15
4
+ version: 0.1.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Rebec
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-03-07 00:00:00.000000000 Z
11
+ date: 2019-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -104,16 +104,21 @@ files:
104
104
  - MIT-LICENSE
105
105
  - README.md
106
106
  - app/assets/config/trail_guide/admin_manifest.js
107
+ - app/assets/images/trail_guide/trailguide.png
107
108
  - app/assets/javascripts/trail_guide/admin/application.js
108
109
  - app/assets/javascripts/trailguide.js
109
110
  - app/assets/stylesheets/trail_guide/admin/application.css
111
+ - app/assets/stylesheets/trail_guide/admin/experiments.css
110
112
  - app/controllers/trail_guide/admin/application_controller.rb
111
113
  - app/controllers/trail_guide/admin/experiments_controller.rb
112
114
  - app/controllers/trail_guide/experiments_controller.rb
115
+ - app/views/layouts/trail_guide/admin/_footer.erb
116
+ - app/views/layouts/trail_guide/admin/_header.erb
113
117
  - app/views/layouts/trail_guide/admin/application.html.erb
114
118
  - app/views/trail_guide/admin/experiments/_combined_experiment.html.erb
115
119
  - app/views/trail_guide/admin/experiments/_experiment.html.erb
116
120
  - app/views/trail_guide/admin/experiments/index.html.erb
121
+ - config/initializers/assets.rb
117
122
  - config/routes.rb
118
123
  - lib/trail_guide/adapters.rb
119
124
  - lib/trail_guide/adapters/participants.rb