i_wonder 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/README.rdoc +9 -97
  2. data/app/assets/javascripts/i_wonder/ab_tests.js +2 -0
  3. data/app/assets/javascripts/i_wonder/application.js +15 -0
  4. data/app/assets/stylesheets/i_wonder/ab_tests.css.scss +22 -0
  5. data/app/assets/stylesheets/i_wonder/application.css.scss +1 -0
  6. data/app/controllers/i_wonder/ab_tests_controller.rb +48 -0
  7. data/app/controllers/i_wonder/application_controller.rb +4 -4
  8. data/app/controllers/i_wonder/metrics_controller.rb +33 -34
  9. data/app/helpers/i_wonder/ab_tests_helper.rb +4 -0
  10. data/app/models/i_wonder/ab_test.rb +101 -0
  11. data/app/models/i_wonder/ab_test_goal.rb +49 -0
  12. data/app/models/i_wonder/event.rb +5 -2
  13. data/app/models/i_wonder/test_group_membership.rb +12 -0
  14. data/app/views/i_wonder/ab_tests/_ab_test_goal_row.html.erb +23 -0
  15. data/app/views/i_wonder/ab_tests/_ab_test_goals.html.erb +13 -0
  16. data/app/views/i_wonder/ab_tests/_ab_test_group_row.html.erb +5 -0
  17. data/app/views/i_wonder/ab_tests/_ab_test_groups.html.erb +9 -0
  18. data/app/views/i_wonder/ab_tests/_form.html.erb +50 -0
  19. data/app/views/i_wonder/ab_tests/_show_not_started.html.erb +18 -0
  20. data/app/views/i_wonder/ab_tests/_show_report.html.erb +26 -0
  21. data/app/views/i_wonder/ab_tests/edit.html.erb +7 -0
  22. data/app/views/i_wonder/ab_tests/index.html.erb +20 -0
  23. data/app/views/i_wonder/ab_tests/new.html.erb +7 -0
  24. data/app/views/i_wonder/ab_tests/show.html.erb +20 -0
  25. data/app/views/i_wonder/reports/_form.html.erb +9 -5
  26. data/app/views/layouts/i_wonder.html.erb +1 -0
  27. data/config/routes.rb +1 -0
  28. data/db/migrate/20111022230720_i_wonder_migrations.rb +35 -26
  29. data/lib/i_wonder/ab_testing/action_controller_mixins.rb +20 -0
  30. data/lib/i_wonder/engine.rb +2 -0
  31. data/lib/i_wonder/logging/{mixins/action_controller_mixins.rb → action_controller_mixins.rb} +3 -3
  32. data/lib/i_wonder/logging/{mixins/active_record_mixins.rb → active_record_mixins.rb} +0 -0
  33. data/lib/i_wonder/version.rb +1 -1
  34. data/lib/i_wonder.rb +7 -3
  35. data/test/dummy/app/controllers/application_controller.rb +7 -7
  36. data/test/dummy/db/schema.rb +33 -0
  37. data/test/dummy/log/development.log +16012 -0
  38. data/test/dummy/log/test.log +30206 -0
  39. data/test/dummy/tmp/cache/assets/C7B/1F0/sprockets%2F69ce38b6f0a103527048612c29753c1f +0 -0
  40. data/test/dummy/tmp/cache/assets/C81/600/sprockets%2F30b4f9798169b0d0157b48c09ea23472 +0 -0
  41. data/test/dummy/tmp/cache/assets/CB1/1C0/sprockets%2F0677230b357317e47dffaff9013e0377 +0 -0
  42. data/test/dummy/tmp/cache/assets/CE6/720/sprockets%2F9718b522ff20c58a59b179b7759ca20b +0 -0
  43. data/test/dummy/tmp/cache/assets/D20/120/sprockets%2F73fcb7f2f97642793feb2c3d871c1482 +0 -0
  44. data/test/dummy/tmp/cache/assets/D2F/9D0/sprockets%2F50ce229bd4200bed47618724bcab6c73 +0 -0
  45. data/test/dummy/tmp/cache/assets/D37/100/sprockets%2F4e900e2ccfdda5075214028c89e25f0e +0 -0
  46. data/test/dummy/tmp/cache/assets/D79/600/sprockets%2Fc2a1e7e47cead78810554cc9c0de9388 +0 -0
  47. data/test/dummy/tmp/cache/assets/D84/210/sprockets%2Fabd0103ccec2b428ac62c94e4c40b384 +0 -0
  48. data/test/dummy/tmp/cache/assets/D8E/250/sprockets%2Fefe57e932e1d26c3b6af42a5311f0a1c +0 -0
  49. data/test/dummy/tmp/cache/assets/DA7/BA0/sprockets%2F463a9dd811f2418d96fa72cfe05fb8cd +0 -0
  50. data/test/dummy/tmp/cache/assets/DAD/040/sprockets%2Fc59fd78ac2642a5f3c1b6ef899d50cf3 +0 -0
  51. data/test/dummy/tmp/cache/assets/DD1/B10/sprockets%2F85b9bce1c5bc3fb86a83d8c8c34e05e0 +0 -0
  52. data/test/dummy/tmp/cache/assets/DDF/BA0/sprockets%2F187ad995e2ff3588c339fdbecceba119 +0 -0
  53. data/test/dummy/tmp/cache/assets/DF9/5C0/sprockets%2Fd711a81a91fcc3cd1a30798dd94fdcbd +0 -0
  54. data/test/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
  55. data/test/dummy/tmp/pids/server.pid +1 -0
  56. data/test/functional/ab_controller_mixins_test.rb +80 -0
  57. data/test/functional/{test_controller_test.rb → logging_controller_mixins_test.rb} +4 -3
  58. data/test/integration/i_wonder/dashboard_controller_test.rb +10 -3
  59. data/test/integration/middleware_test.rb +4 -0
  60. data/test/unit/helpers/i_wonder/ab_tests_helper_test.rb +6 -0
  61. data/test/unit/i_wonder/ab_test_test.rb +37 -0
  62. data/test/unit/i_wonder/event_test.rb +5 -3
  63. data/test/unit/i_wonder/test_group_membership_test.rb +9 -0
  64. metadata +65 -24
data/README.rdoc CHANGED
@@ -2,114 +2,26 @@
2
2
 
3
3
  Flexible Analytics, and Testing for every corner of your rails application. I read "The Lean Startup" and got inspired. There were a few good solutions, but none of them quite did what I was looking for. I highly recommend you check out Vanity (http://vanity.labnotes.org/) as it is a drop-in solution for many rails apps.
4
4
 
5
- Key Differences of IWonder:
6
-
7
- - Events get tracked by session, as well as an optional user and/or account. This allows the tracking of a users history.
8
- - Tests can be performed against a session (non-user), logged in user, or an entire account. For account based apps, having different versions for different users can be bad.
9
- - Data is collected periodically. Lots of useful scenerios for this. (info get's deleted, overwritten, etc)
10
- - Different reports (and some metrics) can be setup and changed without having to modify your code.
11
- - Different reports can be generated without coding knowledge.
12
- - Each hit and visitor comes with a referrer and IP code. Because that events will be transferred to the user when they signup, you can see which campaigns are resulting in actual signups and paid conversions.
13
- - Because the session events get added to the user when they log in, you avoid false positives on marketing analytics from visitors returning on different devices. If your application has a mobile interface, this basically avoids all your users being logged as visitors twice.
14
-
15
- More big plans!
16
-
17
- - Long term campaign tracking interface (the events already get logged, but this will make it easier to see results)
18
- - E-mail open tracking (for application generated e-mails like the 'Welcome E-mail')
19
- - Feature request buttons. - details coming soon
20
-
21
- == Requirements
22
-
23
- * Due to performance optimization IWonder must be used with an SQL style DB currently.
24
- * Rails 3.1 or higher
25
- * Ability to run hourly rake tasks (rake cron)
26
- * Ability to run a background worker process (delayed_job)
5
+ More information can be found at https://github.com/forrest/i_wonder/wiki
27
6
 
28
7
  == Installation
29
8
 
30
- === Install the Gem
9
+ You can install IWonder with almost no change to your code in 10-15 minutes. Check it out at http://github.com/forrest/i_wonder/wiki/Installation
31
10
 
32
- Add to your <code>Gemfile</code>:
33
-
34
- gem "i_wonder"
11
+ == Usage
35
12
 
36
- and install:
13
+ Usage instructions can be found at https://github.com/forrest/i_wonder/wiki/Usage
37
14
 
38
- bundle install
39
15
 
40
- === Migrate the DB
41
-
42
- Generate the migration files and migrate your database:
43
-
44
- rake i_wonder:install:migrations
45
- rake db:migrate
46
-
47
-
48
- === Create the dashboard controller
49
-
50
- Add the engine to your routes. This is where the dashboard will be hosted
51
-
52
- Rails.application.routes.draw do
53
- ...
54
- mount IWonder::Engine => "/super_analytics_engine"
55
- end
56
-
57
- === Setting up the hourly cron task
58
-
59
- This gem hooks into the "<code>rake cron</code>" request. It can't take snapshots of your metrics any faster than this cron is run. We recommend every hour.
60
- This will be run automatically if you are hosted on Heroku and have the 'hourly cron' add-on enabled.
61
-
62
-
63
- === Install Dependecies
64
-
65
- This Gem requires the Delayed::Job gem. Installation and configuration instructions can be found at http://github.com/tobi/delayed_job.
66
-
67
- We also require the highcharts javascript file in your applications asset path. We don't include it because of licencing reasons. It's free for testing and development. You can find it at http://www.highcharts.com/
68
-
69
-
70
- === Security
71
-
72
- IWonder's controllers will run any <code>:before_filters</code> you have set in your application controller. You can easily check for a password or admin status there.
73
-
74
- === Options & Configurations
75
-
76
- You can choose to navigate directly to the dashboard. If you want to include a link on your admin page to the i_wonder dashboard, your can access the url with <code>i_wonder.root_url</code> such as:
77
-
78
- <%= link_to "Go to analytics >", i_wonder.root_url %>
79
-
80
-
81
- If you want to configure this gem, add this to an initializer.
82
-
83
- IWonder.configure do |c|
84
- c.controllers_to_ignore = [] # These are all the controllers which don't log any events (i_wonder is also ignored)
85
- c.only_log_hits_on_200 = true # By default hits and new visitors won't be logged on anything but a 200 or 304 (not modified). Your custom events will still be recorded.
86
- c.back_to_app_link = "/" # this is the link hitting the home button in IWonder will take you to.
87
- c.app_name = "My App"
88
- end
89
-
90
-
91
- == Usage
92
-
93
- === Event tracking
94
-
95
- ==== Setting session, user, and account details
16
+ == Contributing & Reporting Issues
96
17
 
97
- This gem will track the visitor with a permanent cookie (will remember them next visit). However, it can also track users and accounts. Simply call one or both of these methods anywhere in the controller. When a user get's added part way through, the :user_id will get filled in for all the previous events for that session. The :new_visitor event for the session will also be removed if the user has an earlier dated :new_visitor event on their account.
18
+ Report a bug at http://github.com/forrest/i_wonder/issues
98
19
 
99
- i_wonder_for_user_id(user_id)
20
+ Request a feature at http://github.com/forrest/i_wonder/issues
100
21
 
101
- or
102
-
103
- i_wonder_for_account_id(account_id)
104
-
105
- :hits and :new_visitors will be logged automatically. You can store any additional by calling <code>report!(event_sym, options = {})</code> in the controller.
106
- You can also log an event from a model with the same method <code>report!(event_sym, options = {})</code>, but you will need to provide the <code>:user_id</code>
107
- or <code>:account_id</code> in the options, if you want them recorded.
108
-
109
-
110
- == Contributing & Reporting Issues
22
+ Contribute code by picking something off the feature request list at http://github.com/forrest/i_wonder/issues
111
23
 
112
- In progress...
24
+ Contribute time by helping with some documentation at http://github.com/forrest/i_wonder/wiki
113
25
 
114
26
 
115
27
  == Licensing
@@ -0,0 +1,2 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
@@ -8,3 +8,18 @@
8
8
  //= require jquery-ui
9
9
  //= require jquery_ujs
10
10
  //= require_tree .
11
+
12
+
13
+
14
+ function nested_delete(raw_this){
15
+ var row = $(raw_this).closest('.deletable_row');
16
+ row.hide();
17
+ row.find(".hidden_destroy").val("true");
18
+ }
19
+
20
+ //this is called when a new row is being inserted from a partial.
21
+ //It will replace the "new_row_id" with the current timestamp.
22
+ function new_row_inserter(html){
23
+ var timestamp = new Date().getTime();
24
+ return html.replace(/new_row_id/g, timestamp)
25
+ }
@@ -0,0 +1,22 @@
1
+ @import "_shared.css.scss";
2
+
3
+ .main_form.ab_test { min-width:400px;
4
+
5
+ #new_goal, #new_group {margin:10px 0; float:right;}
6
+
7
+ #ab_test_goals, #ab_test_groups { list-style:decimal; padding:0 0 0 25px; margin:0;
8
+ li { padding:0px; clear:both;
9
+ .options {display:inline-block; margin-left:10px;}
10
+ .delete {float:right;}
11
+ }
12
+ }
13
+ }
14
+
15
+ table#ab_test_summary { background-color:$light_blue_box_background; padding:0px; margin:0px; border-style:solid; border-width:1px 1px 0 0; border-color:$light_blue_box_border;
16
+ thead {
17
+ td {background-color:#ddd;}
18
+ }
19
+ tr {
20
+ td {padding:5px; border-style:solid; border-width:0 0 1px 1px; border-color:$light_blue_box_border; text-align:center;}
21
+ }
22
+ }
@@ -7,6 +7,7 @@
7
7
  *= require i_wonder/events
8
8
  *= require i_wonder/reports
9
9
  *= require i_wonder/metrics
10
+ *= require i_wonder/ab_tests
10
11
  */
11
12
 
12
13
  @import "_shared.css.scss";
@@ -0,0 +1,48 @@
1
+ module IWonder
2
+ class AbTestsController < ApplicationController
3
+ layout "i_wonder"
4
+
5
+ def index
6
+ @ab_tests = AbTest.all
7
+ end
8
+
9
+ def show
10
+ @ab_test = AbTest.find(params[:id])
11
+ end
12
+
13
+ def new
14
+ @ab_test = AbTest.new(params[:ab_test])
15
+ end
16
+
17
+ def create
18
+ @ab_test = AbTest.new(params[:ab_test])
19
+
20
+ if @ab_test.save
21
+ redirect_to @ab_test, :notice => "Successfully created ABTest"
22
+ else
23
+ render "new"
24
+ end
25
+ end
26
+
27
+ def edit
28
+ @ab_test = AbTest.find(params[:id])
29
+ render "edit"
30
+ end
31
+
32
+ def update
33
+ @ab_test = AbTest.find(params[:id])
34
+
35
+ if @ab_test.update_attributes(params[:ab_test])
36
+ redirect_to @ab_test, :notice => "Successfully updated ABTest"
37
+ else
38
+ render "edit"
39
+ end
40
+ end
41
+
42
+ def destroy
43
+ @ab_test = AbTest.find(params[:id])
44
+ @ab_test.destroy
45
+ redirect_to ab_tests_path, :notice => "ABTest has been destroyed"
46
+ end
47
+ end
48
+ end
@@ -1,4 +1,4 @@
1
- module IWonder
2
- class ApplicationController < ActionController::Base
3
- end
4
- end
1
+ # module IWonder
2
+ # class ApplicationController < ActionController::Base
3
+ # end
4
+ # end
@@ -1,49 +1,48 @@
1
1
  module IWonder
2
2
  class MetricsController < ApplicationController
3
3
  layout "i_wonder"
4
-
5
- def index
6
- @metrics = Metric.all
7
- end
8
4
 
9
- def show
10
- @metric = Metric.find(params[:id])
11
- end
5
+ def index
6
+ @metrics = Metric.all
7
+ end
12
8
 
13
- def new
14
- @metric = Metric.new(params[:metric])
15
- end
9
+ def show
10
+ @metric = Metric.find(params[:id])
11
+ end
16
12
 
17
- def create
18
- @metric = Metric.new(params[:metric])
13
+ def new
14
+ @metric = Metric.new(params[:metric])
15
+ end
19
16
 
20
- if @metric.save
21
- redirect_to @metric, :notice => "Successfully created metric"
22
- else
23
- render "new"
24
- end
25
- end
17
+ def create
18
+ @metric = Metric.new(params[:metric])
26
19
 
27
- def edit
28
- @metric = Metric.find(params[:id])
29
- render "edit"
20
+ if @metric.save
21
+ redirect_to @metric, :notice => "Successfully created metric"
22
+ else
23
+ render "new"
30
24
  end
25
+ end
31
26
 
32
- def update
33
- @metric = Metric.find(params[:id])
27
+ def edit
28
+ @metric = Metric.find(params[:id])
29
+ render "edit"
30
+ end
34
31
 
35
- if @metric.update_attributes(params[:metric])
36
- redirect_to @metric, :notice => "Successfully updated metric"
37
- else
38
- render "edit"
39
- end
40
- end
32
+ def update
33
+ @metric = Metric.find(params[:id])
41
34
 
42
- def destroy
43
- @metric = Metric.find(params[:id])
44
- @metric.destroy
45
- redirect_to metrics_path, :notice => "Metric has been destroyed"
35
+ if @metric.update_attributes(params[:metric])
36
+ redirect_to @metric, :notice => "Successfully updated metric"
37
+ else
38
+ render "edit"
46
39
  end
47
-
40
+ end
41
+
42
+ def destroy
43
+ @metric = Metric.find(params[:id])
44
+ @metric.destroy
45
+ redirect_to metrics_path, :notice => "Metric has been destroyed"
46
+ end
48
47
  end
49
48
  end
@@ -0,0 +1,4 @@
1
+ module IWonder
2
+ module AbTestsHelper
3
+ end
4
+ end
@@ -0,0 +1,101 @@
1
+ module IWonder
2
+ class AbTest < ActiveRecord::Base
3
+ attr_accessible :name, :sym, :description, :ab_test_goals_attributes, :test_applies_to, :test_group_names
4
+ serialize :options, Hash
5
+ serialize :test_group_data, Hash
6
+
7
+ has_many :test_group_memberships, :dependent => :destroy
8
+
9
+ has_many :ab_test_goals, :dependent => :destroy
10
+ accepts_nested_attributes_for :ab_test_goals, :allow_destroy => true
11
+
12
+ hash_accessor :options, :test_group_names, :type => :array, :reject_blanks => true
13
+
14
+ validates_presence_of :name, :on => :create, :message => "can't be blank"
15
+ validates_uniqueness_of :name, :on => :create, :message => "must be unique"
16
+ validates_presence_of :sym, :on => :create, :message => "can't be blank"
17
+ validates_format_of :sym, :with => /^[\w\d\_]+$/, :on => :create, :message => "can only contain letters, numbers and underscores"
18
+ validates_uniqueness_of :sym, :on => :create, :message => "must be unique"
19
+ validates_inclusion_of :test_applies_to, :in => %w( session user account ), :on => :create, :message => "extension %s is not included in the list"
20
+
21
+ validate :has_two_groups_and_a_goal
22
+ def has_two_groups_and_a_goal
23
+ unless test_group_names.length >= 2
24
+ errors.add(:base, "Must have atleast two test groups")
25
+ end
26
+
27
+ if ab_test_goals.reject(&:marked_for_destruction?).length == 0
28
+ errors.add(:base, "Must have atleast one goal")
29
+ end
30
+ end
31
+
32
+ def started?
33
+ test_group_memberships.count > 0
34
+ end
35
+
36
+ def which_test_group?(current_controller)
37
+ if(current_group = get_current_group(current_controller))
38
+ return current_group.test_group_name
39
+ else
40
+ test_group = add_to_test_group(randomly_chosen_test_group, current_controller)
41
+ return test_group.test_group_name
42
+ end
43
+ end
44
+
45
+ def assigned_count(test_group_name)
46
+ self.test_group_memberships.where(:test_group_name => test_group_name).count
47
+ end
48
+
49
+ def get_results_for(test_group_name, ab_test_goal)
50
+ scoped_test_group_memberships = self.test_group_memberships.where(:test_group_name => test_group_name).scoped
51
+
52
+ if test_applies_to =~ /account/i
53
+ event_membership_key = "account_id"
54
+ elsif test_applies_to =~ /user/i
55
+ event_membership_key = "user_id"
56
+ else
57
+ event_membership_key = "session_id"
58
+ end
59
+
60
+ scoped_groups_with_events = scoped_test_group_memberships.joins("LEFT JOIN i_wonder_events ON i_wonder_events.#{event_membership_key} = i_wonder_test_group_memberships.member_id")
61
+
62
+ scoped_groups_with_goal_events = ab_test_goal.add_goal_to_query(scoped_groups_with_events)
63
+
64
+ scoped_groups_with_goal_events.count
65
+ end
66
+
67
+
68
+ private
69
+
70
+ def get_current_group(current_controller)
71
+ if test_applies_to =~ /account/i
72
+ env = current_controller.request.env
73
+ test_group_memberships.where(:member_type => "account", :member_id => env[ENV_KEY]["account_id"].to_s).first
74
+ elsif test_applies_to =~ /user/i
75
+ env = current_controller.request.env
76
+ test_group_memberships.where(:member_type => "user", :member_id => env[ENV_KEY]["user_id"].to_s).first
77
+ else
78
+ session_id = current_controller.send("cookies")[COOKIE_KEY+Logging::SESSION_KEY_NAME]
79
+ test_group_memberships.where(:member_type => "session", :member_id => session_id.to_s).first
80
+ end
81
+ end
82
+
83
+ def add_to_test_group(test_group_name, current_controller)
84
+ if test_applies_to =~ /account/i
85
+ env = current_controller.request.env
86
+ test_group_memberships.create!(:member_type => "account", :member_id => env[ENV_KEY]["account_id"].to_s, :test_group_name => test_group_name)
87
+ elsif test_applies_to =~ /user/i
88
+ env = current_controller.request.env
89
+ test_group_memberships.create!(:member_type => "user", :member_id => env[ENV_KEY]["user_id"].to_s, :test_group_name => test_group_name)
90
+ else
91
+ session_id = current_controller.send("cookies")[COOKIE_KEY+Logging::SESSION_KEY_NAME]
92
+ test_group_memberships.create!(:member_type => "session", :member_id => session_id.to_s, :test_group_name => test_group_name)
93
+ end
94
+ end
95
+
96
+ def randomly_chosen_test_group
97
+ test_group_names[rand(test_group_names.length)]
98
+ end
99
+
100
+ end
101
+ end
@@ -0,0 +1,49 @@
1
+ module IWonder
2
+ class AbTestGoal < ActiveRecord::Base
3
+ belongs_to :ab_test
4
+
5
+ attr_accessible :goal_type, :event_type, :page_view_controller, :page_view_action
6
+ serialize :options, Hash
7
+ hash_accessor :options, :goal_type, :default => "Event"
8
+ hash_accessor :options, :event_type
9
+ hash_accessor :options, :page_view_controller
10
+ hash_accessor :options, :page_view_action
11
+
12
+ validates_inclusion_of :goal_type, :in => ["Event", "Page View"]
13
+ validates_presence_of :event_type, :if => :tracks_event?
14
+ validates_presence_of :page_view_controller, :if => :tracks_page_view?
15
+
16
+ def tracks_event?
17
+ goal_type == "Event"
18
+ end
19
+
20
+ def tracks_page_view?
21
+ goal_type == "Page View"
22
+ end
23
+
24
+ def add_goal_to_query(scoped_statement)
25
+ if tracks_event?
26
+ sc = scoped_statement.where("i_wonder_events.event_type = ?", event_type)
27
+ else
28
+ sc = scoped_statement.where("i_wonder_events.event_type = ? AND controller = ?", "hit", page_view_controller)
29
+
30
+ if page_view_action.present?
31
+ sc = sc.where("i_wonder_events.action = ?", page_view_action)
32
+ end
33
+ end
34
+
35
+ sc.where("i_wonder_events.created_at > ?", self.ab_test.created_at) # event had to come after goal
36
+ end
37
+
38
+ def to_s
39
+ if tracks_event?
40
+ "#{event_type} occurred"
41
+ elsif page_view_action.present?
42
+ "#{page_view_controller}##{page_view_action} hit"
43
+ else
44
+ "any #{page_view_controller} hit"
45
+ end
46
+ end
47
+
48
+ end
49
+ end
@@ -15,12 +15,15 @@ module IWonder
15
15
  # for all events on the current session, attach the user and session from that first event
16
16
  update_all({:user_id => user_id, :session_id => original_session_id}, ["session_id = ? AND user_id IS NULL", session_id])
17
17
 
18
- # clear our any new_visitor events other than the first one
18
+ # Change the new_visitor to a return_visit
19
19
  if new_visitor_event and original_session_id != session_id
20
- Event.where(:user_id => user_id, :event_type => "new_visitor").where("id <> ?", new_visitor_event.id).delete_all
20
+ update_all({:event_type => "return_visit"}, ["user_id = ? AND event_type = ? AND id <> ?", user_id, "new_visitor", new_visitor_event.id])
21
21
  end
22
22
 
23
23
 
24
+ # TODO: merge any tests on the session id over to the original session_id
25
+ # remove any duplicates (going with the original)
26
+
24
27
  return original_session_id
25
28
  end
26
29
  # handle_asynchronously :merge_session_to_user
@@ -0,0 +1,12 @@
1
+ module IWonder
2
+ class TestGroupMembership < ActiveRecord::Base
3
+ belongs_to :ab_test
4
+
5
+ validates_presence_of :test_group_name, :on => :create, :message => "can't be blank"
6
+ validates_inclusion_of :member_type, :in => %w( account user session ), :on => :create
7
+ validates_presence_of :member_id, :on => :create, :message => "can't be blank"
8
+
9
+ validates_uniqueness_of :ab_test_id, :on => :create, :message => "must be unique for this member", :scope => [:member_type, :member_id]
10
+
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ <li class="ab_test_goal deletable_row">
2
+ <%= f.hidden_field :id%>
3
+
4
+ <%= f.select :goal_type, ["Event", "Page View"], {}, :onchange => "var row = $(this).closest('li'); row.find('.options').hide(); if($(this).val()=='Event'){ row.find('#event_options').show(); }else{ row.find('#page_view_options').show(); }" %>
5
+
6
+ <div id="page_view_options" class='options' <% unless f.object.tracks_page_view? %> style="display:none" <% end %> >
7
+ <%= f.text_field :page_view_controller, :placeholder => "controller", :size => 20 %>
8
+
9
+ <%= f.text_field :page_view_action, :placeholder => "action (optional)", :size => 20 %>
10
+ </div>
11
+
12
+ <div id="event_options" class='options' <% unless f.object.tracks_event? %> style="display:none" <% end %> >
13
+ <%= f.text_field :event_type, :placeholder => "event name" %>
14
+ </div>
15
+
16
+ <% if f.object.new_record? %>
17
+ <%= link_to_function "", "$(this).closest('li').remove();", :class => "ui-icon ui-icon-circle-close delete" %>
18
+ <% else%>
19
+ <%= f.hidden_field :_destroy, :class => "hidden_destroy" %>
20
+ <%= link_to_function "", "nested_delete(this);", :class => "ui-icon ui-icon-circle-close delete" %>
21
+ <% end %>
22
+
23
+ </li>
@@ -0,0 +1,13 @@
1
+ <%= f.fields_for :ab_test_goals, IWonder::AbTestGoal.new, :child_index => "new_row_id" do |f2|%>
2
+ <%= link_to_function "+ Add Goal", "$('#ab_test_goals').append(new_row_inserter(\"#{j render(:partial => "ab_test_goal_row", :locals => {:f => f2})}\"));", :id => "new_goal" %>
3
+ <% end %>
4
+
5
+ <h4>Goals</h4>
6
+
7
+ <ul id="ab_test_goals">
8
+ <%= f.fields_for :ab_test_goals do |f2|%>
9
+ <%= render :partial => "ab_test_goal_row", :locals => {:f => f2} %>
10
+ <% end %>
11
+ </ul>
12
+
13
+ <br style="clear:both"/>
@@ -0,0 +1,5 @@
1
+ <li class="ab_test_group deletable_row">
2
+ <%= text_field_tag "ab_test[test_group_names][]", (defined?(test_group_name) ? test_group_name : ""), :placeholder => "Name..." %>
3
+
4
+ <%= link_to_function "", "$(this).closest('li').remove();", :class => "ui-icon ui-icon-circle-close delete" %>
5
+ </li>
@@ -0,0 +1,9 @@
1
+ <%= link_to_function "+ Add Group", "$('#ab_test_groups').append(\"#{j render(:partial => "ab_test_group_row")}\");", :id => "new_goal" %>
2
+
3
+ <h4>Test Groups</h4>
4
+
5
+ <ul id="ab_test_groups">
6
+ <%= render :partial => "ab_test_group_row", :collection => f.object.test_group_names, :as => :test_group_name %>
7
+ </ul>
8
+
9
+ <br style="clear:both"/>
@@ -0,0 +1,50 @@
1
+ <%= form_for @ab_test, :html => {:class => "main_form ab_test"} do |f| %>
2
+ <% if @ab_test.errors.any? %>
3
+ <div id="error_explanation">
4
+ <h2><%= pluralize(@ab_test.errors.count, "error") %> prohibited this test from being saved:</h2>
5
+
6
+ <ul>
7
+ <% @ab_test.errors.full_messages.each do |msg| %>
8
+ <li><%= msg %></li>
9
+ <% end %>
10
+ </ul>
11
+ </div>
12
+ <% end %>
13
+
14
+ <div class="field">
15
+ <b>I wonder</b> <%= f.text_field :name, :placeholder => "test name...", :size => 50 %><b>?</b>
16
+ </div>
17
+
18
+ <div class="field">
19
+ <%= f.label :description %>
20
+ <%= f.text_area :description, :rows => 3, :cols => 50%>
21
+ </div>
22
+
23
+ <div class="field">
24
+ <%= f.label :test_applies_to %>
25
+ <%= f.select :test_applies_to, [["Visitor", "session"], ["User", "user"], ["Account", "account"]] %><br/>
26
+ <span class="description">If User or Account is selected but not available, the first option will be returned.</span>
27
+ </div>
28
+
29
+
30
+ <div class="field">
31
+ <%= f.label :sym, "Code reference symbol" %>
32
+ <%= f.text_field :sym, :size => 10 %>
33
+ <span class="description">letters numbers and underscores. NO spaces!</span>
34
+ </div>
35
+
36
+ <hr/>
37
+
38
+ <%= render :partial => "ab_test_groups", :locals => {:f => f} %>
39
+
40
+ <hr/>
41
+
42
+ <%= render :partial => "ab_test_goals", :locals => {:f => f} %>
43
+
44
+ <hr/>
45
+
46
+ <div class="field">
47
+ <%= f.submit %>
48
+ </div>
49
+
50
+ <% end %>
@@ -0,0 +1,18 @@
1
+ <p>
2
+ This test hasn't started yet. Use the following code in a controller or view to begin:
3
+ </p>
4
+
5
+ <br/>
6
+ <p class="description">You can use the group name directly:</p>
7
+ <pre>&lt;%= which_test_group?("<%= @ab_test.sym %>") %&gt;</pre>
8
+
9
+ <p class="description">or run through different scenarios:</p>
10
+
11
+ <pre>
12
+ case which_test_group?("<%= @ab_test.sym %>")
13
+ <% for option_name in @ab_test.test_group_names -%>
14
+ when "<%= option_name%>"
15
+ ...
16
+ <% end -%>
17
+ end
18
+ </pre>
@@ -0,0 +1,26 @@
1
+ <table id="ab_test_summary" cellpadding=0 cellspacing=0>
2
+ <thead>
3
+ <tr>
4
+ <td></td>
5
+ <td>Assigned</td>
6
+
7
+ <% for ab_test_goal in @ab_test.ab_test_goals do %>
8
+ <td><%= ab_test_goal %></td>
9
+ <% end %>
10
+
11
+ </tr>
12
+ </thead>
13
+
14
+ <% for group_name in @ab_test.test_group_names do %>
15
+ <tr>
16
+ <td><%= group_name%></td>
17
+ <td><%= @ab_test.test_group_memberships.where(:test_group_name => group_name).count %></td>
18
+
19
+ <% for ab_test_goal in @ab_test.ab_test_goals do %>
20
+ <td><%= @ab_test.get_results_for(group_name, ab_test_goal) %></td>
21
+ <% end %>
22
+
23
+
24
+ </tr>
25
+ <% end %>
26
+ </table>