vanity 2.0.0.beta4 → 2.0.0.beta5

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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +4 -0
  3. data/Appraisals +14 -0
  4. data/Gemfile +2 -2
  5. data/Gemfile.lock +5 -3
  6. data/README.rdoc +5 -1
  7. data/config/locales/vanity.en.yml +39 -0
  8. data/config/locales/vanity.pt-BR.yml +41 -0
  9. data/doc/ab_testing.textile +1 -1
  10. data/doc/rails.textile +13 -0
  11. data/gemfiles/rails32.gemfile +2 -2
  12. data/gemfiles/rails32.gemfile.lock +4 -3
  13. data/gemfiles/rails4.gemfile +2 -2
  14. data/gemfiles/rails4.gemfile.lock +4 -3
  15. data/gemfiles/rails41.gemfile +29 -0
  16. data/gemfiles/rails41.gemfile.lock +196 -0
  17. data/gemfiles/rails42.gemfile +29 -0
  18. data/gemfiles/rails42.gemfile.lock +235 -0
  19. data/lib/generators/templates/vanity_migration.rb +1 -1
  20. data/lib/generators/vanity/views_generator.rb +16 -0
  21. data/lib/vanity.rb +1 -0
  22. data/lib/vanity/experiment/ab_test.rb +15 -15
  23. data/lib/vanity/experiment/alternative.rb +2 -2
  24. data/lib/vanity/frameworks/rails.rb +16 -3
  25. data/lib/vanity/metric/active_record.rb +1 -1
  26. data/lib/vanity/playground.rb +19 -25
  27. data/lib/vanity/templates.rb +29 -0
  28. data/lib/vanity/templates/_ab_test.erb +12 -12
  29. data/lib/vanity/templates/_experiment.erb +4 -2
  30. data/lib/vanity/templates/_participant.erb +2 -2
  31. data/lib/vanity/templates/_report.erb +6 -6
  32. data/lib/vanity/templates/_vanity.js.erb +13 -15
  33. data/lib/vanity/templates/vanity.css +4 -4
  34. data/lib/vanity/version.rb +1 -1
  35. data/test/frameworks/rails/action_controller_test.rb +1 -1
  36. data/test/frameworks/rails/rails_test.rb +118 -102
  37. data/test/playground_test.rb +13 -0
  38. data/test/templates_test.rb +28 -0
  39. data/test/test_helper.rb +15 -17
  40. data/test/web/rails/dashboard_test.rb +25 -4
  41. data/vanity.gemspec +2 -0
  42. metadata +36 -18
@@ -0,0 +1,29 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rack"
6
+ gem "redis", ">= 2.1"
7
+ gem "redis-namespace", ">= 1.1.0"
8
+ gem "bson_ext"
9
+ gem "mongo"
10
+ gem "sqlite3"
11
+ gem "integration", "<= 0.1.0"
12
+ gem "rubystats"
13
+ gem "garb", "< 0.9.2"
14
+ gem "timecop", :require=>false
15
+ gem "webmock", :require=>false
16
+ gem "mocha", "~> 1.0", :require=>false
17
+ gem "rails", "4.2.0"
18
+ gem "fastthread", :git=>"git://github.com/zoltankiss/fastthread.git", :platforms=>:mri_20
19
+ gem "passenger", "~>3.0"
20
+
21
+ group :development do
22
+ gem "appraisal", ">= 1.0.0.beta2"
23
+ gem "jekyll"
24
+ gem "rake"
25
+ gem "RedCloth"
26
+ gem "yard"
27
+ end
28
+
29
+ gemspec :path=>".././"
@@ -0,0 +1,235 @@
1
+ GIT
2
+ remote: git://github.com/zoltankiss/fastthread.git
3
+ revision: cefbca3009b9c68df5e473d840462ebcc7fa1504
4
+ specs:
5
+ fastthread (1.0.7)
6
+
7
+ PATH
8
+ remote: ..
9
+ specs:
10
+ vanity (2.0.0.beta5)
11
+ i18n
12
+
13
+ GEM
14
+ remote: https://rubygems.org/
15
+ specs:
16
+ RedCloth (4.2.9)
17
+ actionmailer (4.2.0)
18
+ actionpack (= 4.2.0)
19
+ actionview (= 4.2.0)
20
+ activejob (= 4.2.0)
21
+ mail (~> 2.5, >= 2.5.4)
22
+ rails-dom-testing (~> 1.0, >= 1.0.5)
23
+ actionpack (4.2.0)
24
+ actionview (= 4.2.0)
25
+ activesupport (= 4.2.0)
26
+ rack (~> 1.6.0)
27
+ rack-test (~> 0.6.2)
28
+ rails-dom-testing (~> 1.0, >= 1.0.5)
29
+ rails-html-sanitizer (~> 1.0, >= 1.0.1)
30
+ actionview (4.2.0)
31
+ activesupport (= 4.2.0)
32
+ builder (~> 3.1)
33
+ erubis (~> 2.7.0)
34
+ rails-dom-testing (~> 1.0, >= 1.0.5)
35
+ rails-html-sanitizer (~> 1.0, >= 1.0.1)
36
+ activejob (4.2.0)
37
+ activesupport (= 4.2.0)
38
+ globalid (>= 0.3.0)
39
+ activemodel (4.2.0)
40
+ activesupport (= 4.2.0)
41
+ builder (~> 3.1)
42
+ activerecord (4.2.0)
43
+ activemodel (= 4.2.0)
44
+ activesupport (= 4.2.0)
45
+ arel (~> 6.0)
46
+ activesupport (4.2.0)
47
+ i18n (~> 0.7)
48
+ json (~> 1.7, >= 1.7.7)
49
+ minitest (~> 5.1)
50
+ thread_safe (~> 0.3, >= 0.3.4)
51
+ tzinfo (~> 1.1)
52
+ addressable (2.3.6)
53
+ appraisal (1.0.2)
54
+ bundler
55
+ rake
56
+ thor (>= 0.14.0)
57
+ arel (6.0.0)
58
+ blankslate (2.1.2.4)
59
+ bson (1.10.0)
60
+ bson_ext (1.10.0)
61
+ bson (~> 1.10.0)
62
+ builder (3.2.2)
63
+ celluloid (0.16.0)
64
+ timers (~> 4.0.0)
65
+ classifier-reborn (2.0.3)
66
+ fast-stemmer (~> 1.0)
67
+ coffee-script (2.3.0)
68
+ coffee-script-source
69
+ execjs
70
+ coffee-script-source (1.8.0)
71
+ colorator (0.1)
72
+ crack (0.4.2)
73
+ safe_yaml (~> 1.0.0)
74
+ daemon_controller (1.2.0)
75
+ erubis (2.7.0)
76
+ execjs (2.2.2)
77
+ fast-stemmer (1.0.2)
78
+ ffi (1.9.6)
79
+ garb (0.9.1)
80
+ activesupport (>= 2.2.0)
81
+ crack (>= 0.1.6)
82
+ globalid (0.3.0)
83
+ activesupport (>= 4.1.0)
84
+ hike (1.2.3)
85
+ hitimes (1.2.2)
86
+ i18n (0.7.0)
87
+ integration (0.1.0)
88
+ jekyll (2.5.3)
89
+ classifier-reborn (~> 2.0)
90
+ colorator (~> 0.1)
91
+ jekyll-coffeescript (~> 1.0)
92
+ jekyll-gist (~> 1.0)
93
+ jekyll-paginate (~> 1.0)
94
+ jekyll-sass-converter (~> 1.0)
95
+ jekyll-watch (~> 1.1)
96
+ kramdown (~> 1.3)
97
+ liquid (~> 2.6.1)
98
+ mercenary (~> 0.3.3)
99
+ pygments.rb (~> 0.6.0)
100
+ redcarpet (~> 3.1)
101
+ safe_yaml (~> 1.0)
102
+ toml (~> 0.1.0)
103
+ jekyll-coffeescript (1.0.1)
104
+ coffee-script (~> 2.2)
105
+ jekyll-gist (1.1.0)
106
+ jekyll-paginate (1.1.0)
107
+ jekyll-sass-converter (1.3.0)
108
+ sass (~> 3.2)
109
+ jekyll-watch (1.2.0)
110
+ listen (~> 2.7)
111
+ json (1.8.2)
112
+ kramdown (1.5.0)
113
+ liquid (2.6.1)
114
+ listen (2.8.5)
115
+ celluloid (>= 0.15.2)
116
+ rb-fsevent (>= 0.9.3)
117
+ rb-inotify (>= 0.9)
118
+ loofah (2.0.1)
119
+ nokogiri (>= 1.5.9)
120
+ mail (2.6.3)
121
+ mime-types (>= 1.16, < 3)
122
+ mercenary (0.3.5)
123
+ metaclass (0.0.4)
124
+ mime-types (2.4.3)
125
+ mini_portile (0.6.2)
126
+ minitest (5.5.1)
127
+ mocha (1.1.0)
128
+ metaclass (~> 0.0.1)
129
+ mongo (1.10.0)
130
+ bson (~> 1.10.0)
131
+ multi_json (1.10.1)
132
+ nokogiri (1.6.5)
133
+ mini_portile (~> 0.6.0)
134
+ parslet (1.5.0)
135
+ blankslate (~> 2.0)
136
+ passenger (3.0.21)
137
+ daemon_controller (>= 1.0.0)
138
+ fastthread (>= 1.0.1)
139
+ rack
140
+ rake (>= 0.8.1)
141
+ posix-spawn (0.3.9)
142
+ pygments.rb (0.6.0)
143
+ posix-spawn (~> 0.3.6)
144
+ yajl-ruby (~> 1.1.0)
145
+ rack (1.6.0)
146
+ rack-test (0.6.3)
147
+ rack (>= 1.0)
148
+ rails (4.2.0)
149
+ actionmailer (= 4.2.0)
150
+ actionpack (= 4.2.0)
151
+ actionview (= 4.2.0)
152
+ activejob (= 4.2.0)
153
+ activemodel (= 4.2.0)
154
+ activerecord (= 4.2.0)
155
+ activesupport (= 4.2.0)
156
+ bundler (>= 1.3.0, < 2.0)
157
+ railties (= 4.2.0)
158
+ sprockets-rails
159
+ rails-deprecated_sanitizer (1.0.3)
160
+ activesupport (>= 4.2.0.alpha)
161
+ rails-dom-testing (1.0.5)
162
+ activesupport (>= 4.2.0.beta, < 5.0)
163
+ nokogiri (~> 1.6.0)
164
+ rails-deprecated_sanitizer (>= 1.0.1)
165
+ rails-html-sanitizer (1.0.1)
166
+ loofah (~> 2.0)
167
+ railties (4.2.0)
168
+ actionpack (= 4.2.0)
169
+ activesupport (= 4.2.0)
170
+ rake (>= 0.8.7)
171
+ thor (>= 0.18.1, < 2.0)
172
+ rake (10.4.2)
173
+ rb-fsevent (0.9.4)
174
+ rb-inotify (0.9.5)
175
+ ffi (>= 0.5.0)
176
+ redcarpet (3.2.2)
177
+ redis (3.2.0)
178
+ redis-namespace (1.5.1)
179
+ redis (~> 3.0, >= 3.0.4)
180
+ rubystats (0.2.3)
181
+ safe_yaml (1.0.4)
182
+ sass (3.4.10)
183
+ sprockets (2.12.3)
184
+ hike (~> 1.2)
185
+ multi_json (~> 1.0)
186
+ rack (~> 1.0)
187
+ tilt (~> 1.1, != 1.3.0)
188
+ sprockets-rails (2.2.2)
189
+ actionpack (>= 3.0)
190
+ activesupport (>= 3.0)
191
+ sprockets (>= 2.8, < 4.0)
192
+ sqlite3 (1.3.10)
193
+ thor (0.19.1)
194
+ thread_safe (0.3.4)
195
+ tilt (1.4.1)
196
+ timecop (0.7.1)
197
+ timers (4.0.1)
198
+ hitimes
199
+ toml (0.1.2)
200
+ parslet (~> 1.5.0)
201
+ tzinfo (1.2.2)
202
+ thread_safe (~> 0.1)
203
+ webmock (1.20.4)
204
+ addressable (>= 2.3.6)
205
+ crack (>= 0.3.2)
206
+ yajl-ruby (1.1.0)
207
+ yard (0.8.7.6)
208
+
209
+ PLATFORMS
210
+ ruby
211
+
212
+ DEPENDENCIES
213
+ RedCloth
214
+ appraisal (>= 1.0.0.beta2)
215
+ bson_ext
216
+ bundler (>= 1.0.0)
217
+ fastthread!
218
+ garb (< 0.9.2)
219
+ integration (<= 0.1.0)
220
+ jekyll
221
+ minitest (>= 4.2)
222
+ mocha (~> 1.0)
223
+ mongo
224
+ passenger (~> 3.0)
225
+ rack
226
+ rails (= 4.2.0)
227
+ rake
228
+ redis (>= 2.1)
229
+ redis-namespace (>= 1.1.0)
230
+ rubystats
231
+ sqlite3
232
+ timecop
233
+ vanity!
234
+ webmock
235
+ yard
@@ -12,7 +12,7 @@ class VanityMigration < ActiveRecord::Migration
12
12
  t.integer :value
13
13
  t.string :date
14
14
  end
15
- add_index :vanity_metric_values, [:vanity_metric_id]
15
+ add_index :vanity_metric_values, [:vanity_metric_id, :date]
16
16
 
17
17
  create_table :vanity_experiments do |t|
18
18
  t.string :experiment_id
@@ -0,0 +1,16 @@
1
+ class Vanity::ViewsGenerator < Rails::Generators::Base
2
+ desc "Add copies of the vanity views to your app for customization"
3
+
4
+ def create_view_files
5
+ Vanity::ViewsGenerator.source_root(destination_directory)
6
+ directory source_directory, destination_directory
7
+ end
8
+
9
+ def destination_directory
10
+ File.join(Rails.root, 'app', 'views', 'vanity')
11
+ end
12
+
13
+ def source_directory
14
+ File.join(File.dirname(__FILE__), '..', '..', 'vanity', 'templates')
15
+ end
16
+ end
data/lib/vanity.rb CHANGED
@@ -29,6 +29,7 @@ require "vanity/adapters/abstract_adapter"
29
29
  require "vanity/adapters/mock_adapter"
30
30
  # Playground.
31
31
  require "vanity/playground"
32
+ require "vanity/templates"
32
33
  require "vanity/autoconnect"
33
34
  require "vanity/helpers"
34
35
  # Integration with various frameworks.
@@ -302,13 +302,13 @@ module Vanity
302
302
 
303
303
  # Use the result of #score or #bayes_bandit_score to derive a conclusion. Returns an
304
304
  # array of claims.
305
- def conclusion(score = score)
305
+ def conclusion(score = score())
306
306
  claims = []
307
307
  participants = score.alts.inject(0) { |t,alt| t + alt.participants }
308
- claims << case participants
309
- when 0 ; "There are no participants in this experiment yet."
310
- when 1 ; "There is one participant in this experiment."
311
- else ; "There are #{participants} participants in this experiment."
308
+ claims << if participants.zero?
309
+ I18n.t('vanity.no_participants')
310
+ else
311
+ I18n.t('vanity.experiment_participants', :count=>participants)
312
312
  end
313
313
  # only interested in sorted alternatives with conversion
314
314
  sorted = score.alts.select { |alt| alt.measure > 0.0 }.sort_by(&:measure).reverse
@@ -320,34 +320,34 @@ module Vanity
320
320
  best, second = sorted[0], sorted[1]
321
321
  if best.measure > second.measure
322
322
  diff = ((best.measure - second.measure) / second.measure * 100).round
323
- better = " (%d%% better than %s)" % [diff, second.name] if diff > 0
324
- claims << "The best choice is %s: it converted at %.1f%%%s." % [best.name, best.measure * 100, better]
323
+ better = I18n.t('vanity.better_alternative_than', :probability=>diff.to_i, :alternative=> second.name) if diff > 0
324
+ claims << I18n.t('vanity.best_alternative_measure', :best_alternative=>best.name, :measure=>'%.1f' % (best.measure * 100), :better_than=>better)
325
325
  if score.method == :bayes_bandit_score
326
326
  if best.probability >= 90
327
- claims << "With %d%% probability this result is the best." % score.best.probability
327
+ claims << I18n.t('vanity.best_alternative_probability', :probability=>score.best.probability.to_i)
328
328
  else
329
- claims << "This result does not have strong confidence behind it, suggest you continue this experiment."
329
+ claims << I18n.t('vanity.low_result_confidence')
330
330
  end
331
331
  else
332
332
  if best.probability >= 90
333
- claims << "With %d%% probability this result is statistically significant." % score.best.probability
333
+ claims << I18n.t('vanity.best_alternative_is_significant', :probability=>score.best.probability.to_i)
334
334
  else
335
- claims << "This result is not statistically significant, suggest you continue this experiment."
335
+ claims << I18n.t('vanity.result_isnt_significant')
336
336
  end
337
337
  end
338
338
  sorted.delete best
339
339
  end
340
340
  sorted.each do |alt|
341
341
  if alt.measure > 0.0
342
- claims << "%s converted at %.1f%%." % [alt.name.gsub(/^o/, "O"), alt.measure * 100]
342
+ claims << I18n.t('vanity.converted_percentage', :alternative=>alt.name.sub(/^\w/, &:upcase), :percentage=>'%.1f' % (alt.measure * 100))
343
343
  else
344
- claims << "%s did not convert." % alt.name.gsub(/^o/, "O")
344
+ claims << I18n.t('vanity.didnt_convert', :alternative=>alt.name.sub(/^\w/, &:upcase))
345
345
  end
346
346
  end
347
347
  else
348
- claims << "This experiment did not run long enough to find a clear winner."
348
+ claims << I18n.t('vanity.no_clear_winner')
349
349
  end
350
- claims << "#{score.choice.name.gsub(/^o/, "O")} selected as the best alternative." if score.choice
350
+ claims << I18n.t('vanity.selected_as_best', :alternative=>score.choice.name.sub(/^\w/, &:upcase)) if score.choice
351
351
  claims
352
352
  end
353
353
 
@@ -7,7 +7,7 @@ module Vanity
7
7
  def initialize(experiment, id, value) #, participants, converted, conversions)
8
8
  @experiment = experiment
9
9
  @id = id
10
- @name = "option #{(@id + 65).chr}"
10
+ @name = I18n.t('vanity.option_number', :char=>(@id + 65).chr.upcase)
11
11
  @value = value
12
12
  end
13
13
 
@@ -90,4 +90,4 @@ module Vanity
90
90
  end
91
91
  end
92
92
  end
93
- end
93
+ end
@@ -320,12 +320,25 @@ module Vanity
320
320
 
321
321
  # JS callback action used by vanity_js
322
322
  def add_participant
323
- if params[:e].nil? || params[:e].empty?
323
+ if params[:v].nil?
324
324
  render :status => 404, :nothing => true
325
325
  return
326
326
  end
327
- exp = Vanity.playground.experiment(params[:e].to_sym)
328
- exp.chooses(exp.alternatives[params[:a].to_i].value, request)
327
+
328
+ h = {}
329
+ params[:v].split(',').each do |pair|
330
+ exp_id, answer = pair.split('=')
331
+ exp = Vanity.playground.experiment(exp_id.to_s.to_sym) rescue nil
332
+ answer = answer.to_i
333
+
334
+ if !exp || !exp.alternatives[answer]
335
+ render :status => 404, :nothing => true
336
+ return
337
+ end
338
+ h[exp] = exp.alternatives[answer].value
339
+ end
340
+
341
+ h.each{ |e,a| e.chooses(a, request) }
329
342
  render :status => 200, :nothing => true
330
343
  end
331
344
  end
@@ -91,7 +91,7 @@ module Vanity
91
91
 
92
92
  def last_update_at
93
93
  # SELECT created_at FROM "skies" ORDER BY created_at DESC LIMIT 1
94
- record = @ar_scoped.find(:first, :order=>"#@ar_timestamp DESC", :limit=>1, :select=>@ar_timestamp)
94
+ record = @ar_scoped.order("#{@ar_timestamp} DESC").select(@ar_timestamp).first
95
95
  record && record.send(@ar_timestamp)
96
96
  end
97
97
 
@@ -41,6 +41,8 @@ module Vanity
41
41
  @options = defaults.merge(config).merge(options)
42
42
 
43
43
  @load_path = @options[:load_path] || DEFAULTS[:load_path]
44
+
45
+ I18n.load_path += locale_file_paths
44
46
  unless @logger = @options[:logger]
45
47
  @logger = Logger.new(STDOUT)
46
48
  @logger.level = Logger::ERROR
@@ -68,19 +70,10 @@ module Vanity
68
70
 
69
71
  attr_accessor :request_filter
70
72
 
71
- # Defines a new experiment. Generally, do not call this directly,
72
- # use one of the definition methods (ab_test, measure, etc).
73
- #
74
- # @see Vanity::Experiment
75
- def define(name, type, options = {}, &block)
76
- warn "Deprecated: if you need this functionality let's make a better API"
77
- id = name.to_s.downcase.gsub(/\W/, "_").to_sym
78
- raise "Experiment #{id} already defined once" if experiments[id]
79
- klass = Experiment.const_get(type.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase })
80
- experiment = klass.new(self, id, name, options)
81
- experiment.instance_eval &block
82
- experiment.save
83
- experiments[id] = experiment
73
+ # Path to custom templates (overriding those in the gem)
74
+ attr_writer :custom_templates_path
75
+ def custom_templates_path
76
+ @custom_templates_path ||= (File.expand_path(File.join(::Rails.root, 'app', 'views', 'vanity')) if defined?(::Rails))
84
77
  end
85
78
 
86
79
  # Returns the experiment. You may not have guessed, but this method raises
@@ -89,7 +82,7 @@ module Vanity
89
82
  # @see Vanity::Experiment
90
83
  def experiment(name)
91
84
  id = name.to_s.downcase.gsub(/\W/, "_").to_sym
92
- warn "Deprecated: pleae call experiment method with experiment identifier (a Ruby symbol)" unless id == name
85
+ warn "Deprecated: please call experiment method with experiment identifier (a Ruby symbol)" unless id == name
93
86
  experiments[id.to_sym] or raise NoExperimentError, "No experiment #{id}"
94
87
  end
95
88
 
@@ -227,13 +220,12 @@ module Vanity
227
220
  #
228
221
  # @see Vanity::Experiment
229
222
  def experiments
230
- unless @experiments
231
- @experiments = {}
232
- @logger.info "Vanity: loading experiments from #{load_path}"
233
- Dir[File.join(load_path, "*.rb")].each do |file|
234
- experiment = Experiment::Base.load(self, @loading, file)
235
- experiment.save
236
- end
223
+ return @experiments if @experiments
224
+
225
+ @experiments = {}
226
+ @logger.info "Vanity: loading experiments from #{load_path}"
227
+ Dir[File.join(load_path, "*.rb")].each do |file|
228
+ Experiment::Base.load(self, @loading, file)
237
229
  end
238
230
  @experiments
239
231
  end
@@ -384,6 +376,11 @@ module Vanity
384
376
  YAML.load(ERB.new(File.read(config_file_root + basename)).result)
385
377
  end
386
378
 
379
+ def locale_file_paths
380
+ locale_files_dir = File.expand_path('../../config/locales/', File.dirname(__FILE__))
381
+ Dir[locale_files_dir+'/*.{rb,yml}']
382
+ end
383
+
387
384
  # Returns the current connection. Establishes new connection is necessary.
388
385
  #
389
386
  # @since 1.4.0
@@ -456,9 +453,6 @@ module Vanity
456
453
  Thread.current[:vanity_context] = context
457
454
  end
458
455
 
459
- # Path to template.
460
- def template(name)
461
- File.join(File.dirname(__FILE__), "templates/#{name}")
462
- end
456
+
463
457
  end
464
458
  end