vanity 2.0.1 → 2.1.0

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