vanity 2.0.0.beta4 → 2.0.0.beta5

Sign up to get free protection for your applications and to get access to all the features.
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