vanity 1.4.0 → 1.5.0.beta

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.
@@ -18,7 +18,7 @@ module Vanity
18
18
  def initialize(options)
19
19
  @mongo = Mongo::Connection.new(options[:host], options[:port], :connect=>false)
20
20
  @options = options.clone
21
- @options[:database] ||= (@options[:path] && @options[:path].split("/")[1])
21
+ @options[:database] ||= (@options[:path] && @options[:path].split("/")[1]) || "vanity"
22
22
  connect!
23
23
  end
24
24
 
@@ -38,7 +38,7 @@ module Vanity
38
38
  end
39
39
 
40
40
  def connect!
41
- @mongo.connect_to_master
41
+ @mongo.connect
42
42
  database = @mongo.db(@options[:database])
43
43
  database.authenticate @options[:username], @options[:password], true if @options[:username]
44
44
  @metrics = database.collection("vanity.metrics")
@@ -49,7 +49,7 @@ module Vanity
49
49
 
50
50
  def to_s
51
51
  userinfo = @options.values_at(:username, :password).join(":") if @options[:username]
52
- URI::Generic.build(:scheme=>"mongo", :userinfo=>userinfo, :host=>@options[:host], :port=>@options[:port], :path=>"/#{@options[:database]}").to_s
52
+ URI::Generic.build(:scheme=>"mongodb", :userinfo=>userinfo, :host=>@options[:host], :port=>@options[:port], :path=>"/#{@options[:database]}").to_s
53
53
  end
54
54
 
55
55
  def flushdb
@@ -16,7 +16,7 @@ module Vanity
16
16
  class RedisAdapter < AbstractAdapter
17
17
  def initialize(options)
18
18
  @options = options.clone
19
- @options[:db] = @options[:database] || (@options[:path] && @options[:path].split("/")[1].to_i)
19
+ @options[:db] ||= @options[:database] || (@options[:path] && @options[:path].split("/")[1].to_i)
20
20
  @options[:thread_safe] = true
21
21
  connect!
22
22
  end
@@ -22,12 +22,16 @@ module Vanity
22
22
  end
23
23
 
24
24
  # Escape HTML.
25
- def h(html)
26
- CGI.escapeHTML(html)
25
+ def vanity_h(html)
26
+ CGI.escapeHTML(html.to_s)
27
+ end
28
+
29
+ def vanity_html_safe(text)
30
+ text
27
31
  end
28
32
 
29
33
  # Dumbed down from Rails' simple_format.
30
- def simple_format(text, options={})
34
+ def vanity_simple_format(text, options={})
31
35
  open = "<p #{options.map { |k,v| "#{k}=\"#{CGI.escapeHTML v}\"" }.join(" ")}>"
32
36
  text = open + text.gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
33
37
  gsub(/\n\n+/, "</p>\n\n#{open}"). # 2+ newline -> paragraph
@@ -81,7 +81,9 @@ module Vanity
81
81
  attr_reader :id
82
82
 
83
83
  # Time stamp when experiment was created.
84
- attr_reader :created_at
84
+ def created_at
85
+ @created_at ||= connection.get_experiment_created_at(@id)
86
+ end
85
87
 
86
88
  # Time stamp when experiment was completed.
87
89
  attr_reader :completed_at
@@ -166,7 +168,6 @@ module Vanity
166
168
  def save
167
169
  return unless @playground.collecting?
168
170
  connection.set_experiment_created_at @id, Time.now
169
- @created_at = connection.get_experiment_created_at(@id)
170
171
  end
171
172
 
172
173
  protected
@@ -1,5 +1,16 @@
1
1
  module Vanity
2
2
  module Rails #:nodoc:
3
+ def self.load!
4
+ Vanity.playground.load_path = ::Rails.root + Vanity.playground.load_path
5
+ Vanity.playground.logger ||= Rails.logger
6
+
7
+ # Do this at the very end of initialization, allowing you to change
8
+ # connection adapter, turn collection on/off, etc.
9
+ ::Rails.configuration.after_initialize do
10
+ Vanity.playground.load!
11
+ end
12
+ end
13
+
3
14
  # The use_vanity method will setup the controller to allow testing and
4
15
  # tracking of the current user.
5
16
  module UseVanity
@@ -7,7 +18,7 @@ module Vanity
7
18
  #
8
19
  # Call with the name of a method that returns an object whose identity
9
20
  # will be used as the Vanity identity. Confusing? Let's try by example:
10
- #
21
+ #
11
22
  # class ApplicationController < ActionController::Base
12
23
  # use_vanity :current_user
13
24
  #
@@ -15,7 +26,7 @@ module Vanity
15
26
  # User.find(session[:user_id])
16
27
  # end
17
28
  # end
18
- #
29
+ #
19
30
  # If that method (current_user in this example) returns nil, Vanity will
20
31
  # set the identity for you (using a cookie to remember it across
21
32
  # requests). It also uses this mechanism if you don't provide an
@@ -69,7 +80,7 @@ module Vanity
69
80
  # intercepted, the alternative is chosen, and the user redirected to the
70
81
  # same request URL sans _vanity parameter. This only works for GET
71
82
  # requests.
72
- #
83
+ #
73
84
  # For example, if the user requests the page
74
85
  # http://example.com/?_vanity=2907dac4de, the first alternative of the
75
86
  # :null_abc experiment is chosen and the user redirected to
@@ -80,7 +91,7 @@ module Vanity
80
91
  Vanity.playground.experiments.each do |id, experiment|
81
92
  if experiment.respond_to?(:alternatives)
82
93
  experiment.alternatives.each do |alt|
83
- if hash = hashes.delete(experiment.fingerprint(alt))
94
+ if hash = hashes.delete(experiment.fingerprint(alt))
84
95
  experiment.chooses alt.value
85
96
  break
86
97
  end
@@ -120,11 +131,11 @@ module Vanity
120
131
  # def index
121
132
  # render action: ab_test(:new_page)
122
133
  # end
123
- # @example A/B test inside ERB template (condition)
134
+ # @example A/B test inside ERB template (condition)
124
135
  # <%= if ab_test(:banner) %>100% less complexity!<% end %>
125
- # @example A/B test inside ERB template (value)
136
+ # @example A/B test inside ERB template (value)
126
137
  # <%= ab_test(:greeting) %> <%= current_user.name %>
127
- # @example A/B test inside ERB template (capture)
138
+ # @example A/B test inside ERB template (capture)
128
139
  # <% ab_test :features do |count| %>
129
140
  # <%= count %> features to choose from!
130
141
  # <% end %>
@@ -137,6 +148,22 @@ module Vanity
137
148
  value
138
149
  end
139
150
  end
151
+
152
+ def vanity_h(text)
153
+ h(text)
154
+ end
155
+
156
+ def vanity_html_safe(text)
157
+ if text.respond_to?(:html_safe!)
158
+ text.html_safe!
159
+ else
160
+ text
161
+ end
162
+ end
163
+
164
+ def vanity_simple_format(text, html_options={})
165
+ vanity_html_safe(simple_format(text, html_options))
166
+ end
140
167
  end
141
168
 
142
169
 
@@ -151,13 +178,13 @@ module Vanity
151
178
  # Step 3: Open your browser to http://localhost:3000/vanity
152
179
  module Dashboard
153
180
  def index
154
- render :template=>Vanity.template("_report"), :content_type=>Mime::HTML, :layout=>true
181
+ render :file=>Vanity.template("_report"), :content_type=>Mime::HTML, :layout=>false
155
182
  end
156
183
 
157
184
  def chooses
158
185
  exp = Vanity.playground.experiment(params[:e])
159
186
  exp.chooses(exp.alternatives[params[:a].to_i].value)
160
- render :partial=>Vanity.template("experiment"), :locals=>{ :experiment=>exp }
187
+ render :file=>Vanity.template("_experiment"), :locals=>{:experiment=>exp}
161
188
  end
162
189
  end
163
190
  end
@@ -179,7 +206,7 @@ if defined?(ActionController)
179
206
  # Sets Vanity.context to the current controller, so you can do things like:
180
207
  # experiment(:simple).chooses(:green)
181
208
  def setup_controller_request_and_response
182
- setup_controller_request_and_response_without_vanity
209
+ setup_controller_request_and_response_without_vanity
183
210
  Vanity.context = @controller
184
211
  end
185
212
  end
@@ -187,18 +214,17 @@ if defined?(ActionController)
187
214
  end
188
215
 
189
216
 
190
- # Automatically configure Vanity. Uses the
217
+ # Automatically configure Vanity.
191
218
  if defined?(Rails)
192
- Rails.configuration.after_initialize do
193
- # Use Rails logger by default.
194
- Vanity.playground.logger ||= Rails.logger
195
- Vanity.playground.load_path = Rails.root + Vanity.playground.load_path
196
- Vanity.playground.collecting = Rails.env.production?
197
-
198
- # Do this at the very end of initialization, allowing you to change
199
- # connection adapter, turn collection on/off, etc.
219
+ if Rails.const_defined?(:Railtie) # Rails 3
220
+ class Plugin < Rails::Railtie # :nodoc:
221
+ initializer "vanity.require" do |app|
222
+ Vanity::Rails.load!
223
+ end
224
+ end
225
+ else
200
226
  Rails.configuration.after_initialize do
201
- Vanity.playground.load!
227
+ Vanity::Rails.load!
202
228
  end
203
229
  end
204
230
  end
@@ -206,13 +232,13 @@ end
206
232
 
207
233
  # Reconnect whenever we fork under Passenger.
208
234
  if defined?(PhusionPassenger)
209
- PhusionPassenger.on_event(:starting_worker_process) do |forked|
210
- if forked
235
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
236
+ if forked
211
237
  begin
212
238
  Vanity.playground.establish_connection if Vanity.playground.collecting?
213
239
  rescue Exception=>ex
214
- Rails.logger.error "Error reconnecting: #{ex.to_s}"
240
+ Rails.logger.error "Error reconnecting: #{ex.to_s}"
215
241
  end
216
- end
217
- end
242
+ end
243
+ end
218
244
  end
@@ -57,7 +57,7 @@ module Vanity
57
57
  def values(sdate, edate)
58
58
  query = { :conditions=>{ @ar_timestamp=>(sdate.to_time...(edate + 1).to_time) },
59
59
  :group=>"date(#{@ar_scoped.connection.quote_column_name @ar_timestamp})" }
60
- grouped = @ar_column ? @ar_scoped.calculate(@ar_aggregate, @ar_column, query) : @ar_scoped.count(query)
60
+ grouped = @ar_column ? @ar_scoped.send(@ar_aggregate, @ar_column, query) : @ar_scoped.count(query)
61
61
  (sdate..edate).inject([]) { |ordered, date| ordered << (grouped[date.to_s] || 0) }
62
62
  end
63
63
 
@@ -9,7 +9,7 @@ module Vanity
9
9
  # puts Vanity.playground.map(&:name)
10
10
  class Playground
11
11
 
12
- DEFAULTS = { :load_path=>"experiments" }
12
+ DEFAULTS = { :collecting => true, :load_path=>"experiments" }
13
13
 
14
14
  # Created new Playground. Unless you need to, use the global
15
15
  # Vanity.playground.
@@ -40,7 +40,7 @@ module Vanity
40
40
  @logger.level = Logger::ERROR
41
41
  end
42
42
  @loading = []
43
- @collecting = true
43
+ @collecting = @options[:collecting]
44
44
  end
45
45
 
46
46
  # Deprecated. Use redis.server instead.
@@ -141,7 +141,7 @@ module Vanity
141
141
  Dir[File.join(load_path, "metrics/*.rb")].each do |file|
142
142
  Metric.load self, @loading, file
143
143
  end
144
- if File.exist?("config/vanity.yml") && remote = YAML.load_file("config/vanity.yml")["metrics"]
144
+ if File.exist?("config/vanity.yml") && remote = YAML.load(ERB.new(File.read("config/vanity.yml")).result)["metrics"]
145
145
  remote.each do |id, url|
146
146
  fail "Metric #{id} already defined in playground" if metrics[id.to_sym]
147
147
  metric = Metric.new(self, id)
@@ -195,19 +195,19 @@ module Vanity
195
195
  when nil
196
196
  if File.exists?("config/vanity.yml")
197
197
  env = ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
198
- spec = YAML.load_file("config/vanity.yml")[env]
198
+ spec = YAML.load(ERB.new(File.read("config/vanity.yml")).result)[env]
199
199
  fail "No configuration for #{env}" unless spec
200
200
  establish_connection spec
201
201
  elsif File.exists?("config/redis.yml")
202
202
  env = ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
203
- redis = YAML.load_file("config/redis.yml")[env]
203
+ redis = YAML.load(ERB.new(File.read("config/redis.yml")).result)[env]
204
204
  fail "No configuration for #{env}" unless redis
205
205
  establish_connection "redis://" + redis
206
206
  else
207
207
  establish_connection :adapter=>"redis"
208
208
  end
209
209
  when Symbol
210
- spec = YAML.load_file("config/vanity.yml")[spec.to_s]
210
+ spec = YAML.load(ERB.new(File.read("config/vanity.yml")).result)[spec.to_s]
211
211
  establish_connection spec
212
212
  when String
213
213
  uri = URI.parse(spec)
@@ -283,7 +283,9 @@ module Vanity
283
283
 
284
284
  end
285
285
 
286
- @playground = Playground.new
286
+ # In the case of Rails, use the Rails logger and collect only for
287
+ # production environment by default.
288
+ @playground = Playground.new(defined?(Rails) ? { :logger => Rails.logger, :collecting => Rails.env.production? } : {})
287
289
  class << self
288
290
 
289
291
  # The playground instance.
@@ -5,7 +5,7 @@
5
5
  <% score.alts.each do |alt| %>
6
6
  <tr class="<%= "choice" if score.choice == alt %>">
7
7
  <td class="option"><%= alt.name.gsub(/^o/, "O") %>:</td>
8
- <td class="value"><code><%=h alt.value.to_s %></code></td>
8
+ <td class="value"><code><%=vanity_h alt.value.to_s %></code></td>
9
9
  <td>
10
10
  <%= "%.1f%%" % [alt.conversion_rate * 100] %>
11
11
  <%= "(%d%% better than %s)" % [alt.difference, score.least.name] if alt.difference && alt.difference >= 1 %>
@@ -1,5 +1,5 @@
1
- <h3><%=h experiment.name %> <span class="type">(<%= experiment.class.friendly_name %>)</span></h3>
2
- <%= experiment.description.to_s.split(/\n\s*\n/).map { |para| %{<p class="description">#{h para}</p>} }.join %>
1
+ <h3><%=vanity_h experiment.name %> <span class="type">(<%= experiment.class.friendly_name %>)</span></h3>
2
+ <%= experiment.description.to_s.split(/\n\s*\n/).map { |para| vanity_html_safe(%{<p class="description">#{vanity_h para}</p>}) }.join %>
3
3
  <%= render Vanity.template(experiment.type), :experiment=>experiment %>
4
4
  <p class="meta">Started <%= experiment.created_at.strftime("%a, %b %d") %>
5
5
  <%= " | Completed #{experiment.completed_at.strftime("%a, %b %d")}" unless experiment.active? %></p>
@@ -1,6 +1,6 @@
1
1
  <ul class="experiments">
2
2
  <% experiments.sort_by { |id, experiment| experiment.created_at }.reverse.each do |id, experiment| %>
3
- <li class="experiment <%= experiment.type %>" id="experiment_<%=h id.to_s %>">
3
+ <li class="experiment <%= experiment.type %>" id="experiment_<%=vanity_h id.to_s %>">
4
4
  <%= render Vanity.template("experiment"), :id=>id, :experiment=>experiment %>
5
5
  </li>
6
6
  <% end %>
@@ -1,14 +1,14 @@
1
- <h3><%=h metric.name %></h3>
2
- <%= simple_format h(Vanity::Metric.description(metric).to_s), :class=>"description" %>
1
+ <h3><%=vanity_h metric.name %></h3>
2
+ <%= vanity_simple_format vanity_h(Vanity::Metric.description(metric).to_s), :class=>"description" %>
3
3
  <%=
4
4
  begin
5
5
  data = Vanity::Metric.data(metric)
6
6
  min, max = data.map(&:last).minmax
7
7
  js = data.map { |date,value| "['#{date.to_time.httpdate}',#{value}]" }.join(",")
8
- %{<div class="chart"></div>
8
+ vanity_html_safe(%{<div class="chart"></div>
9
9
  <script type="text/javascript">
10
- $(function(){Vanity.metric("#{h id.to_s}").plot([{label:"#{h metric.name}", data: [#{js}]}])})
11
- </script>}
10
+ $(function(){Vanity.metric("#{vanity_h id.to_s}").plot([{label:"#{vanity_h metric.name}", data: [#{js}]}])})
11
+ </script>})
12
12
  rescue Exception=>ex
13
- %{<div class="error">#{h ex.message}</div>}
13
+ %{<div class="error">#{vanity_h ex.message}</div>}
14
14
  end %>
@@ -1,13 +1,14 @@
1
1
  <ul class="metrics">
2
2
  <% metrics.sort_by { |id, metric| metric.name }.each do |id, metric| %>
3
3
  <li class="metric" id="metric_<%= id %>">
4
- <%= render Vanity.template("metric"), :id=>id, :metric=>metric %>
4
+ <%= render :file=>Vanity.template("_metric"), :locals=>{:id=>id, :metric=>metric} %>
5
+ <%= render :file=>Vanity.template("_metric"), :locals=>{:id=>id, :metric=>metric} %>
5
6
  </li>
6
7
  <% end %>
7
8
  </ul>
8
9
  <form id="milestones">
9
10
  <% experiments.each do |id, experiment| %>
10
11
  <label><input type="checkbox" name="milestone" data-start="<%= experiment.created_at.httpdate %>"
11
- data-end="<%= (experiment.completed_at || Time.now).httpdate %>"><%=h experiment.name %></label>
12
+ data-end="<%= (experiment.completed_at || Time.now).httpdate %>"><%=vanity_h experiment.name %></label>
12
13
  <% end %>
13
14
  </form>
@@ -4,22 +4,22 @@
4
4
  <style>
5
5
  .vanity { margin: 2em auto; width: 40em; font-family: "Helvetica Neue", "Helvetica", "Verdana", sans-serif }
6
6
  .vanity h1 { margin: 1em 0; border-bottom: 3px solid #ccc }
7
- <%= File.read(Vanity.template("vanity.css")) %>
7
+ <%= vanity_html_safe(File.read(Vanity.template("vanity.css"))) %>
8
8
  </style>
9
- <script type="text/javascript"><%= File.read(Vanity.template("jquery.min.js")) %></script>
10
- <script type="text/javascript"><%= File.read(Vanity.template("flot.min.js")) %></script>
11
- <script type="text/javascript"><%= File.read(Vanity.template("vanity.js")) %></script>
9
+ <script type="text/javascript"><%= vanity_html_safe(File.read(Vanity.template("jquery.min.js"))) %></script>
10
+ <script type="text/javascript"><%= vanity_html_safe(File.read(Vanity.template("flot.min.js"))) %></script>
11
+ <script type="text/javascript"><%= vanity_html_safe(File.read(Vanity.template("vanity.js"))) %></script>
12
12
  <% if respond_to?(:form_authenticity_token) %><script type="text/javascript">document.auth_token = "<%= form_authenticity_token %>"</script><% end %>
13
13
  </head>
14
14
  <body>
15
15
  <div class="vanity">
16
16
  <% experiments = Vanity.playground.experiments ; unless experiments.empty? %>
17
17
  <h2>Experiments</h2>
18
- <%= render Vanity.template("experiments"), :experiments=>experiments %>
18
+ <%= render :file=>Vanity.template("_experiments"), :locals=>{:experiments=>experiments} %>
19
19
  <% end %>
20
20
  <% metrics = Vanity.playground.metrics ; unless metrics.empty? %>
21
21
  <h2>Metrics</h2>
22
- <%= render Vanity.template("metrics"), :metrics=>metrics, :experiments=>experiments %>
22
+ <%= render :file=>Vanity.template("_metrics"), :locals=>{:metrics=>metrics, :experiments=>experiments} %>
23
23
  <% end %>
24
24
  <p class="footer">Generated by <a href="http://vanity.labnotes.org">Vanity</a></p>
25
25
  </div>
@@ -0,0 +1,11 @@
1
+ module Vanity
2
+ VERSION = "1.5.0.beta"
3
+
4
+ module Version
5
+ version = VERSION.to_s.split(".").map { |i| i.to_i }
6
+ MAJOR = version[0]
7
+ MINOR = version[1]
8
+ PATCH = version[2]
9
+ STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
10
+ end
11
+ end
@@ -42,7 +42,6 @@ context "ActiveRecord Metric" do
42
42
  end
43
43
 
44
44
  test "record average" do
45
- Sky.aggregates
46
45
  File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
47
46
  f.write <<-RUBY
48
47
  metric "Sky is limit" do
@@ -51,13 +50,12 @@ context "ActiveRecord Metric" do
51
50
  RUBY
52
51
  end
53
52
  Vanity.playground.metrics
54
- Sky.create! :height=>4
53
+ Sky.create! :height=>8
55
54
  Sky.create! :height=>2
56
- assert_equal 3, Vanity::Metric.data(metric(:sky_is_limit)).last.last
55
+ assert_equal 5, Vanity::Metric.data(metric(:sky_is_limit)).last.last
57
56
  end
58
57
 
59
58
  test "record minimum" do
60
- Sky.aggregates
61
59
  File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
62
60
  f.write <<-RUBY
63
61
  metric "Sky is limit" do
@@ -72,7 +70,6 @@ context "ActiveRecord Metric" do
72
70
  end
73
71
 
74
72
  test "record maximum" do
75
- Sky.aggregates
76
73
  File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
77
74
  f.write <<-RUBY
78
75
  metric "Sky is limit" do
@@ -108,7 +105,6 @@ context "ActiveRecord Metric" do
108
105
  end
109
106
 
110
107
  test "with scope" do
111
- Sky.aggregates
112
108
  File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
113
109
  f.write <<-RUBY
114
110
  metric "Sky is limit" do