metry 2.0.5 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +12 -6
  3. data/Rakefile +5 -0
  4. data/TODO +1 -6
  5. data/example/example.rb +1 -1
  6. data/features/app_tracking.feature +1 -2
  7. data/features/basic_tracking.feature +19 -38
  8. data/features/psycho/dashboard.feature +3 -11
  9. data/features/psycho/experiments.feature +28 -0
  10. data/features/psycho/visitor_tracking.feature +4 -4
  11. data/features/step_definitions/experiments.rb +8 -0
  12. data/features/step_definitions/psycho.rb +14 -0
  13. data/features/step_definitions/tracking.rb +56 -24
  14. data/features/support/env.rb +0 -1
  15. data/features/support/helpers.rb +6 -0
  16. data/lib/metry.rb +12 -5
  17. data/lib/metry/cohort.rb +19 -0
  18. data/lib/metry/event.rb +26 -0
  19. data/lib/metry/experiment.rb +36 -13
  20. data/lib/metry/goal.rb +18 -0
  21. data/lib/metry/psycho.rb +24 -93
  22. data/lib/metry/psycho/dashboard.erb +5 -5
  23. data/lib/metry/psycho/edit_goal.erb +1 -1
  24. data/lib/metry/psycho/experiment.erb +25 -0
  25. data/lib/metry/psycho/new_goal.erb +1 -1
  26. data/lib/metry/psycho/visitor.erb +4 -1
  27. data/lib/metry/psycho/visitors.erb +6 -0
  28. data/lib/metry/rack/tracking.rb +19 -18
  29. data/lib/metry/visitor.rb +30 -0
  30. data/radiant/example/features/metry.feature +21 -43
  31. data/radiant/example/features/step_definitions/tracking.rb +56 -24
  32. data/radiant/example/features/step_definitions/web.rb +0 -7
  33. data/radiant/example/features/support/env.rb +7 -3
  34. data/radiant/example/features/support/helpers.rb +6 -0
  35. data/radiant/extension/lib/metry_tags.rb +3 -1
  36. data/test/shared.rb +3 -1
  37. data/test/test_experiment.rb +104 -0
  38. metadata +45 -9
  39. data/features/psycho/goals.feature +0 -53
  40. data/features/step_definitions/goals.rb +0 -3
  41. data/lib/metry/psycho/goal.erb +0 -9
  42. data/lib/metry/storage.rb +0 -142
  43. data/radiant/example/features/step_definitions/experiments.rb +0 -12
  44. data/test/test_storage.rb +0 -25
@@ -29,7 +29,7 @@ Feature: Radiant support
29
29
  Scenario: Running an experiment
30
30
  Given a page at "/" containing:
31
31
  """
32
- <r:metry:experiment name="header" method="mod_visitor">
32
+ <r:metry:experiment name="header" method="sequential">
33
33
  Hello!
34
34
  <r:alternative name="grumpy">
35
35
  Go away!
@@ -41,35 +41,17 @@ Feature: Radiant support
41
41
  """
42
42
  When I view "/"
43
43
  Then I should see "Hello!"
44
+ And visitor #1 should have experiment "header" with "control"
44
45
  When I am a new visitor
45
46
  And there is an empty Radiant cache
46
47
  And I view "/"
47
48
  Then I should see "Go away!"
49
+ And visitor #2 should have experiment "header" with "grumpy"
48
50
  When I am a new visitor
49
51
  And there is an empty Radiant cache
50
52
  And I view "/"
51
53
  Then I should see "Duh?"
52
- Then there should be a tracking event "1":
53
- | key | value |
54
- | visitor | 1 |
55
- | experiment:header | control |
56
- Then there should be a tracking event "2":
57
- | key | value |
58
- | visitor | 2 |
59
- | experiment:header | grumpy |
60
- Then there should be a tracking event "3":
61
- | key | value |
62
- | visitor | 3 |
63
- | experiment:header | dopey |
64
- Then there should be a visitor "1":
65
- | key | value |
66
- | experiment:header | control |
67
- Then there should be a visitor "2":
68
- | key | value |
69
- | experiment:header | grumpy |
70
- Then there should be a visitor "3":
71
- | key | value |
72
- | experiment:header | dopey |
54
+ And visitor #3 should have experiment "header" with "dopey"
73
55
 
74
56
  Scenario: Multiple access by same visitor should give same result
75
57
  Given a page at "/" containing:
@@ -81,35 +63,35 @@ Feature: Radiant support
81
63
  </r:alternative>
82
64
  </r:metry:experiment>
83
65
  """
84
- When I view "/" 10 times
85
- Then I should see the same "switch" alternative 10 times
66
+ When I view "/"
67
+ Then I should see the same page 10 times
86
68
  When I am a new visitor
87
- And I view "/" 10 times
88
- Then I should see the same "switch" alternative 10 times
69
+ And I view "/"
70
+ Then I should see the same page 10 times
89
71
  When I am a new visitor
90
- And I view "/" 10 times
91
- Then I should see the same "switch" alternative 10 times
72
+ And I view "/"
73
+ Then I should see the same page 10 times
92
74
  When I am a new visitor
93
- And I view "/" 10 times
94
- Then I should see the same "switch" alternative 10 times
75
+ And I view "/"
76
+ Then I should see the same page 10 times
95
77
 
96
78
  Scenario: Caching shouldn't interfere with experiments
97
79
  Given a page at "/" containing:
98
80
  """
99
- <r:metry:experiment name="cached" method="mod_visitor">
81
+ <r:metry:experiment name="cached" method="sequential">
100
82
  Sweet
101
83
  <r:alternative name="two">
102
84
  Sour
103
85
  </r:alternative>
104
86
  </r:metry:experiment>
105
87
  """
106
- When I view "/" 2 times
107
- Then there should be a tracking event "1":
108
- | key | value |
109
- | experiment:cached | control |
110
- Then there should be a tracking event "2":
111
- | key | value |
112
- | experiment:cached | control |
88
+ When I view "/"
89
+ Then visitor #1 should have experiment "cached" with "control"
90
+ And I should see "Sweet"
91
+ When I am a new visitor
92
+ And I view "/"
93
+ Then visitor #2 should have experiment "cached" with "two"
94
+ And I should see "Sour"
113
95
 
114
96
  Scenario: rand method should give reasonable experiment distribution
115
97
  Given a page at "/" containing:
@@ -131,8 +113,4 @@ Feature: Radiant support
131
113
  </r:metry:experiment>
132
114
  """
133
115
  When 100 visitors view "/"
134
- Then at least 10 should see alternative "control" of experiment "lots"
135
- Then at least 10 should see alternative "alt1" of experiment "lots"
136
- Then at least 10 should see alternative "alt2" of experiment "lots"
137
- Then at least 10 should see alternative "alt3" of experiment "lots"
138
- Then at least 10 should see alternative "alt4" of experiment "lots"
116
+ Then there should be 5 alternatives each displayed at least 8 times
@@ -1,43 +1,75 @@
1
1
  Given /^an empty tracking database$/ do
2
- Metry.current.clear
2
+ Metry.clear
3
3
  end
4
4
 
5
5
  Then /^there should be (\d+) tracking events?$/ do |event_count|
6
- assert_equal(event_count.to_i, Metry.current.event_count)
6
+ assert_equal(event_count.to_i, Metry::Event.count)
7
7
  end
8
8
 
9
9
  Then /^there should be (\d+) visitors$/ do |visitor_count|
10
- assert_equal(visitor_count.to_i, Metry.current.visitor_count)
10
+ assert_equal(visitor_count.to_i, Metry::Visitor.count)
11
11
  end
12
12
 
13
- When /^there should be a tracking event "(\d+)":$/ do |id, table|
14
- event = Metry.current.event(id)
15
- assert event, "Unable to lookup event #{id}."
16
- table.hashes.each do |hash|
17
- expected = hash["value"]
13
+ Then /^tracking event #(\d+) should contain:$/ do |index, table|
14
+ event = at(Metry::Event, index)
15
+ assert event, "Unable to lookup event at #{index}."
16
+ table.rows_hash.each do |key, expected|
18
17
  case expected
19
18
  when "_exists_"
20
- assert event[hash["key"]], "Key #{hash["key"]} does not exist."
19
+ assert event.send(key), "Key #{key} does not exist."
21
20
  else
22
- assert_equal expected, event[hash["key"]], "Key #{hash["key"]} does not match."
21
+ assert_equal expected, event.send(key), "Key #{key} does not match."
23
22
  end
24
23
  end
25
24
  end
26
25
 
27
- Then /^there should be a visitor "([^\"]*)":$/ do |id, table|
28
- visitor = Metry.current.visitor(id)
29
- assert visitor, "Unable to lookup visitor #{id}."
30
- table.hashes.each do |hash|
31
- expected = hash["value"]
32
- case expected
33
- when "_exists_"
34
- assert visitor[hash["key"]], "Key #{hash["key"]} does not exist."
35
- else
36
- assert_equal expected, visitor[hash["key"]], "Key #{hash["key"]} does not match."
37
- end
26
+ Then /^tracking event #(\d+) extra should contain:$/ do |index, table|
27
+ event = at(Metry::Event, index)
28
+ table.rows_hash.each do |key, expected|
29
+ assert_equal expected, event.extra[key]
38
30
  end
39
31
  end
40
32
 
41
- Then /^there should be a visitor "([^\"]*)"$/ do |id|
42
- assert Metry.current.visitor(id)
43
- end
33
+ Then /^there should be a tracking event #(\d+) with visitor #(\d+)$/ do |event_index, visitor_index|
34
+ event = at(Metry::Event, event_index)
35
+ visitor = at(Metry::Visitor, visitor_index)
36
+ assert_equal event.visitor, visitor
37
+ end
38
+
39
+ Then /^there should be a visitor #(\d+)$/ do |index|
40
+ assert at(Metry::Visitor, index)
41
+ end
42
+
43
+ Then /^visitor #(\d+) should have experiment "([^\"]*)" with "([^\"]*)"$/ do |index, experiment, alternative|
44
+ visitor = at(Metry::Visitor, index)
45
+ experiment = Metry::Experiment.find(:first, :conditions => {:name => experiment})
46
+ assert experiment.cohort_for(visitor)
47
+ end
48
+
49
+ Then /^event #(\d+) should have experiment "([^\"]*)" with "([^\"]*)"$/ do |index, experiment, alternative|
50
+ event = at(Metry::Event, index)
51
+ experiment = Metry::Experiment.find(:first, :conditions => {:name => experiment})
52
+ assert_equal alternative, event.experiments[experiment.id]
53
+ end
54
+
55
+ Then /^I should see the same page (\d+) times$/ do |count|
56
+ expected_body = webrat.response_body
57
+ count.to_i.times do
58
+ When(%(I view "#{current_url}"))
59
+ assert_equal expected_body, webrat.response_body
60
+ end
61
+ end
62
+
63
+ When /^(\d+) visitors view "([^\"]*)"$/ do |count, path|
64
+ @alternatives = Hash.new(0)
65
+ count.to_i.times do
66
+ visit(path)
67
+ @alternatives[webrat.response_body] += 1
68
+ clear_cookies
69
+ end
70
+ end
71
+
72
+ Then /^there should be (\d+) alternatives each displayed at least (\d+) times$/ do |alternatives, minimum|
73
+ assert_equal alternatives.to_i, @alternatives.size
74
+ assert @alternatives.values.all?{|e| e >= minimum.to_i}
75
+ end
@@ -6,13 +6,6 @@ When /^I view "([^\"]*)"$/ do |path|
6
6
  visit(path)
7
7
  end
8
8
 
9
- When /^(\d+) visitors view "([^\"]*)"$/ do |count, path|
10
- count.to_i.times do
11
- visit(path)
12
- clear_cookies
13
- end
14
- end
15
-
16
9
  When /^I view "([^\"]*)" (\d+) times$/ do |path, count|
17
10
  count.to_i.times{visit(path)}
18
11
  end
@@ -17,6 +17,13 @@ World(Test::Unit::Assertions)
17
17
  require 'rack/test'
18
18
  World(Rack::Test::Methods)
19
19
 
20
+ module CustomRackTestSession
21
+ def rack_test_session
22
+ @_rack_test_session ||= Rack::Test::Session.new(app, "www.example.com")
23
+ end
24
+ end
25
+ World(CustomRackTestSession)
26
+
20
27
  $: << RAILS_ROOT + '/../../vendor/webrat/lib'
21
28
  require 'webrat'
22
29
  require 'webrat/rack_test'
@@ -25,9 +32,6 @@ Webrat.configure do |config|
25
32
  config.mode = :rack_test
26
33
  end
27
34
 
28
- require 'metry'
29
- Metry::Storage.predictable_keys = true
30
-
31
35
  def app
32
36
  ActionController::Dispatcher.new
33
37
  end
@@ -0,0 +1,6 @@
1
+ module CucumberHelpers
2
+ def at(model, index)
3
+ model.find(:all, :order => 'created_at')[index.to_i-1]
4
+ end
5
+ end
6
+ World(CucumberHelpers)
@@ -14,7 +14,9 @@ module MetryTags
14
14
  tag "metry:experiment" do |tag|
15
15
  control = tag.expand
16
16
  options = tag.locals.alternatives.merge("control" => control)
17
- Metry::Experiment.new(tag.attr["name"], tag.locals.event, tag.locals.visitor).choose(options, tag.attr["method"])
17
+ experiment = Metry::Experiment.find_or_create_by_name(tag.attr["name"])
18
+ alternative = experiment.choose(options.keys, tag.attr["method"], tag.locals.event, tag.locals.visitor)
19
+ options[alternative]
18
20
  end
19
21
 
20
22
  desc %{ Alternatives go in here with a name. }
@@ -3,4 +3,6 @@ $: << File.dirname(__FILE__) + '/../lib'
3
3
  require 'metry'
4
4
 
5
5
  require 'test/unit'
6
- require 'shoulda'
6
+ require 'shoulda'
7
+
8
+ Metry.init 'test'
@@ -0,0 +1,104 @@
1
+ require File.dirname(__FILE__) + '/shared'
2
+
3
+ class TestExperiment < Test::Unit::TestCase
4
+ context "Goldilocks experiment" do
5
+ setup do
6
+ Metry.clear
7
+ @experiment = Metry::Experiment.create(:name => "Goldilocks")
8
+ end
9
+
10
+ should "be able to create and find experiment" do
11
+ e = reload(@experiment)
12
+ assert_equal @experiment, e
13
+ assert_equal "Goldilocks", e.name
14
+ end
15
+
16
+ should "be able to find or create experiment" do
17
+ assert_equal @experiment, Metry::Experiment.find_or_create_by_name("Goldilocks")
18
+ assert_not_equal @experiment, Metry::Experiment.find_or_create_by_name("Three Little Pigs")
19
+ end
20
+
21
+ should "be able to lookup a cohort" do
22
+ assert_equal 0, @experiment.cohorts.count
23
+
24
+ c1 = @experiment.lookup_cohort("Daddy bear")
25
+ c2 = @experiment.lookup_cohort("Momma bear")
26
+
27
+ e = reload(@experiment)
28
+ assert_equal 2, e.cohorts.count
29
+ assert e.cohorts.include?(c1)
30
+ assert e.cohorts.include?(c2)
31
+
32
+ assert_equal c1, @experiment.lookup_cohort("Daddy bear")
33
+ assert_equal 2, e.cohorts.count
34
+ end
35
+
36
+ context "with existing cohorts" do
37
+ setup do
38
+ @daddy_cohort = @experiment.lookup_cohort("Daddy bear")
39
+ @momma_cohort = @experiment.lookup_cohort("Momma bear")
40
+ @baby_cohort = @experiment.lookup_cohort("Baby bear")
41
+ end
42
+
43
+ should "be able to associate visitors with a cohort" do
44
+ v1 = Metry::Visitor.create
45
+ v2 = Metry::Visitor.create
46
+
47
+ v1.in_cohort @daddy_cohort
48
+ v2.in_cohort @momma_cohort
49
+
50
+ daddy = reload(@daddy_cohort)
51
+ assert daddy.visitors.include?(v1)
52
+ assert v1.cohorts.include?(daddy)
53
+
54
+ momma = reload(@momma_cohort)
55
+ assert momma.visitors.include?(v2)
56
+ assert v2.cohorts.include?(momma)
57
+ end
58
+
59
+ should "be able to tell which cohort a visitor is in for a given experiment" do
60
+ v1 = Metry::Visitor.create
61
+ v1.in_cohort @daddy_cohort
62
+ assert_equal @daddy_cohort, @experiment.cohort_for(v1)
63
+ end
64
+
65
+ context "with visitors with events" do
66
+ setup do
67
+ @experiment.goals << (@goal = Metry::Goal.create(:name => "porridge", :path => "bowl/?"))
68
+ @v1 = Metry::Visitor.create; @v1.in_cohort @daddy_cohort
69
+ @v2 = Metry::Visitor.create; @v2.in_cohort @daddy_cohort
70
+ @v3 = Metry::Visitor.create; @v3.in_cohort @daddy_cohort
71
+ @v4 = Metry::Visitor.create; @v4.in_cohort @momma_cohort
72
+ Metry::Event.create(:path => "bowl", :visitor => @v1)
73
+ Metry::Event.create(:path => "bowl/", :visitor => @v2)
74
+ Metry::Event.create(:path => "bowl/", :visitor => @v2)
75
+ Metry::Event.create(:path => "sheet", :visitor => @v3)
76
+ Metry::Event.create(:path => "sheet", :visitor => @v4)
77
+ end
78
+
79
+ should "be able to find visitors that have reached a goal" do
80
+ assert_equal [@v1, @v2].sort_by{|e| e.id}, reload(@goal).visitors.sort_by{|e| e.id}
81
+ end
82
+
83
+ should "be able to calculate cohort/goal percentages" do
84
+ assert_equal((2.0/3.0), @daddy_cohort.reached_goal(@goal))
85
+ assert_equal 0, @momma_cohort.reached_goal(@goal)
86
+ end
87
+ end
88
+
89
+ end
90
+
91
+ should "be able to add goals" do
92
+ @experiment.goals << (g1 = Metry::Goal.create(:name => "porridge", :path => "bowl"))
93
+ @experiment.goals << (g2 = Metry::Goal.create(:name => "bed", :path => "sheet"))
94
+
95
+ ex = reload(@experiment)
96
+ assert ex.goals.include?(g1)
97
+ assert ex.goals.include?(g2)
98
+ end
99
+ end
100
+
101
+ def reload(model)
102
+ model.class.find(model.id)
103
+ end
104
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: metry
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.5
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathaniel Talbott
@@ -9,9 +9,39 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-08-09 00:00:00 -04:00
12
+ date: 2009-08-13 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: mongomapper
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: "0.3"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: mongodb-mongo
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: "0.10"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: mongodb-mongo_ext
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: "0.4"
44
+ version:
15
45
  - !ruby/object:Gem::Dependency
16
46
  name: hoe
17
47
  type: :development
@@ -45,24 +75,30 @@ files:
45
75
  - features/app_tracking.feature
46
76
  - features/basic_tracking.feature
47
77
  - features/psycho/dashboard.feature
48
- - features/psycho/goals.feature
78
+ - features/psycho/experiments.feature
49
79
  - features/psycho/visitor_tracking.feature
50
80
  - features/sample_application.feature
51
- - features/step_definitions/goals.rb
81
+ - features/step_definitions/experiments.rb
82
+ - features/step_definitions/psycho.rb
52
83
  - features/step_definitions/tracking.rb
53
84
  - features/step_definitions/web.rb
54
85
  - features/support/env.rb
86
+ - features/support/helpers.rb
55
87
  - lib/metry.rb
88
+ - lib/metry/cohort.rb
89
+ - lib/metry/event.rb
56
90
  - lib/metry/experiment.rb
91
+ - lib/metry/goal.rb
57
92
  - lib/metry/psycho.rb
58
93
  - lib/metry/psycho/dashboard.erb
59
94
  - lib/metry/psycho/edit_goal.erb
60
- - lib/metry/psycho/goal.erb
95
+ - lib/metry/psycho/experiment.erb
61
96
  - lib/metry/psycho/layout.erb
62
97
  - lib/metry/psycho/new_goal.erb
63
98
  - lib/metry/psycho/visitor.erb
99
+ - lib/metry/psycho/visitors.erb
64
100
  - lib/metry/rack/tracking.rb
65
- - lib/metry/storage.rb
101
+ - lib/metry/visitor.rb
66
102
  - radiant/example/CHANGELOG
67
103
  - radiant/example/CONTRIBUTORS
68
104
  - radiant/example/INSTALL
@@ -80,11 +116,11 @@ files:
80
116
  - radiant/example/db/schema.rb
81
117
  - radiant/example/features/metry.feature
82
118
  - radiant/example/features/psycho.feature
83
- - radiant/example/features/step_definitions/experiments.rb
84
119
  - radiant/example/features/step_definitions/radiant.rb
85
120
  - radiant/example/features/step_definitions/tracking.rb
86
121
  - radiant/example/features/step_definitions/web.rb
87
122
  - radiant/example/features/support/env.rb
123
+ - radiant/example/features/support/helpers.rb
88
124
  - radiant/example/public/.htaccess
89
125
  - radiant/example/public/404.html
90
126
  - radiant/example/public/500.html
@@ -151,7 +187,7 @@ files:
151
187
  - radiant/extension/lib/tasks/metry_extension_tasks.rake
152
188
  - radiant/extension/metry_extension.rb
153
189
  - test/shared.rb
154
- - test/test_storage.rb
190
+ - test/test_experiment.rb
155
191
  - vendor/webrat/.document
156
192
  - vendor/webrat/History.txt
157
193
  - vendor/webrat/MIT-LICENSE.txt
@@ -385,4 +421,4 @@ signing_key:
385
421
  specification_version: 2
386
422
  summary: Metry aims to close the gap between knowing you ought to be making decisions based on hard data about how visitors are using your site, and actually doing it
387
423
  test_files:
388
- - test/test_storage.rb
424
+ - test/test_experiment.rb