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 +4 -4
- data/app/assets/images/trail_guide/trailguide.png +0 -0
- data/app/assets/stylesheets/trail_guide/admin/application.css +13 -0
- data/app/assets/stylesheets/trail_guide/admin/experiments.css +13 -0
- data/app/controllers/trail_guide/admin/application_controller.rb +13 -0
- data/app/views/layouts/trail_guide/admin/_footer.erb +5 -0
- data/app/views/layouts/trail_guide/admin/_header.erb +8 -0
- data/app/views/layouts/trail_guide/admin/application.html.erb +8 -1
- data/app/views/trail_guide/admin/experiments/_combined_experiment.html.erb +17 -13
- data/app/views/trail_guide/admin/experiments/_experiment.html.erb +16 -12
- data/app/views/trail_guide/admin/experiments/index.html.erb +1 -9
- data/config/initializers/assets.rb +1 -0
- data/lib/trail_guide/admin.rb +6 -0
- data/lib/trail_guide/catalog.rb +52 -7
- data/lib/trail_guide/engine.rb +1 -55
- data/lib/trail_guide/experiments/base.rb +3 -3
- data/lib/trail_guide/experiments/config.rb +12 -1
- data/lib/trail_guide/participant.rb +7 -7
- data/lib/trail_guide/variant.rb +3 -3
- data/lib/trail_guide/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5bee61db15d9f3cfea6ecfe502be220772f82c34a256a8bdcde1f093ab637cd3
|
4
|
+
data.tar.gz: b2f3a245b0643fb631c38f8dffb245a28a8456391fd06ac4d9c12b6f44e1aac3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0357c34c58aa5ff3d8d5fab56a5b08f137cf686b44f563d3cb72eea422fb2f60d04a01732ff0f57926a3a36811e8dc82f5fdf3b643705219e75b9d91c0efad15
|
7
|
+
data.tar.gz: 8179d27ac67509e1b4ed26c67995d446994fdfec8fe21893c1b2042203de2128fb35714f6683b85bec0ce9f1beb67eca41c3fad3051194d232a8489fd0f882a2
|
Binary file
|
@@ -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,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
|
-
|
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
|
-
<
|
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
|
-
</
|
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
|
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">—</span>
|
@@ -41,9 +44,9 @@
|
|
41
44
|
<thead class="thead-light">
|
42
45
|
<tr>
|
43
46
|
<th scope="col">
|
44
|
-
<
|
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
|
-
</
|
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
|
68
|
+
<tr>
|
66
69
|
<th scope="row">
|
67
|
-
|
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"
|
119
|
+
<th scope="row"> </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
|
-
<
|
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
|
-
</
|
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
|
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">—</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"
|
44
|
+
<th scope="col"> </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
|
62
|
+
<tr>
|
60
63
|
<th scope="row">
|
61
|
-
|
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"
|
112
|
+
<th scope="row"> </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="
|
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 )
|
data/lib/trail_guide/admin.rb
CHANGED
data/lib/trail_guide/catalog.rb
CHANGED
@@ -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
|
11
|
-
catalog
|
12
|
-
|
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
|
-
|
15
|
-
|
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
|
19
|
-
|
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)
|
data/lib/trail_guide/engine.rb
CHANGED
@@ -7,63 +7,9 @@ module TrailGuide
|
|
7
7
|
end
|
8
8
|
|
9
9
|
initializer "trailguide" do |app|
|
10
|
-
TrailGuide::
|
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, :
|
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? ||
|
225
|
-
raise InvalidGoalError, "Invalid goal checkpoint `#{checkpoint}` for `#{experiment_name}`." unless checkpoint.nil? ||
|
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,
|
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.
|
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.
|
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.
|
67
|
-
storage_key = "#{experiment.storage_key}:#{
|
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.
|
94
|
-
|
95
|
-
adapter.delete(
|
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
|
data/lib/trail_guide/variant.rb
CHANGED
@@ -57,14 +57,14 @@ module TrailGuide
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def converted(checkpoint=nil)
|
60
|
-
if experiment.
|
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.
|
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.
|
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
|
data/lib/trail_guide/version.rb
CHANGED
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.
|
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-
|
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
|