mountain-goat 0.1.8 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. data/README.md +119 -48
  2. data/generators/mg/mg_generator.rb +17 -9
  3. data/generators/mg/templates/create_mountain_goat_tables.rb +61 -25
  4. data/generators/mg/templates/mg.rb +1 -0
  5. data/generators/mg/templates/mountain-goat.yml +22 -3
  6. data/generators/mg/templates/mountain_goat_reports.rake +17 -0
  7. data/generators/mg/templates/update_mountain_goat_tables.rb +104 -0
  8. data/lib/mountain-goat.rb +21 -12
  9. data/lib/mountain-goat/analytics.rb +134 -0
  10. data/lib/mountain-goat/controllers/{mountain_goat/mountain_goat_converts_controller.rb → mg/converts_controller.rb} +12 -11
  11. data/lib/mountain-goat/controllers/{mountain_goat/mountain_goat_metric_variants_controller.rb → mg/metric_variants_controller.rb} +6 -5
  12. data/lib/mountain-goat/controllers/{mountain_goat/mountain_goat_metrics_controller.rb → mg/metrics_controller.rb} +8 -6
  13. data/lib/mountain-goat/controllers/mg/mg.rb +47 -0
  14. data/lib/mountain-goat/controllers/{mountain_goat → mg}/mountain_goat_controller.rb +5 -42
  15. data/lib/mountain-goat/controllers/mg/playground_controller.rb +8 -0
  16. data/lib/mountain-goat/controllers/{mountain_goat/mountain_goat_rallies_controller.rb → mg/rallies_controller.rb} +4 -26
  17. data/lib/mountain-goat/controllers/mg/report_items_controller.rb +82 -0
  18. data/lib/mountain-goat/controllers/mg/reports_controller.rb +90 -0
  19. data/lib/mountain-goat/m_g.rb +64 -0
  20. data/lib/mountain-goat/metric_tracking.rb +192 -74
  21. data/lib/mountain-goat/models/mg/ci_meta.rb +10 -0
  22. data/lib/mountain-goat/models/mg/convert.rb +147 -0
  23. data/lib/mountain-goat/models/mg/convert_meta_type.rb +20 -0
  24. data/lib/mountain-goat/models/mg/cs_meta.rb +10 -0
  25. data/lib/mountain-goat/models/{metric.rb → mg/metric.rb} +3 -5
  26. data/lib/mountain-goat/models/mg/metric_variant.rb +25 -0
  27. data/lib/mountain-goat/models/mg/mountain_goat.rb +3 -0
  28. data/lib/mountain-goat/models/{rally.rb → mg/rally.rb} +4 -3
  29. data/lib/mountain-goat/models/mg/report.rb +11 -0
  30. data/lib/mountain-goat/models/mg/report_item.rb +24 -0
  31. data/lib/mountain-goat/models/mg/report_mailer.rb +18 -0
  32. data/lib/mountain-goat/public/g-bar-min.js +7 -0
  33. data/lib/mountain-goat/public/g-dot-min.js +7 -0
  34. data/lib/mountain-goat/public/g-line-min.js +7 -0
  35. data/lib/mountain-goat/public/g-pie-min.js +1 -0
  36. data/lib/mountain-goat/public/g-raphael-min.js +7 -0
  37. data/lib/mountain-goat/public/jqModel.css +41 -0
  38. data/lib/mountain-goat/public/jqModel.js +69 -0
  39. data/lib/mountain-goat/public/jquery.raphael.js +208 -0
  40. data/lib/mountain-goat/public/mg.css +135 -26
  41. data/lib/mountain-goat/public/mg.js +53 -1
  42. data/lib/mountain-goat/public/raphael-min.js +7 -0
  43. data/lib/mountain-goat/public/utils.js +520 -0
  44. data/lib/mountain-goat/switch_variant.rb +4 -4
  45. data/lib/mountain-goat/version.rb +1 -1
  46. data/lib/mountain-goat/views/mountain_goat/layouts/_pdf.html.erb +15 -0
  47. data/lib/mountain-goat/views/mountain_goat/layouts/mountain_goat.html.erb +17 -5
  48. data/lib/mountain-goat/views/mountain_goat/layouts/xhr.html.erb +2 -0
  49. data/lib/mountain-goat/views/mountain_goat/{mountain_goat_converts → mg/converts}/.tmp_show.html.erb.4433~ +0 -0
  50. data/lib/mountain-goat/views/mountain_goat/{mountain_goat_converts → mg/converts}/_convert_form.html.erb +0 -0
  51. data/lib/mountain-goat/views/mountain_goat/{mountain_goat_converts → mg/converts}/_convert_meta_type_form.html.erb +0 -0
  52. data/lib/mountain-goat/views/mountain_goat/mg/converts/edit.html.erb +13 -0
  53. data/lib/mountain-goat/views/mountain_goat/mg/converts/index.html.erb +25 -0
  54. data/lib/mountain-goat/views/mountain_goat/mg/converts/new.html.erb +13 -0
  55. data/lib/mountain-goat/views/mountain_goat/{mountain_goat_converts → mg/converts}/show.html.erb +3 -28
  56. data/lib/mountain-goat/views/mountain_goat/{mountain_goat_metric_variants → mg/metric_variants}/_metric_variant_form.html.erb +0 -4
  57. data/lib/mountain-goat/views/mountain_goat/mg/metric_variants/edit.html.erb +13 -0
  58. data/lib/mountain-goat/views/mountain_goat/{mountain_goat_metric_variants → mg/metric_variants}/index.html.erb +3 -3
  59. data/lib/mountain-goat/views/mountain_goat/mg/metric_variants/new.html.erb +15 -0
  60. data/lib/mountain-goat/views/mountain_goat/{mountain_goat_metric_variants → mg/metric_variants}/show.html.erb +4 -5
  61. data/lib/mountain-goat/views/mountain_goat/{mountain_goat_metrics → mg/metrics}/.tmp_show.html.erb.21270~ +0 -0
  62. data/lib/mountain-goat/views/mountain_goat/{mountain_goat_metrics → mg/metrics}/_metric_form.html.erb +0 -2
  63. data/lib/mountain-goat/views/mountain_goat/mg/metrics/edit.html.erb +13 -0
  64. data/lib/mountain-goat/views/mountain_goat/mg/metrics/index.html.erb +14 -0
  65. data/lib/mountain-goat/views/mountain_goat/mg/metrics/new.html.erb +14 -0
  66. data/lib/mountain-goat/views/mountain_goat/{mountain_goat_metrics → mg/metrics}/show.html.erb +7 -8
  67. data/lib/mountain-goat/views/mountain_goat/{mountain_goat → mg/mountain_goat}/login.html.erb +0 -0
  68. data/lib/mountain-goat/views/mountain_goat/mg/playground/test.html.erb +14 -0
  69. data/lib/mountain-goat/views/mountain_goat/{mountain_goat_rallies → mg/rallies}/.tmp__rally.html.erb.40484~ +0 -0
  70. data/lib/mountain-goat/views/mountain_goat/mg/rallies/_rallies.html.erb +5 -0
  71. data/lib/mountain-goat/views/mountain_goat/{mountain_goat_rallies → mg/rallies}/_rallies_form.html.erb +0 -0
  72. data/lib/mountain-goat/views/mountain_goat/{mountain_goat_rallies → mg/rallies}/_rally.html.erb +0 -0
  73. data/lib/mountain-goat/views/mountain_goat/mg/rallies/edit.html.erb +13 -0
  74. data/lib/mountain-goat/views/mountain_goat/{mountain_goat_rallies → mg/rallies}/index.html.erb +1 -1
  75. data/lib/mountain-goat/views/mountain_goat/mg/rallies/new.html.erb +13 -0
  76. data/lib/mountain-goat/views/mountain_goat/{mountain_goat_rallies → mg/rallies}/show.html.erb +1 -1
  77. data/lib/mountain-goat/views/mountain_goat/mg/report_items/_chart.html.erb +18 -0
  78. data/lib/mountain-goat/views/mountain_goat/mg/report_items/_report_item_form.html.erb +10 -0
  79. data/lib/mountain-goat/views/mountain_goat/mg/report_items/_report_item_pivot_form.html.erb +14 -0
  80. data/lib/mountain-goat/views/mountain_goat/mg/report_items/_show.html.erb +29 -0
  81. data/lib/mountain-goat/views/mountain_goat/mg/report_items/_svg_chart.html.erb +4 -0
  82. data/lib/mountain-goat/views/mountain_goat/mg/report_items/edit.html.erb +19 -0
  83. data/lib/mountain-goat/views/mountain_goat/mg/report_items/new.html.erb +17 -0
  84. data/lib/mountain-goat/views/mountain_goat/mg/report_mailer/report.html.erb +27 -0
  85. data/lib/mountain-goat/views/mountain_goat/mg/reports/_report.html.erb +22 -0
  86. data/lib/mountain-goat/views/mountain_goat/mg/reports/_report_form.html.erb +21 -0
  87. data/lib/mountain-goat/views/mountain_goat/mg/reports/_report_report_items.html.erb +5 -0
  88. data/lib/mountain-goat/views/mountain_goat/mg/reports/edit.html.erb +36 -0
  89. data/lib/mountain-goat/views/mountain_goat/mg/reports/index.html.erb +26 -0
  90. data/lib/mountain-goat/views/mountain_goat/mg/reports/new.html.erb +17 -0
  91. data/lib/mountain-goat/views/mountain_goat/mg/reports/show.html.erb +21 -0
  92. data/test/fixtures/{ci_metas.yml → mg_ci_metas.yml} +0 -0
  93. data/test/fixtures/{convert_meta_types.yml → mg_convert_meta_types.yml} +0 -0
  94. data/test/fixtures/{converts.yml → mg_converts.yml} +5 -0
  95. data/test/fixtures/{cs_metas.yml → mg_cs_metas.yml} +0 -0
  96. data/test/fixtures/mg_deliveries.yml +9 -0
  97. data/test/fixtures/{metric_variants.yml → mg_metric_variants.yml} +0 -0
  98. data/test/fixtures/{metrics.yml → mg_metrics.yml} +2 -3
  99. data/test/fixtures/{rallies.yml → mg_rallies.yml} +6 -2
  100. data/test/fixtures/mg_report_items.yml +13 -0
  101. data/test/fixtures/mg_reports.yml +15 -0
  102. data/test/mg_convert_test.rb +32 -0
  103. data/test/mg_converts_controller_test.rb +47 -0
  104. data/test/mg_metric_variants_controller_test.rb +46 -0
  105. data/test/mg_metrics_controller_test.rb +52 -0
  106. data/test/mg_mountain_goat_controller_test.rb +45 -0
  107. data/test/mg_mountain_goat_test.rb +392 -0
  108. data/test/mg_playground_controller_test.rb +11 -0
  109. data/test/mg_rallies_controller_test.rb +36 -0
  110. data/test/mg_report_item_test.rb +7 -0
  111. data/test/mg_report_items_controller_test.rb +31 -0
  112. data/test/mg_report_test.rb +18 -0
  113. data/test/mg_reports_controller_test.rb +50 -0
  114. data/test/test_helper.rb +203 -0
  115. metadata +108 -55
  116. data/lib/mountain-goat/models/ci_meta.rb +0 -9
  117. data/lib/mountain-goat/models/convert.rb +0 -70
  118. data/lib/mountain-goat/models/convert_meta_type.rb +0 -19
  119. data/lib/mountain-goat/models/cs_meta.rb +0 -9
  120. data/lib/mountain-goat/models/metric_variant.rb +0 -22
  121. data/lib/mountain-goat/views/mountain_goat/mountain_goat_converts/edit.html.erb +0 -13
  122. data/lib/mountain-goat/views/mountain_goat/mountain_goat_converts/index.html.erb +0 -48
  123. data/lib/mountain-goat/views/mountain_goat/mountain_goat_converts/new.html.erb +0 -13
  124. data/lib/mountain-goat/views/mountain_goat/mountain_goat_metric_variants/edit.html.erb +0 -13
  125. data/lib/mountain-goat/views/mountain_goat/mountain_goat_metric_variants/new.html.erb +0 -15
  126. data/lib/mountain-goat/views/mountain_goat/mountain_goat_metrics/edit.html.erb +0 -13
  127. data/lib/mountain-goat/views/mountain_goat/mountain_goat_metrics/index.html.erb +0 -14
  128. data/lib/mountain-goat/views/mountain_goat/mountain_goat_metrics/new.html.erb +0 -14
  129. data/lib/mountain-goat/views/mountain_goat/mountain_goat_rallies/_rallies.html.erb +0 -5
  130. data/lib/mountain-goat/views/mountain_goat/mountain_goat_rallies/edit.html.erb +0 -13
  131. data/lib/mountain-goat/views/mountain_goat/mountain_goat_rallies/new.html.erb +0 -13
  132. data/test/ocelot_converts_controller_test.rb +0 -45
  133. data/test/ocelot_metric_variants_controller_test.rb +0 -45
  134. data/test/ocelot_metrics_controller_test.rb +0 -45
  135. data/test/ocelot_rallies_controller_test.rb +0 -8
data/README.md CHANGED
@@ -1,25 +1,33 @@
1
1
  # Mountain-Goat
2
2
 
3
- Embed a high-quality analytics platform in your application in minutes. Gain important A/B test business insights, and view the results and analytics in real time.
3
+ Embed a professional analytics and reporting platform into your application in minutes. Gain important business insights through bandit (automated a/b) testing, view the results and analytics in real time, and get delivered daily or weekly usage reports.
4
+
5
+ Note: For updating from version < 1.0.0, please read the **Upgrade** section below.
4
6
 
5
7
  Add simple hooks in your code to display a/b metrics
6
8
 
7
- <%= metric_variant(:homescreen_text, :user_signup, "Welcome here") %>
9
+ <%= bd(:homescreen_text, "Welcome here") %>
8
10
 
9
- This creates a database entry to a/b test "homescreen_text" against a goal "user signup". Visit "http://yourdomain.com/mg" and you can add / adjust options (variants) for this text. When a user converts on this goal, you run the following code.
11
+ This creates a database entry for your a/b test "homescreen_text". Visit "http://yourdomain.com/mg" and you can add / adjust options (variants) for this text. When a user converts on a goal, you run the following code.
10
12
 
13
+ #e.g. users_controller.rb
11
14
  def create
12
15
  record_conversion(:user_signup)
13
16
  ...
14
17
  end
15
18
 
16
- This will track a conversion not only for the goal, but for the variant of "homescreen_text" (and any other associated metrics) that the user was served when he came to the home-page.
19
+ This will track a conversion not only for the goal, but for the variant of "homescreen_text" that the user was served when your user came to the home-page.
20
+
21
+ Bandit testing (see [Wikipedia - Multi-armed Bandit](http://en.wikipedia.org/wiki/Multi-armed_bandit) automatically converges on the variant that achieves the highest success through exploration each variant. You essentially get a Bayesian solution with no hassle!
17
22
 
18
- The best part? The mountain-goat admin console is located on your server and you can view and analyze your data in real time.
23
+ The best part? The Mountain-Goat Administrative console is located on your server and you can view and analyze your data in real time (or through setting up usage report emails).
19
24
 
20
25
  - See which metric variants are working and not working ("Cowabunga!" did 120% better than "Enter here", but 10% worse than "Do it rockapella!")
21
- - A/B testing? How about A/B/C/D/E testing?
22
- - Visually analyze the your metric variants; change them on the fly, adding new ones.
26
+ - Get daily / weekly / whenever emails delivered to your inbox, full of the data you crave (e.g. Signs up by day / comments by referring domain )
27
+ - Similar to Bayesian learning, Multi-armed bandit solutions will automatically deliver the highest performing variants
28
+ - This is done while still achieving minimal (logarithmically decreasing) regret (showing of poorer performing variants)
29
+ - A/B testing? How about A/B/C/D/E testing? Add as many variants as you like.
30
+ - Visually analyze the your metric variants; change them on the fly, adding new ones or kicking out poor performers.
23
31
  - Watch goal conversions in real-time with live-action console (grab the popcorn and watch how your users "sign up" and "view items" and ...)
24
32
  - You can do more than change text, "switch variants" let you enter arbitrary ruby code, change the control of your site ("Do my users have to sign-in before commenting? Let's test!")
25
33
  - Track goals with meta data (record_conversion(:user_signup, :referrer => request.env['HTTP_REFERER']))
@@ -30,13 +38,23 @@ The best part? The mountain-goat admin console is located on your server and yo
30
38
 
31
39
  For more information, read my blog post on how mountain goat quickly accomplishes a/b testing: http://blog.drawn.to/how-we-do-ab-testing
32
40
 
41
+ ## Upgrade from < 1.0.0
42
+
43
+ If you are upgrading from Mountain Goat < 1.0.0, please run the following command (please overwrite mountain-goat.yml when prompted):
44
+
45
+ ./script/generate mg --update=1.0.0
46
+
47
+ rake db:migrate
48
+
49
+ This will install new migrations necessary for version 1.0.0.
50
+
33
51
  ## Install
34
52
 
35
53
  ### Mountain Goat gem
36
54
 
37
55
  gem install mountain-goat
38
56
 
39
- ### Mountain Goat configration
57
+ ### Mountain Goat configuration
40
58
 
41
59
  Next run generator to create config file and necessary database migration (optionally pass in --password=my_mg_password)
42
60
 
@@ -45,7 +63,8 @@ Next run generator to create config file and necessary database migration (optio
45
63
  This will generate
46
64
 
47
65
  /config/mountain-goat.yml (for storing a password to access mountain-goat)
48
- /db/migrate/xxx_create_mountain_goat_tables.rb (necessary databae migrations to store mg data)
66
+ /db/migrate/xxx_create_mountain_goat_tables.rb (necessary database migrations to store mg data)
67
+ /lib/tasks/mountain_goat_reports.rake (add tasks for report delivery)
49
68
 
50
69
  Modify /config/mountain-goat.yml to setup a password for each environment of your product
51
70
 
@@ -66,27 +85,51 @@ Mountain Goat hinges around three core concepts:
66
85
 
67
86
  After you set up your database with some mountain-goat tables, the code will handle populating these tables for you. In your code, you can start A/B testing immediately.
68
87
 
69
- <h2><%= metric_variant(:banner_on_store_front, :user_purchases_coffee, "Now arsenic and gluten-free") %></h2>
88
+ <h2><%= bd(:banner_on_store_front, "Now arsenic and gluten-free") %></h2>
70
89
 
71
- The metric_variant function (or, mv for short) takes three parameters:
90
+ The bandit (bd) function takes two parameters:
72
91
 
73
- mv(metric_name, convert_name, default)
92
+ bd(metric_name, default)
74
93
 
75
- This will automatically create a metric and conversion and populate a metric variant with the default value. Easy, eh?
76
-
77
- From here, you can go into the mountain goat admin center and add new metric variants to fit your need. It's all built-in to *your* application, in house.
94
+ This will automatically create a metric and populate a metric variant with the default value. Easy, eh?
95
+
96
+ From here, you can go into the mountain goat admin center and add new metric variants to fit your need. It's all built into *your* application, in house.
78
97
 
79
98
  http://{your_rails_app}/mg (e.g. if you're at railsrocks.com, then visit http://railsrocks.com/mg)
80
99
 
81
100
  The other important code you'll need to implement is to tell the system when a goal is achieved.
82
101
 
83
102
  def purchase #in coffees_controller.rb
84
- record_conversion(:user_purchases_coffee)
103
+ rw(:user_purchases_coffee, 10) #10 "points"
85
104
  ...
86
105
  end
87
106
 
88
- This will go in and record a conversion (a "rally") for a user purchasing coffee. Further, it will track a hit for any metric-variants served to that user that relate to this goal. For example "Chuck Norris works here" might get one point. You will see which metrics lead to a conversion; this is the core of A/B testing.
89
-
107
+ This will go in and record a conversion (a "rally") for a user purchasing coffee. Further, it will track a hit for any metric-variants served to that user. For example "Chuck Norris works here" might get reward points. You will see which metrics lead to a conversion; this is the core of A/B and bandit testing.
108
+
109
+ ## Bandit Testing
110
+
111
+ Why points? Bandit testing is about maximizing a return. The original idea is if I have a slot machine with 30 handles each with different payouts, how do I maximize my return? You pull the arm that has given you the best pay out so far, but occasionally pull other arms to make sure there's not something better. Mountain-Goat's Bandit system will do the same for you.
112
+
113
+ The most common solution to the multi-armed bandit solution is epsilon-greedy (documented [here](http://www.cs.nyu.edu/~mohri/pub/bandit.pdf)).
114
+
115
+ Given epsilon in [0, 1] #commonly 0.05 - 0.15
116
+
117
+ If any variant has never been pulled
118
+ Pull that variant
119
+ Otherwise
120
+
121
+ If random(0..1) < epsilon
122
+ Pull random arm
123
+ Else
124
+ Pull winning arm #highest "reward"
125
+ End
126
+
127
+ This way, we most commonly explore the best arm (e.g. your best slogan) while occasionally trying other arms.
128
+
129
+ A small variant on this is called epsilon-greedy-decreasing, which reduces epsilon each pull by 1/total_pulls. Thus, after 100 pulls, epsilon = epsilon_0 / 100. This is shown to have an optimal minimal expectation of regret.
130
+
131
+ You can configure these options in `mountain-goat.yml`.
132
+
90
133
  ## Mountain Goat admin suite
91
134
 
92
135
  Navigate to /mg in your rails application (on your actual server instance) to reach the mountain-goat admin center. Here, you can analyze / adjust your A/B tests.
@@ -110,7 +153,21 @@ Go to "Metrics" and visit a specific metric. You'll see which metric variants a
110
153
  ###Rallies
111
154
 
112
155
  Rallies shows you what's going on, in real time. You will see conversions (goals) being hit by your clients in real time. Grab a bag of pop-corn and watch users struggle (or glide) across your site. Add meta data to get further information. This page automatically updates as new rallies come in.
113
-
156
+
157
+ ###Reports
158
+
159
+ In the Mountain Goat Administrative suite, you can add reports. Reports will be delivered as emails with an attached pdf showing statistics about your product. You'll need the following installed to use Mountain Goat Reports.
160
+
161
+ gem install pdfkit
162
+ gem install svg-graph
163
+
164
+ You'll need [wkhtmltopdf](http://code.google.com/p/wkhtmltopdf/) installed. Please configure the location of the executable in `mountain-goat.yml`.
165
+
166
+ Finally, simply set up a cron task on your system when you would like your reports delivered. E.g.
167
+
168
+ crontab -l | { cat; echo "45 6 * * * cd /path/to/project && /usr/bin/rake RAILS_ENV=production mg:deliver[daily] >> /path/to/project/log/cron.log 2>&1"; } | crontab -
169
+ crontab -l | { cat; echo "10 6 1 * * cd /path/to/project && /usr/bin/rake RAILS_ENV=production mg:deliver[monthly] >> $app_root/current/log/cron.log 2>&1"; } | crontab -
170
+
114
171
  ## Advanced Features
115
172
 
116
173
  ### Meta data
@@ -135,19 +192,13 @@ Instead of just serving text, you can also serve flow control in Mountain Goat,
135
192
  discount = 0.90
136
193
  end
137
194
 
138
- variant.whomp_whomp(0.5) do #reduce priority to 0.5 (default 1.0)
195
+ variant.whomp_whomp do
139
196
  discount = 0.0
140
197
  end
141
198
  end
142
199
 
143
- Mountain goat will automatically break those down into three cases (:ten_percent, :big_winner, :whomp_whomp) and serve them out at random to the user (with whomp_whomp half as likely to be served as the others).
144
-
145
- ### Priorities
200
+ Mountain goat will automatically break those down into three cases (:ten_percent, :big_winner, :whomp_whomp) and serve them out at random to the user.
146
201
 
147
- You may want to test certain items with a lower serve rate (bold new slogans). You can assign priorities to any metric variant. The change of a given metric variant being shown is
148
-
149
- my priority / sum(all priorities for this metric)
150
-
151
202
  ### Meta Options
152
203
 
153
204
  There is certain meta data that you may wish to collect for a number of different conversions. For example, you may want to track ip-address so you can later pivot this column to find new / returning users. To do this, add an initializer that calls MountainGoat.add_meta_option().
@@ -167,30 +218,50 @@ Then, when we track the conversion, you'll get meta-data for the user's ip-addre
167
218
  As mountain goat is a suite that is added into your project dynamically, the following routes and tables are added during setup:
168
219
 
169
220
  - Tables
170
- * ci_metas (indexes: ci_metas_cmt_data_index, ci_metas_cmt_index, ci_metas_rally_index)
171
- * convert_meta_types
172
- * converts
173
- * cs_metas (indexes: cs_metas_cmt_data_index, cs_metas_cmt_index, cs_metas_rally_index)
174
- * metric_variants
175
- * metrics
176
- * rallies
177
-
178
- - Routes
179
- * map.mg '/mg', :controller => :mountain_goat_converts, :action => :index
180
- * map.mg_login '/mg/login', :controller => :mountain_goat, :action => :login
181
- * map.mg_login_create '/mg/login/create', :controller => :mountain_goat, :action => :login_create
182
- * map.resources :mountain_goat_metric_variants
183
- * map.resources :mountain_goat_converts, :has_many => [ :mountain_goat_metrics, :mountain_goat_rallies ]
184
- * map.resources :mountain_goat_metrics, :has_many => :mountain_goat_metric_variants
185
- * map.resources :mountain_goat_rallies
186
- * map.new_rallies '/mg/rallies/new', :controller => :mountain_goat_rallies, :action => :new_rallies
187
- * map.fresh_metrics '/fresh-metrics', :controller => :mountain_goat_metrics, :action => :fresh_metrics
188
- * map.connect '/mg/public/:file', :controller => :mountain_goat, :action => :fetch
221
+ * mg_ci_metas (indexes: ci_metas_cmt_data_index, ci_metas_cmt_index, ci_metas_rally_index)
222
+ * mg_convert_meta_types
223
+ * mg_converts
224
+ * mg_cs_metas (indexes: cs_metas_cmt_data_index, cs_metas_cmt_index, cs_metas_rally_index)
225
+ * mg_metric_variants
226
+ * mg_metrics
227
+ * mg_rallies
228
+ * mg_report_items
229
+ * mg_reports
230
+
231
+ - Routes (all namespaced mg)
232
+ * mg.mg '/mg', :controller => :converts, :action => :index, :path_prefix => ""
233
+ * mg.login '/login', :controller => :mountain_goat, :action => :login
234
+ * mg.login_create '/login/create', :controller => :mountain_goat, :action => :login_create
235
+ * mg.resources :metric_variants
236
+ * mg.resources :converts, :has_many => [ :rallies ]
237
+ * mg.resources :metrics, :has_many => :metric_variants
238
+ * mg.resources :rallies, :collection => { :new_rallies => :get }
239
+ * mg.resources :reports, :has_many => :report_items, :member => { :show_svg => :get }
240
+ * mg.resources :report_items, :member => { :destroy => :get, :update => :post }, :collection => { :get_extra => :get }
241
+ * mg.resources :playground, :collection => { :test => :get }
242
+ * mg.new_rallies '/rallies/new', :controller => :rallies, :action => :new_rallies
243
+ * mg.fresh_metrics '/fresh-metrics', :controller => :metrics, :action => :fresh_metrics
244
+ * mg.connect '/public/:file', :controller => :mountain_goat, :action => :fetch
189
245
 
246
+ - Models
247
+ * Mg::CiMeta - Integer-typed meta data for Rallies (e.g. 'Click Count')
248
+ * Mg::ConvertMetaType - Meta-types for Rallies
249
+ * Mg::Convert - Goals (e.g. 'Page View', 'User Sign-up')
250
+ * Mg::CsMeta - String-typed meta data for Rallies (e.g. 'Referring domain')
251
+ * Mg::MetricVariant - Variant for a/b testing (e.g. 'Come see our store!')
252
+ * Mg::Metric - Type to vary for a/b testing (e.g. 'Homescreen Text')
253
+ * Mg::Rally - Instance of a goal conversion (e.g. when a user clicks sign up)
254
+ * Mg::ReportItem - Item to show in a report (e.g. Sign ups by day)
255
+ * Mg::Report - Report to deliver (e.g. collection of user report items)
256
+
257
+ ## Change log
258
+ 1.0.0 - Changed from a/b testing to multi-armed bandit
259
+ - Added Mountain Goat Reporting
260
+ - Added extensive test cases for stability
261
+
190
262
  ## TODO
191
263
  - Better documentation (rdocs)
192
- - Add namespacing to avoid conflicts
193
264
 
194
265
  ## Copyright
195
266
 
196
- Copyright (c) 2011 Geoffrey Hayes, drawn.to. Contact me, Geoff, <geoff@drawn.to> with any questions / ideas / enhacements. See LICENSE for details.
267
+ Copyright (c) 2011 Geoffrey Hayes, meloncard.com. Contact me, Geoff, <geoff@meloncard.com> with any questions / ideas / enhancements. See LICENSE for details.
@@ -1,9 +1,11 @@
1
1
  class MgGenerator < Rails::Generator::Base
2
2
  def add_options!(opt)
3
3
  opt.on('-p', '--password=password', String, "Your password to access Mountain Goat") { |v| options[:password] = v}
4
+ opt.on('-w', '--wkhtmltopdf=/path/to/dir', String, "Path to installation of wkhtmltopdf (optional)") { |v| options[:wkhtmltopdf] = v}
5
+ opt.on('-u', '--update=1.0.0', String, "If you have previously installed Mountain Goat, use to generate *update* tables.") { |v| options[:update] = v}
4
6
  puts <<-HELPFUL_INSTRUCTIONS
5
7
 
6
- Mountain Goat is your home for A/B testing in-house.
8
+ Mountain Goat is your home for in-house bandit testing.
7
9
 
8
10
  We have installed your configuration file to config/mountain-goat.yml
9
11
  Please choose a password for each environment in this file. Make
@@ -18,20 +20,26 @@ class MgGenerator < Rails::Generator::Base
18
20
  1) Choose an admin-password in config/mountain-goat.yml
19
21
  2) Run `rake db:migrate`
20
22
  3) Start your server and navigate to 'http://mydomain.com/mg'
21
- 4) Play around, read the docs, A/B test, enjoy.
23
+ 4) Play around, read the docs, bandit, enjoy.
22
24
 
23
25
  HELPFUL_INSTRUCTIONS
24
26
  end
25
27
 
26
28
  def manifest
27
- if options[:password].blank?
28
- password = ""
29
- else
30
- password = options[:password]
31
- end
29
+ password = options[:password] || ""
30
+ wkhtmltopdf = options[:wkhtmltopdf] || ""
31
+ update = options[:update]
32
+
32
33
  record do |m|
33
- #m.template 'mountain-goat.yml', 'config/mountain-goat.yml', :assigns => {:password => password}
34
- m.migration_template 'create_mountain_goat_tables.rb', 'db/migrate', { :migration_file_name => "create_mountain_goat_tables" }
34
+ m.template 'mountain_goat_reports.rake', 'lib/tasks/mountain_goat_reports.rake'
35
+ m.template 'mountain-goat.yml', 'config/mountain-goat.yml', :assigns => { :password => password, :wkhtmltopdf => wkhtmltopdf }
36
+
37
+ if !update.blank?
38
+ m.migration_template 'create_mountain_goat_tables.rb', 'db/migrate', { :migration_file_name => "create_mountain_goat_tables" }
39
+ elsif update == "1.0.0"
40
+ m.migration_template 'update_mountain_goat_tables.rb', 'db/migrate', { :migration_file_name => "update_mountain_goat_tables" }
41
+ end
42
+
35
43
  end
36
44
  end
37
45
 
@@ -1,7 +1,7 @@
1
1
  class CreateMountainGoatTables < ActiveRecord::Migration
2
2
 
3
3
  def self.up
4
- create_table "ci_metas", :force => true do |t|
4
+ create_table "mg_ci_metas", :force => true do |t|
5
5
  t.integer "rally_id"
6
6
  t.integer "convert_meta_type_id"
7
7
  t.integer "data"
@@ -9,11 +9,11 @@ class CreateMountainGoatTables < ActiveRecord::Migration
9
9
  t.datetime "updated_at"
10
10
  end
11
11
 
12
- add_index "ci_metas", ["convert_meta_type_id", "data"], :name => "ci_metas_cmt_data_index"
13
- add_index "ci_metas", ["convert_meta_type_id"], :name => "ci_metas_cmt_index"
14
- add_index "ci_metas", ["rally_id"], :name => "ci_metas_rally_index"
15
-
16
- create_table "convert_meta_types", :force => true do |t|
12
+ add_index "mg_ci_metas", ["convert_meta_type_id", "data"], :name => "ci_metas_cmt_data_index"
13
+ add_index "mg_ci_metas", ["convert_meta_type_id"], :name => "ci_metas_cmt_index"
14
+ add_index "mg_ci_metas", ["rally_id"], :name => "ci_metas_rally_index"
15
+
16
+ create_table "mg_convert_meta_types", :force => true do |t|
17
17
  t.integer "convert_id"
18
18
  t.string "name"
19
19
  t.string "var"
@@ -22,14 +22,16 @@ class CreateMountainGoatTables < ActiveRecord::Migration
22
22
  t.datetime "updated_at"
23
23
  end
24
24
 
25
- create_table "converts", :force => true do |t|
25
+ create_table "mg_converts", :force => true do |t|
26
26
  t.string "convert_type"
27
27
  t.datetime "created_at"
28
28
  t.datetime "updated_at"
29
29
  t.string "name"
30
+ t.float "reward", :default => 1.0
31
+ t.datetime "deleted_at"
30
32
  end
31
33
 
32
- create_table "cs_metas", :force => true do |t|
34
+ create_table "mg_cs_metas", :force => true do |t|
33
35
  t.integer "rally_id"
34
36
  t.integer "convert_meta_type_id"
35
37
  t.string "data"
@@ -37,11 +39,11 @@ class CreateMountainGoatTables < ActiveRecord::Migration
37
39
  t.datetime "updated_at"
38
40
  end
39
41
 
40
- add_index "cs_metas", ["convert_meta_type_id", "data"], :name => "cs_metas_cmt_data_index"
41
- add_index "cs_metas", ["convert_meta_type_id"], :name => "cs_metas_cmt_index"
42
- add_index "cs_metas", ["rally_id"], :name => "cs_metas_rally_index"
43
-
44
- create_table "metric_variants", :force => true do |t|
42
+ add_index "mg_cs_metas", ["convert_meta_type_id", "data"], :name => "cs_metas_cmt_data_index"
43
+ add_index "mg_cs_metas", ["convert_meta_type_id"], :name => "cs_metas_cmt_index"
44
+ add_index "mg_cs_metas", ["rally_id"], :name => "cs_metas_rally_index"
45
+
46
+ create_table "mg_metric_variants", :force => true do |t|
45
47
  t.integer "metric_id"
46
48
  t.text "value"
47
49
  t.text "opt1"
@@ -51,35 +53,69 @@ class CreateMountainGoatTables < ActiveRecord::Migration
51
53
  t.datetime "created_at"
52
54
  t.datetime "updated_at"
53
55
  t.string "name"
54
- t.float "priority", :default => 1.0, :null => false
55
56
  t.string "switch_type"
57
+ t.float "reward"
58
+ t.datetime "deleted_at"
56
59
  end
57
60
 
58
- create_table "metrics", :force => true do |t|
61
+ create_table "mg_metrics", :force => true do |t|
59
62
  t.string "metric_type"
60
63
  t.string "title"
61
64
  t.datetime "created_at"
62
65
  t.datetime "updated_at"
63
- t.integer "convert_id"
64
66
  t.boolean "tally_each_serve", :default => true
65
67
  t.boolean "is_switch", :default => false
68
+ t.datetime "deleted_at"
66
69
  end
67
-
68
- create_table "rallies", :force => true do |t|
70
+
71
+ create_table "mg_rallies", :force => true do |t|
69
72
  t.integer "convert_id"
70
73
  t.datetime "created_at"
71
74
  t.datetime "updated_at"
72
75
  end
76
+
77
+ create_table "mg_report_items", :force => true do |t|
78
+ t.integer "report_id"
79
+ t.integer "reportable_id"
80
+ t.string "reportable_type"
81
+ t.integer "order"
82
+ t.datetime "created_at"
83
+ t.datetime "updated_at"
84
+ t.string "pivot_type"
85
+ t.integer "pivot_id"
86
+ t.string "filter"
87
+ t.string "meta_type"
88
+ t.integer "meta_id"
89
+ t.string "extra"
90
+ t.integer "variant"
91
+ t.integer "subvariant"
92
+ t.datetime "start"
93
+ t.datetime "end"
94
+ t.integer "size"
95
+ end
96
+
97
+ create_table "mg_reports", :force => true do |t|
98
+ t.string "title"
99
+ t.text "description"
100
+ t.datetime "created_at"
101
+ t.datetime "updated_at"
102
+ t.string "delivery_set"
103
+ t.string "recipients"
104
+ t.datetime "deleted_at"
105
+ t.string "theme"
106
+ end
73
107
  end
74
108
 
75
109
  def self.down
76
- drop_table :ci_metas
77
- drop_table :convert_meta_types
78
- drop_table :converts
79
- drop_table :cs_metas
80
- drop_table :metric_variants
81
- drop_table :metrics
82
- drop_table :rallies
110
+ drop_table :mg_ci_metas
111
+ drop_table :mg_convert_meta_types
112
+ drop_table :mg_converts
113
+ drop_table :mg_cs_metas
114
+ drop_table :mg_metric_variants
115
+ drop_table :mg_metrics
116
+ drop_table :mg_rallies
117
+ drop_table :mg_report_items
118
+ drop_table :mg_reports
83
119
  end
84
120
 
85
121
  end
@@ -0,0 +1 @@
1
+ #Dir["#{Gem.searcher.find('facebooker').full_gem_path}/lib/tasks/*.rake"].each { |ext| load ext }
@@ -4,12 +4,31 @@
4
4
  #################
5
5
 
6
6
  # Add your access passwords for each environment
7
- #
7
+ #
8
+ # Settings: epsilon for e-greedy strategy
9
+ # strategy: one of [ 'e-greedy', 'e-greedy-decreasing', 'a/b' ]
10
+ # storage: ['cookies','session','none']
11
+
12
+ # wkhtmltopdf must be installed to use Mountain Goat Reporting
13
+ # Please specify the executable for wkhtmltopdf below
14
+ # E.g. (Linux) /usr/local/bin/wkhtmltopdf
15
+ # (Windows) C:\Program Files (x86)\wkhtmltopdf\wkhtmltopdf.exe
16
+ # wkhtmltopdf can be installed from http://code.google.com/p/wkhtmltopdf/
17
+
18
+ settings:
19
+ epsilon: 0.1
20
+ strategy: e-greedy
21
+ storage: cookies
22
+
8
23
  test:
9
- password: <%= password %>
24
+ password: <%= password %>
25
+ wkhtmltopdf: <%= wkhtmltopdf %>
10
26
  development:
11
27
  password: <%= password %>
28
+ wkhtmltopdf: <%= wkhtmltopdf %>
12
29
  staging:
13
30
  password: <%= password %>
31
+ wkhtmltopdf: <%= wkhtmltopdf %>
14
32
  production:
15
- password: <%= password %>
33
+ password: <%= password %>
34
+ wkhtmltopdf: <%= wkhtmltopdf %>