moses-vanity 1.7.1
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.
- data/.autotest +22 -0
- data/.gitignore +7 -0
- data/.rvmrc +3 -0
- data/.travis.yml +13 -0
- data/CHANGELOG +374 -0
- data/Gemfile +28 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +108 -0
- data/Rakefile +189 -0
- data/bin/vanity +16 -0
- data/doc/_config.yml +2 -0
- data/doc/_layouts/_header.html +34 -0
- data/doc/_layouts/page.html +47 -0
- data/doc/_metrics.textile +12 -0
- data/doc/ab_testing.textile +210 -0
- data/doc/configuring.textile +45 -0
- data/doc/contributing.textile +93 -0
- data/doc/credits.textile +23 -0
- data/doc/css/page.css +83 -0
- data/doc/css/print.css +43 -0
- data/doc/css/syntax.css +7 -0
- data/doc/email.textile +129 -0
- data/doc/experimental.textile +31 -0
- data/doc/faq.textile +8 -0
- data/doc/identity.textile +43 -0
- data/doc/images/ab_in_dashboard.png +0 -0
- data/doc/images/clear_winner.png +0 -0
- data/doc/images/price_options.png +0 -0
- data/doc/images/sidebar_test.png +0 -0
- data/doc/images/signup_metric.png +0 -0
- data/doc/images/vanity.png +0 -0
- data/doc/index.textile +91 -0
- data/doc/metrics.textile +231 -0
- data/doc/rails.textile +89 -0
- data/doc/site.js +27 -0
- data/generators/templates/vanity_migration.rb +53 -0
- data/generators/vanity_generator.rb +8 -0
- data/lib/generators/templates/vanity_migration.rb +53 -0
- data/lib/generators/vanity_generator.rb +15 -0
- data/lib/vanity.rb +36 -0
- data/lib/vanity/adapters/abstract_adapter.rb +140 -0
- data/lib/vanity/adapters/active_record_adapter.rb +248 -0
- data/lib/vanity/adapters/mock_adapter.rb +157 -0
- data/lib/vanity/adapters/mongodb_adapter.rb +178 -0
- data/lib/vanity/adapters/redis_adapter.rb +160 -0
- data/lib/vanity/backport.rb +26 -0
- data/lib/vanity/commands/list.rb +21 -0
- data/lib/vanity/commands/report.rb +64 -0
- data/lib/vanity/commands/upgrade.rb +34 -0
- data/lib/vanity/experiment/ab_test.rb +507 -0
- data/lib/vanity/experiment/base.rb +214 -0
- data/lib/vanity/frameworks.rb +16 -0
- data/lib/vanity/frameworks/rails.rb +318 -0
- data/lib/vanity/helpers.rb +66 -0
- data/lib/vanity/images/x.gif +0 -0
- data/lib/vanity/metric/active_record.rb +85 -0
- data/lib/vanity/metric/base.rb +244 -0
- data/lib/vanity/metric/google_analytics.rb +83 -0
- data/lib/vanity/metric/remote.rb +53 -0
- data/lib/vanity/playground.rb +396 -0
- data/lib/vanity/templates/_ab_test.erb +28 -0
- data/lib/vanity/templates/_experiment.erb +5 -0
- data/lib/vanity/templates/_experiments.erb +7 -0
- data/lib/vanity/templates/_metric.erb +14 -0
- data/lib/vanity/templates/_metrics.erb +13 -0
- data/lib/vanity/templates/_report.erb +27 -0
- data/lib/vanity/templates/_vanity.js.erb +20 -0
- data/lib/vanity/templates/flot.min.js +1 -0
- data/lib/vanity/templates/jquery.min.js +19 -0
- data/lib/vanity/templates/vanity.css +26 -0
- data/lib/vanity/templates/vanity.js +82 -0
- data/lib/vanity/version.rb +11 -0
- data/test/adapters/redis_adapter_test.rb +17 -0
- data/test/experiment/ab_test.rb +771 -0
- data/test/experiment/base_test.rb +150 -0
- data/test/experiments/age_and_zipcode.rb +19 -0
- data/test/experiments/metrics/cheers.rb +3 -0
- data/test/experiments/metrics/signups.rb +2 -0
- data/test/experiments/metrics/yawns.rb +3 -0
- data/test/experiments/null_abc.rb +5 -0
- data/test/metric/active_record_test.rb +277 -0
- data/test/metric/base_test.rb +293 -0
- data/test/metric/google_analytics_test.rb +104 -0
- data/test/metric/remote_test.rb +109 -0
- data/test/myapp/app/controllers/application_controller.rb +2 -0
- data/test/myapp/app/controllers/main_controller.rb +7 -0
- data/test/myapp/config/boot.rb +110 -0
- data/test/myapp/config/environment.rb +10 -0
- data/test/myapp/config/environments/production.rb +0 -0
- data/test/myapp/config/routes.rb +3 -0
- data/test/passenger_test.rb +43 -0
- data/test/playground_test.rb +26 -0
- data/test/rails_dashboard_test.rb +37 -0
- data/test/rails_helper_test.rb +36 -0
- data/test/rails_test.rb +389 -0
- data/test/test_helper.rb +145 -0
- data/vanity.gemspec +26 -0
- metadata +202 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
require "net/http"
|
2
|
+
require "cgi"
|
3
|
+
|
4
|
+
module Vanity
|
5
|
+
class Metric
|
6
|
+
|
7
|
+
# Specifies the base URL to use for a remote metric. For example:
|
8
|
+
# metric :sandbox do
|
9
|
+
# remote "http://api.vanitydash.com/metrics/sandbox"
|
10
|
+
# end
|
11
|
+
def remote(url = nil)
|
12
|
+
@remote_url = URI.parse(url) if url
|
13
|
+
@mutex ||= Mutex.new
|
14
|
+
extend Remote
|
15
|
+
@remote_url
|
16
|
+
end
|
17
|
+
|
18
|
+
# To update a remote metric, make a POST request to the metric URL with the
|
19
|
+
# content type "application/x-www-form-urlencoded" and the following
|
20
|
+
# fields:
|
21
|
+
# - The +metric+ identifier,
|
22
|
+
# - The +timestamp+ must be RFC 2616 formatted (in Ruby just call +httpdate+
|
23
|
+
# on the Time object),
|
24
|
+
# - The +identity+ (optional),
|
25
|
+
# - Pass consecutive values using the field +values[]+, or
|
26
|
+
# - Set values by their index using +values[0]+, +values[1]+, etc or
|
27
|
+
# - Set values by series name using +values[foo]+, +values[bar]+, etc.
|
28
|
+
module Remote
|
29
|
+
|
30
|
+
def track!(args = nil)
|
31
|
+
return unless @playground.collecting?
|
32
|
+
timestamp, identity, values = track_args(args)
|
33
|
+
params = ["metric=#{CGI.escape @id.to_s}", "timestamp=#{CGI.escape timestamp.httpdate}"]
|
34
|
+
params << "identity=#{CGI.escape identity.to_s}" if identity
|
35
|
+
params.concat values.map { |v| "values[]=#{v.to_i}" }
|
36
|
+
params << @remote_url.query if @remote_url.query
|
37
|
+
@mutex.synchronize do
|
38
|
+
@http ||= Net::HTTP.start(@remote_url.host, @remote_url.port)
|
39
|
+
@http.request Net::HTTP::Post.new(@remote_url.path, "Content-Type"=>"application/x-www-form-urlencoded"), params.join("&")
|
40
|
+
end
|
41
|
+
rescue Timeout::Error, StandardError
|
42
|
+
@playground.logger.error "Error sending data for metric #{name}: #{$!}"
|
43
|
+
@http = nil
|
44
|
+
ensure
|
45
|
+
call_hooks timestamp, identity, values
|
46
|
+
end
|
47
|
+
|
48
|
+
# "Don't worry, be crappy. Revolutionary means you ship and then test."
|
49
|
+
# -- Guy Kawazaki
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,396 @@
|
|
1
|
+
require "uri"
|
2
|
+
|
3
|
+
module Vanity
|
4
|
+
|
5
|
+
# Playground catalogs all your experiments, holds the Vanity configuration.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# Vanity.playground.logger = my_logger
|
9
|
+
# puts Vanity.playground.map(&:name)
|
10
|
+
class Playground
|
11
|
+
|
12
|
+
DEFAULTS = { :collecting => true, :load_path=>"experiments" }
|
13
|
+
DEFAULT_ADD_PARTICIPANT_PATH = '/vanity/add_participant'
|
14
|
+
|
15
|
+
# Created new Playground. Unless you need to, use the global
|
16
|
+
# Vanity.playground.
|
17
|
+
#
|
18
|
+
# First argument is connection specification (see #redis=), last argument is
|
19
|
+
# a set of options, both are optional. Supported options are:
|
20
|
+
# - connection -- Connection specification
|
21
|
+
# - namespace -- Namespace to use
|
22
|
+
# - load_path -- Path to load experiments/metrics from
|
23
|
+
# - logger -- Logger to use
|
24
|
+
def initialize(*args)
|
25
|
+
options = Hash === args.last ? args.pop : {}
|
26
|
+
# In the case of Rails, use the Rails logger and collect only for
|
27
|
+
# production environment by default.
|
28
|
+
defaults = options[:rails] ? DEFAULTS.merge(:collecting => ::Rails.env.production?, :logger => ::Rails.logger) : DEFAULTS
|
29
|
+
if config_file_exists?
|
30
|
+
env = ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
|
31
|
+
config = load_config_file[env]
|
32
|
+
if Hash === config
|
33
|
+
config = config.inject({}) { |h,kv| h[kv.first.to_sym] = kv.last ; h }
|
34
|
+
else
|
35
|
+
config = { :connection=>config }
|
36
|
+
end
|
37
|
+
else
|
38
|
+
config = {}
|
39
|
+
end
|
40
|
+
|
41
|
+
@options = defaults.merge(config).merge(options)
|
42
|
+
if @options[:host] == 'redis' && @options.values_at(:host, :port, :db).any?
|
43
|
+
warn "Deprecated: please specify Redis connection as URL (\"redis://host:port/db\")"
|
44
|
+
establish_connection :adapter=>"redis", :host=>@options[:host], :port=>@options[:port], :database=>@options[:db] || @options[:database]
|
45
|
+
elsif @options[:redis]
|
46
|
+
@adapter = RedisAdapter.new(:redis=>@options[:redis])
|
47
|
+
else
|
48
|
+
connection_spec = args.shift || @options[:connection]
|
49
|
+
if connection_spec
|
50
|
+
connection_spec = "redis://" + connection_spec unless connection_spec[/^\w+:/]
|
51
|
+
establish_connection connection_spec
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
warn "Deprecated: namespace option no longer supported directly" if @options[:namespace]
|
56
|
+
@load_path = @options[:load_path] || DEFAULTS[:load_path]
|
57
|
+
unless @logger = @options[:logger]
|
58
|
+
@logger = Logger.new(STDOUT)
|
59
|
+
@logger.level = Logger::ERROR
|
60
|
+
end
|
61
|
+
@loading = []
|
62
|
+
@use_js = false
|
63
|
+
self.add_participant_path = DEFAULT_ADD_PARTICIPANT_PATH
|
64
|
+
@collecting = !!@options[:collecting]
|
65
|
+
end
|
66
|
+
|
67
|
+
# Deprecated. Use redis.server instead.
|
68
|
+
attr_accessor :host, :port, :db, :password, :namespace
|
69
|
+
|
70
|
+
# Path to load experiment files from.
|
71
|
+
attr_accessor :load_path
|
72
|
+
|
73
|
+
# Logger.
|
74
|
+
attr_accessor :logger
|
75
|
+
|
76
|
+
# Path to the add_participant action, necessary if you have called use_js!
|
77
|
+
attr_accessor :add_participant_path
|
78
|
+
|
79
|
+
# Defines a new experiment. Generally, do not call this directly,
|
80
|
+
# use one of the definition methods (ab_test, measure, etc).
|
81
|
+
#
|
82
|
+
# @see Vanity::Experiment
|
83
|
+
def define(name, type, options = {}, &block)
|
84
|
+
warn "Deprecated: if you need this functionality let's make a better API"
|
85
|
+
id = name.to_s.downcase.gsub(/\W/, "_").to_sym
|
86
|
+
raise "Experiment #{id} already defined once" if experiments[id]
|
87
|
+
klass = Experiment.const_get(type.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase })
|
88
|
+
experiment = klass.new(self, id, name, options)
|
89
|
+
experiment.instance_eval &block
|
90
|
+
experiment.save
|
91
|
+
experiments[id] = experiment
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the experiment. You may not have guessed, but this method raises
|
95
|
+
# an exception if it cannot load the experiment's definition.
|
96
|
+
#
|
97
|
+
# @see Vanity::Experiment
|
98
|
+
def experiment(name)
|
99
|
+
id = name.to_s.downcase.gsub(/\W/, "_").to_sym
|
100
|
+
warn "Deprecated: pleae call experiment method with experiment identifier (a Ruby symbol)" unless id == name
|
101
|
+
experiments[id.to_sym] or raise NameError, "No experiment #{id}"
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
# -- Robot Detection --
|
106
|
+
|
107
|
+
# Call to indicate that participants should be added via js
|
108
|
+
# This helps keep robots from participating in the ab test
|
109
|
+
# and skewing results.
|
110
|
+
#
|
111
|
+
# If you use this, there are two more steps:
|
112
|
+
# - Set Vanity.playground.add_participant_path = '/path/to/vanity/action',
|
113
|
+
# this should point to the add_participant path that is added with
|
114
|
+
# Vanity::Rails::Dashboard, make sure that this action is available
|
115
|
+
# to all users
|
116
|
+
# - Add <%= vanity_js %> to any page that needs uses an ab_test. vanity_js
|
117
|
+
# needs to be included after your call to ab_test so that it knows which
|
118
|
+
# version of the experiment the participant is a member of. The helper
|
119
|
+
# will render nothing if the there are no ab_tests running on the current
|
120
|
+
# page, so adding vanity_js to the bottom of your layouts is a good
|
121
|
+
# option. Keep in mind that if you call use_js! and don't include
|
122
|
+
# vanity_js in your view no participants will be recorded.
|
123
|
+
def use_js!
|
124
|
+
@use_js = true
|
125
|
+
end
|
126
|
+
|
127
|
+
def using_js?
|
128
|
+
@use_js
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
# Returns hash of experiments (key is experiment id).
|
133
|
+
#
|
134
|
+
# @see Vanity::Experiment
|
135
|
+
def experiments
|
136
|
+
unless @experiments
|
137
|
+
@experiments = {}
|
138
|
+
@logger.info "Vanity: loading experiments from #{load_path}"
|
139
|
+
Dir[File.join(load_path, "*.rb")].each do |file|
|
140
|
+
experiment = Experiment::Base.load(self, @loading, file)
|
141
|
+
experiment.save
|
142
|
+
end
|
143
|
+
end
|
144
|
+
@experiments
|
145
|
+
end
|
146
|
+
|
147
|
+
# Reloads all metrics and experiments. Rails calls this for each request in
|
148
|
+
# development mode.
|
149
|
+
def reload!
|
150
|
+
@experiments = nil
|
151
|
+
@metrics = nil
|
152
|
+
load!
|
153
|
+
end
|
154
|
+
|
155
|
+
# Loads all metrics and experiments. Rails calls this during
|
156
|
+
# initialization.
|
157
|
+
def load!
|
158
|
+
experiments
|
159
|
+
metrics
|
160
|
+
end
|
161
|
+
|
162
|
+
# Returns a metric (raises NameError if no metric with that identifier).
|
163
|
+
#
|
164
|
+
# @see Vanity::Metric
|
165
|
+
# @since 1.1.0
|
166
|
+
def metric(id)
|
167
|
+
metrics[id.to_sym] or raise NameError, "No metric #{id}"
|
168
|
+
end
|
169
|
+
|
170
|
+
# True if collection data (metrics and experiments). You only want to
|
171
|
+
# collect data in production environment, everywhere else run with
|
172
|
+
# collection off.
|
173
|
+
#
|
174
|
+
# @since 1.4.0
|
175
|
+
def collecting?
|
176
|
+
@collecting
|
177
|
+
end
|
178
|
+
|
179
|
+
# Turns data collection on and off.
|
180
|
+
#
|
181
|
+
# @since 1.4.0
|
182
|
+
def collecting=(enabled)
|
183
|
+
@collecting = !!enabled
|
184
|
+
end
|
185
|
+
|
186
|
+
# Returns hash of metrics (key is metric id).
|
187
|
+
#
|
188
|
+
# @see Vanity::Metric
|
189
|
+
# @since 1.1.0
|
190
|
+
def metrics
|
191
|
+
unless @metrics
|
192
|
+
@metrics = {}
|
193
|
+
@logger.info "Vanity: loading metrics from #{load_path}/metrics"
|
194
|
+
Dir[File.join(load_path, "metrics/*.rb")].each do |file|
|
195
|
+
Metric.load self, @loading, file
|
196
|
+
end
|
197
|
+
if config_file_exists? && remote = load_config_file["metrics"]
|
198
|
+
remote.each do |id, url|
|
199
|
+
fail "Metric #{id} already defined in playground" if metrics[id.to_sym]
|
200
|
+
metric = Metric.new(self, id)
|
201
|
+
metric.remote url
|
202
|
+
metrics[id.to_sym] = metric
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
@metrics
|
207
|
+
end
|
208
|
+
|
209
|
+
# Tracks an action associated with a metric.
|
210
|
+
#
|
211
|
+
# @example
|
212
|
+
# Vanity.playground.track! :uploaded_video
|
213
|
+
#
|
214
|
+
# @since 1.1.0
|
215
|
+
def track!(id, count = 1)
|
216
|
+
metric(id).track! count
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
# -- Connection management --
|
221
|
+
|
222
|
+
# This is the preferred way to programmatically create a new connection (or
|
223
|
+
# switch to a new connection). If no connection was established, the
|
224
|
+
# playground will create a new one by calling this method with no arguments.
|
225
|
+
#
|
226
|
+
# With no argument, uses the connection specified in config/vanity.yml file
|
227
|
+
# for the current environment (RACK_ENV, RAILS_ENV or development). If there
|
228
|
+
# is no config/vanity.yml file, picks the configuration from
|
229
|
+
# config/redis.yml, or defaults to Redis on localhost, port 6379.
|
230
|
+
#
|
231
|
+
# If the argument is a symbol, uses the connection specified in
|
232
|
+
# config/vanity.yml for that environment. For example:
|
233
|
+
# Vanity.playground.establish_connection :production
|
234
|
+
#
|
235
|
+
# If the argument is a string, it is processed as a URL. For example:
|
236
|
+
# Vanity.playground.establish_connection "redis://redis.local/5"
|
237
|
+
#
|
238
|
+
# Otherwise, the argument is a hash and specifies the adapter name and any
|
239
|
+
# additional options understood by that adapter (as with config/vanity.yml).
|
240
|
+
# For example:
|
241
|
+
# Vanity.playground.establish_connection :adapter=>:redis,
|
242
|
+
# :host=>"redis.local"
|
243
|
+
#
|
244
|
+
# @since 1.4.0
|
245
|
+
def establish_connection(spec = nil)
|
246
|
+
@spec = spec
|
247
|
+
disconnect! if @adapter
|
248
|
+
case spec
|
249
|
+
when nil
|
250
|
+
if config_file_exists?
|
251
|
+
env = ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
|
252
|
+
spec = load_config_file[env]
|
253
|
+
fail "No configuration for #{env}" unless spec
|
254
|
+
establish_connection spec
|
255
|
+
elsif config_file_exists?("redis.yml")
|
256
|
+
env = ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
|
257
|
+
redis = load_config_file("redis.yml")[env]
|
258
|
+
fail "No configuration for #{env}" unless redis
|
259
|
+
establish_connection "redis://" + redis
|
260
|
+
else
|
261
|
+
establish_connection :adapter=>"redis"
|
262
|
+
end
|
263
|
+
when Symbol
|
264
|
+
spec = load_config_file[spec.to_s]
|
265
|
+
establish_connection spec
|
266
|
+
when String
|
267
|
+
uri = URI.parse(spec)
|
268
|
+
params = CGI.parse(uri.query) if uri.query
|
269
|
+
establish_connection :adapter=>uri.scheme, :username=>uri.user, :password=>uri.password,
|
270
|
+
:host=>uri.host, :port=>uri.port, :path=>uri.path, :params=>params
|
271
|
+
else
|
272
|
+
spec = spec.inject({}) { |hash,(k,v)| hash[k.to_sym] = v ; hash }
|
273
|
+
@adapter = Adapters.establish_connection(spec)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def config_file_root
|
278
|
+
(defined?(::Rails) ? ::Rails.root : Pathname.new(".")) + "config"
|
279
|
+
end
|
280
|
+
|
281
|
+
def config_file_exists?(basename = "vanity.yml")
|
282
|
+
File.exists?(config_file_root + basename)
|
283
|
+
end
|
284
|
+
|
285
|
+
def load_config_file(basename = "vanity.yml")
|
286
|
+
YAML.load(ERB.new(File.read(config_file_root + basename)).result)
|
287
|
+
end
|
288
|
+
|
289
|
+
# Returns the current connection. Establishes new connection is necessary.
|
290
|
+
#
|
291
|
+
# @since 1.4.0
|
292
|
+
def connection
|
293
|
+
@adapter || establish_connection
|
294
|
+
end
|
295
|
+
|
296
|
+
# Returns true if connection is open.
|
297
|
+
#
|
298
|
+
# @since 1.4.0
|
299
|
+
def connected?
|
300
|
+
@adapter && @adapter.active?
|
301
|
+
end
|
302
|
+
|
303
|
+
# Closes the current connection.
|
304
|
+
#
|
305
|
+
# @since 1.4.0
|
306
|
+
def disconnect!
|
307
|
+
@adapter.disconnect! if @adapter
|
308
|
+
end
|
309
|
+
|
310
|
+
# Closes the current connection and establishes a new one.
|
311
|
+
#
|
312
|
+
# @since 1.3.0
|
313
|
+
def reconnect!
|
314
|
+
establish_connection(@spec)
|
315
|
+
end
|
316
|
+
|
317
|
+
# Deprecated. Use Vanity.playground.collecting = true/false instead. Under
|
318
|
+
# Rails, collecting is true in production environment, false in all other
|
319
|
+
# environments, which is exactly what you want.
|
320
|
+
def test!
|
321
|
+
warn "Deprecated: use collecting = false instead"
|
322
|
+
self.collecting = false
|
323
|
+
end
|
324
|
+
|
325
|
+
# Deprecated. Use establish_connection or configuration file instead.
|
326
|
+
def redis=(spec_or_connection)
|
327
|
+
warn "Deprecated: use establish_connection method instead"
|
328
|
+
case spec_or_connection
|
329
|
+
when String
|
330
|
+
establish_connection "redis://" + spec_or_connection
|
331
|
+
when ::Redis
|
332
|
+
@connection = Adapters::RedisAdapter.new(spec_or_connection)
|
333
|
+
when :mock
|
334
|
+
establish_connection :adapter=>:mock
|
335
|
+
else
|
336
|
+
raise "I don't know what to do with #{spec_or_connection.inspect}"
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
def redis
|
341
|
+
warn "Deprecated: use connection method instead"
|
342
|
+
connection
|
343
|
+
end
|
344
|
+
|
345
|
+
end
|
346
|
+
|
347
|
+
# In the case of Rails, use the Rails logger and collect only for
|
348
|
+
# production environment by default.
|
349
|
+
class << self
|
350
|
+
|
351
|
+
# The playground instance.
|
352
|
+
#
|
353
|
+
# @see Vanity::Playground
|
354
|
+
attr_accessor :playground
|
355
|
+
def playground
|
356
|
+
# In the case of Rails, use the Rails logger and collect only for
|
357
|
+
# production environment by default.
|
358
|
+
@playground ||= Playground.new(:rails=>defined?(::Rails))
|
359
|
+
end
|
360
|
+
|
361
|
+
# Returns the Vanity context. For example, when using Rails this would be
|
362
|
+
# the current controller, which can be used to get/set the vanity identity.
|
363
|
+
def context
|
364
|
+
Thread.current[:vanity_context]
|
365
|
+
end
|
366
|
+
|
367
|
+
# Sets the Vanity context. For example, when using Rails this would be
|
368
|
+
# set by the set_vanity_context before filter (via Vanity::Rails#use_vanity).
|
369
|
+
def context=(context)
|
370
|
+
Thread.current[:vanity_context] = context
|
371
|
+
end
|
372
|
+
|
373
|
+
# Path to template.
|
374
|
+
def template(name)
|
375
|
+
path = File.join(File.dirname(__FILE__), "templates/#{name}")
|
376
|
+
path << ".erb" unless name["."]
|
377
|
+
path
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
|
383
|
+
class Object
|
384
|
+
|
385
|
+
# Use this method to access an experiment by name.
|
386
|
+
#
|
387
|
+
# @example
|
388
|
+
# puts experiment(:text_size).alternatives
|
389
|
+
#
|
390
|
+
# @see Vanity::Playground#experiment
|
391
|
+
# @deprecated
|
392
|
+
def experiment(name)
|
393
|
+
warn "Deprecated. Please call Vanity.playground.experiment directly."
|
394
|
+
Vanity.playground.experiment(name)
|
395
|
+
end
|
396
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<% score = experiment.score %>
|
2
|
+
<table>
|
3
|
+
<caption>
|
4
|
+
<%= experiment.conclusion(score).join(" ") %></caption>
|
5
|
+
<% score.alts.each do |alt| %>
|
6
|
+
<tr class="<%= "choice" if score.choice == alt %>">
|
7
|
+
<td class="option"><%= alt.name.gsub(/^o/, "O") %>:</td>
|
8
|
+
<td class="value"><code><%=vanity_h alt.value.to_s %></code></td>
|
9
|
+
<td class="value"><%= alt.participants %> participants</td>
|
10
|
+
<td class="value"><%= alt.converted %> converted</td>
|
11
|
+
<td>
|
12
|
+
<%= "%.1f%%" % [alt.conversion_rate * 100] %>
|
13
|
+
<%= "(%d%% better than %s)" % [alt.difference, score.least.name] if alt.difference && alt.difference >= 1 %>
|
14
|
+
</td>
|
15
|
+
<td class="action">
|
16
|
+
<% if experiment.active? && respond_to?(:url_for) %>
|
17
|
+
<% if experiment.showing?(alt) %>
|
18
|
+
showing
|
19
|
+
<% else %>
|
20
|
+
<a class="button chooses" title="Show me this alternative from now on" href="#"
|
21
|
+
data-id="<%= experiment.id %>" data-url="<%= url_for(:action=>:chooses, :e=>experiment.id, :a=>alt.id) %>">show</a>
|
22
|
+
<% end %>
|
23
|
+
<% end %>
|
24
|
+
</td>
|
25
|
+
</tr>
|
26
|
+
<% end %>
|
27
|
+
</table>
|
28
|
+
<%= %>
|