vanity 2.0.1 → 2.1.0

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -2
  3. data/Appraisals +6 -6
  4. data/CHANGELOG +9 -3
  5. data/Gemfile.lock +1 -1
  6. data/README.md +299 -0
  7. data/doc/configuring.textile +8 -1
  8. data/doc/identity.textile +2 -0
  9. data/doc/metrics.textile +10 -0
  10. data/gemfiles/rails32.gemfile.lock +1 -1
  11. data/gemfiles/rails41.gemfile.lock +1 -1
  12. data/gemfiles/rails42.gemfile.lock +1 -1
  13. data/gemfiles/{rails4.gemfile → rails42_protected_attributes.gemfile} +2 -2
  14. data/gemfiles/rails42_protected_attributes.gemfile.lock +209 -0
  15. data/lib/generators/templates/vanity_migration.rb +1 -0
  16. data/lib/vanity/adapters/abstract_adapter.rb +11 -0
  17. data/lib/vanity/adapters/active_record_adapter.rb +15 -1
  18. data/lib/vanity/adapters/mock_adapter.rb +14 -0
  19. data/lib/vanity/adapters/mongodb_adapter.rb +14 -0
  20. data/lib/vanity/adapters/redis_adapter.rb +15 -0
  21. data/lib/vanity/configuration.rb +43 -11
  22. data/lib/vanity/experiment/ab_test.rb +145 -15
  23. data/lib/vanity/experiment/alternative.rb +4 -0
  24. data/lib/vanity/frameworks/rails.rb +69 -31
  25. data/lib/vanity/locales/vanity.en.yml +9 -0
  26. data/lib/vanity/locales/vanity.pt-BR.yml +4 -0
  27. data/lib/vanity/metric/active_record.rb +9 -1
  28. data/lib/vanity/templates/_ab_test.erb +9 -2
  29. data/lib/vanity/templates/_experiment.erb +21 -1
  30. data/lib/vanity/templates/vanity.css +11 -3
  31. data/lib/vanity/templates/vanity.js +35 -6
  32. data/lib/vanity/version.rb +1 -1
  33. data/test/commands/report_test.rb +1 -0
  34. data/test/dummy/config/application.rb +1 -0
  35. data/test/experiment/ab_test.rb +414 -0
  36. data/test/experiment/base_test.rb +16 -10
  37. data/test/frameworks/rails/action_controller_test.rb +14 -6
  38. data/test/frameworks/rails/action_mailer_test.rb +8 -6
  39. data/test/frameworks/rails/action_view_test.rb +1 -0
  40. data/test/helper_test.rb +2 -0
  41. data/test/metric/active_record_test.rb +56 -0
  42. data/test/playground_test.rb +3 -0
  43. data/test/test_helper.rb +28 -2
  44. data/test/web/rails/dashboard_test.rb +2 -0
  45. data/vanity.gemspec +2 -2
  46. metadata +8 -8
  47. data/README.rdoc +0 -231
  48. data/gemfiles/rails4.gemfile.lock +0 -179
@@ -49,6 +49,8 @@ module Vanity
49
49
  @ar_timestamp, @ar_timestamp_table = @ar_timestamp.to_s.split('.').reverse
50
50
  @ar_timestamp_table ||= @ar_scoped.table_name
51
51
 
52
+ @ar_identity_block = options.delete(:identity)
53
+
52
54
  fail "Unrecognized options: #{options.keys * ", "}" unless options.empty?
53
55
 
54
56
  @ar_scoped.after_create(self)
@@ -99,7 +101,13 @@ module Vanity
99
101
  def after_create(record)
100
102
  return unless @playground.collecting?
101
103
  count = @ar_column ? (record.send(@ar_column) || 0) : 1
102
- call_hooks record.send(@ar_timestamp), nil, [count] if count > 0 && @ar_scoped.exists?(record.id)
104
+
105
+ identity = Vanity.context.vanity_identity rescue nil
106
+ identity ||= if @ar_identity_block
107
+ @ar_identity_block.call(record)
108
+ end
109
+
110
+ call_hooks record.send(@ar_timestamp), identity, [count] if count > 0 && @ar_scoped.exists?(record.id)
103
111
  end
104
112
  end
105
113
  end
@@ -2,10 +2,17 @@
2
2
  <table>
3
3
  <caption>
4
4
  <%= experiment.conclusion(score).join(" ") %>
5
+ <% if experiment.active? && !experiment.enabled? %>
6
+ <div class='disabled_info'><%=I18n.t('vanity.experiment_has_been_disabled', name: experiment.default.name)%></div>
7
+ <% end %>
5
8
  </caption>
6
9
  <% score.alts.each do |alt| %>
7
10
  <tr class="<%= "choice" if score.choice == alt %>">
8
- <td class="option"><%= alt.name.gsub(/^o/, "O") %>:</td>
11
+ <td class="option"><%= alt.name.gsub(/^o/, "O") %>:
12
+ <% if alt.default? %>
13
+ <div class='default'><%=I18n.t('vanity.default')%></div>
14
+ <% end %>
15
+ </td>
9
16
  <td class="value"><code><%=vanity_h alt.value.to_s %></code></td>
10
17
  <td class="value"><%= I18n.t 'vanity.participants', :count=>alt.participants %></td>
11
18
  <td class="value"><%= I18n.t 'vanity.converted', :count=>alt.converted %></td>
@@ -14,7 +21,7 @@
14
21
  <%= I18n.t('vanity.best_alternative', :probabilty=>alt.probability.to_i) if score.method == :bayes_score %>
15
22
  <%= I18n.t('vanity.better_alternative_than', :probability=>alt.difference.to_i, :alternative=>score.least.name) if alt.difference && alt.difference >= 1 %>
16
23
  </td>
17
- <% if experiment.active? && respond_to?(:url_for) %>
24
+ <% if experiment.enabled? && experiment.active? && respond_to?(:url_for) %>
18
25
  <td class="action">
19
26
  <small>
20
27
  <% if experiment.showing?(alt) %>
@@ -1,4 +1,24 @@
1
- <h3><%=vanity_h experiment.name %> <span class="type">(<%= experiment.class.friendly_name %>)</span></h3>
1
+ <% if experiment.active? %>
2
+ <% status = experiment.enabled? ? 'status_enabled' : 'status_disabled' %>
3
+ <% else %>
4
+ <% status = 'status_completed' %>
5
+ <% end %>
6
+ <div class="inner <%= status %>">
7
+ <h3>
8
+ <%=vanity_h experiment.name %><span class="type">(<%= experiment.class.friendly_name %>)</span>
9
+ <% if experiment.type == 'ab_test' && experiment.active? && experiment.playground.collecting? %>
10
+ <span class='enabled-links'>
11
+ <% action = experiment.enabled? ? :disable : :enable %>
12
+
13
+ <% if experiment.enabled? %> <%= I18n.t( 'vanity.enabled' ) %> | <% end %>
14
+ <a title='<%=I18n.t( action, scope: 'vanity.act_on_this_experiment' )%>' href='#'
15
+ data-id='<%= experiment.id %>' data-url='<%= url_for(:action=>action, :e => experiment.id) %>'>
16
+ <%= action %></a>
17
+ <% if !experiment.enabled? %> | <%= I18n.t( 'vanity.disabled' ) %> <% end %>
18
+
19
+ </span>
20
+ <% end %>
21
+ </h3>
2
22
  <%= experiment.description.to_s.split(/\n\s*\n/).map { |para| vanity_html_safe(%{<p class="description">#{vanity_h para}</p>}) }.join.html_safe %>
3
23
  <% if flash.notice %>
4
24
  <p>
@@ -7,14 +7,22 @@
7
7
  .vanity .experiments { list-style: none; margin: 0; padding: 0 }
8
8
  .vanity .experiment { margin: 1em 0 2em 0; border-bottom: 1px solid #ddd }
9
9
  .vanity .experiment .type { margin-left: .3em; color: #bbb; font-size: .8em; font-weight: normal }
10
+ .vanity .experiment .inner {padding: 1em}
11
+ .vanity .experiment .status_completed { background: #EEF }
12
+ .vanity .experiment .status_enabled { background: #EFE }
13
+ .vanity .experiment .status_disabled { background: #FEE }
14
+ .vanity .experiment .enabled-links { float: right; font-weight: normal }
10
15
 
11
16
  .vanity .ab_test table { border-collapse: collapse; table-layout: fixed; width: 100%; border-bottom: 1px solid #ccc; margin: 1em 0 0 0 }
12
17
  .vanity .ab_test td { padding: .5em; border-top: 1px solid #ccc; width: 2em; overflow: hidden }
13
18
  .vanity .ab_test .choice td { font-weight: bold; background: #f0f0f8 }
14
19
  .vanity .ab_test caption { caption-side: bottom; padding: .5em; background: transparent; text-align: left }
15
- .vanity .ab_test td.option { width: 5em; white-space: nowrap; }
16
- .vanity .ab_test td.value { width: 8em; }
17
- .vanity .ab_test td.action { width: 6em; text-align: right }
20
+ .vanity .ab_test caption .disabled_info { padding-top: .5em }
21
+ .vanity .ab_test td.option { width: 5em; white-space: nowrap; overflow: hidden }
22
+ .vanity .ab_test td.option .default { font-size: 75% }
23
+ .vanity .ab_test td.value { width: 8em; white-space: nowrap; overflow: hidden }
24
+ .vanity .ab_test td.action { width: 6em; overflow: hidden; text-align: right }
25
+ .vanity .ab_test button.reset { float: right }
18
26
 
19
27
  .vanity .metrics { list-style: none; margin: 0; padding: 0; border-bottom: 1px solid #ddd }
20
28
  .vanity .metric { margin: 2em 0 }
@@ -70,13 +70,42 @@ $(function() {
70
70
  });
71
71
  });
72
72
 
73
- $(".experiment.ab_test a.button.chooses").live("click", function() {
74
- var link = $(this);
75
- $.ajax({
76
- data: 'authenticity_token=' + encodeURIComponent(document.auth_token),
77
- success: function(request){ $('#experiment_' + link.attr("data-id")).html(request) },
78
- url: link.attr("data-url"), type: 'post'
73
+ function post_on_click(sel) {
74
+ $(sel).live("click", function() {
75
+ var link = $(this);
76
+ $.ajax({
77
+ data: 'authenticity_token=' + encodeURIComponent(document.auth_token),
78
+ success: function(request){ $('#experiment_' + link.attr("data-id")).html(request) },
79
+ url: link.attr("data-url"), type: 'post'
80
+ });
81
+ return false;
79
82
  });
83
+ }
84
+
85
+ post_on_click(".experiment.ab_test a.button.chooses");
86
+ post_on_click(".experiment.ab_test .enabled-links a");
87
+
88
+ $(".experiment button.reset").live("click", function() {
89
+ if (confirm('Are you sure you want to reset the experiment? This will clear all collected data so far and restart the experiment from scratch. This cannot be undone.')){
90
+ var link = $(this);
91
+ $.ajax({
92
+ data: 'authenticity_token=' + encodeURIComponent(document.auth_token),
93
+ success: function(request){ $('#experiment_' + link.attr("data-id")).html(request) },
94
+ url: link.attr("data-url"), type: 'post'
95
+ });
96
+ }
97
+ return false;
98
+ });
99
+
100
+ $(".experiment button.finish").live("click", function() {
101
+ var link = $(this);
102
+ if (confirm('Are you sure you want to complete the experiment and set ' + link.attr("alt-name") + ' as the outcome?')){
103
+ $.ajax({
104
+ data: 'authenticity_token=' + encodeURIComponent(document.auth_token),
105
+ success: function(request){ $('#experiment_' + link.attr("data-id")).html(request) },
106
+ url: link.attr("data-url"), type: 'post'
107
+ });
108
+ }
80
109
  return false;
81
110
  });
82
111
 
@@ -1,5 +1,5 @@
1
1
  module Vanity
2
- VERSION = "2.0.1"
2
+ VERSION = "2.1.0"
3
3
 
4
4
  module Version
5
5
  version = VERSION.to_s.split(".").map { |i| i.to_i }
@@ -15,6 +15,7 @@ describe Vanity::Commands do
15
15
  alternatives "foo", "bar"
16
16
  identify { "me" }
17
17
  metrics :coolness
18
+ default "foo"
18
19
  end
19
20
  experiment(:foobar).choose
20
21
 
@@ -40,6 +40,7 @@ module Dummy
40
40
  # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
41
41
  # parameters by using an attr_accessible or attr_protected declaration.
42
42
  config.active_record.whitelist_attributes = true if ActiveRecord::Base.respond_to?(:whitelist_attributes=)
43
+ config.active_record.mass_assignment_sanitizer = :strict if defined?(ProtectedAttributes)
43
44
 
44
45
  # Configure the default encoding used in templates for Ruby 1.9.
45
46
  config.encoding = "utf-8"