mountain-goat 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/README.md +84 -70
  2. data/generators/mg/mg_generator.rb +9 -3
  3. data/generators/mg/templates/create_mountain_goat_tables.rb +4 -0
  4. data/generators/mg/templates/mountain-goat.yml +1 -0
  5. data/lib/mountain-goat.rb +12 -12
  6. data/lib/mountain-goat/controllers/mg/mountain_goat_controller.rb +1 -1
  7. data/lib/mountain-goat/controllers/mg/report_items_controller.rb +5 -5
  8. data/lib/mountain-goat/controllers/mg/reports_controller.rb +28 -1
  9. data/lib/mountain-goat/metric_tracking.rb +4 -4
  10. data/lib/mountain-goat/models/mg/report.rb +4 -4
  11. data/lib/mountain-goat/models/mg/report_item.rb +2 -2
  12. data/lib/mountain-goat/models/mg/report_mailer.rb +7 -0
  13. data/lib/mountain-goat/public/jquery.raphael.js +25 -1
  14. data/lib/mountain-goat/public/mg.css +307 -316
  15. data/lib/mountain-goat/public/mg.js +17 -16
  16. data/lib/mountain-goat/public/mg.png +0 -0
  17. data/lib/mountain-goat/version.rb +1 -1
  18. data/lib/mountain-goat/views/mountain_goat/layouts/mountain_goat.html.erb +21 -20
  19. data/lib/mountain-goat/views/mountain_goat/mg/mountain_goat/login.html.erb +11 -19
  20. data/lib/mountain-goat/views/mountain_goat/mg/report_items/_report_item_form.html.erb +1 -1
  21. data/lib/mountain-goat/views/mountain_goat/mg/report_items/_report_item_pivot_form.html.erb +4 -4
  22. data/lib/mountain-goat/views/mountain_goat/mg/reports/_report_form.html.erb +7 -2
  23. data/lib/mountain-goat/views/mountain_goat/mg/reports/_report_report_items.html.erb +1 -1
  24. data/lib/mountain-goat/views/mountain_goat/mg/reports/edit.html.erb +27 -30
  25. data/lib/mountain-goat/views/mountain_goat/mg/reports/index.html.erb +24 -20
  26. data/lib/mountain-goat/views/mountain_goat/mg/reports/new.html.erb +14 -13
  27. data/lib/mountain-goat/views/mountain_goat/mg/reports/show.html.erb +10 -13
  28. data/mountain-goat.gemspec +2 -2
  29. metadata +31 -54
  30. data/lib/mountain-goat/controllers/mg/metric_variants_controller.rb +0 -81
  31. data/lib/mountain-goat/controllers/mg/metrics_controller.rb +0 -110
  32. data/lib/mountain-goat/controllers/mg/rallies_controller.rb +0 -43
  33. data/lib/mountain-goat/models/mg/ci_meta.rb +0 -10
  34. data/lib/mountain-goat/models/mg/convert.rb +0 -147
  35. data/lib/mountain-goat/models/mg/convert_meta_type.rb +0 -20
  36. data/lib/mountain-goat/models/mg/cs_meta.rb +0 -10
  37. data/lib/mountain-goat/models/mg/metric.rb +0 -8
  38. data/lib/mountain-goat/models/mg/metric_variant.rb +0 -25
  39. data/lib/mountain-goat/models/mg/rally.rb +0 -59
  40. data/lib/mountain-goat/views/mountain_goat/mg/converts/.tmp_show.html.erb.4433~ +0 -69
  41. data/lib/mountain-goat/views/mountain_goat/mg/converts/_convert_form.html.erb +0 -26
  42. data/lib/mountain-goat/views/mountain_goat/mg/converts/_convert_meta_type_form.html.erb +0 -27
  43. data/lib/mountain-goat/views/mountain_goat/mg/converts/edit.html.erb +0 -13
  44. data/lib/mountain-goat/views/mountain_goat/mg/converts/index.html.erb +0 -25
  45. data/lib/mountain-goat/views/mountain_goat/mg/converts/new.html.erb +0 -13
  46. data/lib/mountain-goat/views/mountain_goat/mg/converts/show.html.erb +0 -44
  47. data/lib/mountain-goat/views/mountain_goat/mg/metric_variants/_metric_variant_form.html.erb +0 -37
  48. data/lib/mountain-goat/views/mountain_goat/mg/metric_variants/edit.html.erb +0 -13
  49. data/lib/mountain-goat/views/mountain_goat/mg/metric_variants/index.html.erb +0 -34
  50. data/lib/mountain-goat/views/mountain_goat/mg/metric_variants/new.html.erb +0 -15
  51. data/lib/mountain-goat/views/mountain_goat/mg/metric_variants/show.html.erb +0 -30
  52. data/lib/mountain-goat/views/mountain_goat/mg/metrics/.tmp_show.html.erb.21270~ +0 -68
  53. data/lib/mountain-goat/views/mountain_goat/mg/metrics/_metric_form.html.erb +0 -24
  54. data/lib/mountain-goat/views/mountain_goat/mg/metrics/edit.html.erb +0 -13
  55. data/lib/mountain-goat/views/mountain_goat/mg/metrics/index.html.erb +0 -14
  56. data/lib/mountain-goat/views/mountain_goat/mg/metrics/new.html.erb +0 -14
  57. data/lib/mountain-goat/views/mountain_goat/mg/metrics/show.html.erb +0 -67
  58. data/lib/mountain-goat/views/mountain_goat/mg/rallies/.tmp__rally.html.erb.40484~ +0 -12
  59. data/lib/mountain-goat/views/mountain_goat/mg/rallies/_rallies.html.erb +0 -5
  60. data/lib/mountain-goat/views/mountain_goat/mg/rallies/_rallies_form.html.erb +0 -21
  61. data/lib/mountain-goat/views/mountain_goat/mg/rallies/_rally.html.erb +0 -12
  62. data/lib/mountain-goat/views/mountain_goat/mg/rallies/edit.html.erb +0 -13
  63. data/lib/mountain-goat/views/mountain_goat/mg/rallies/index.html.erb +0 -19
  64. data/lib/mountain-goat/views/mountain_goat/mg/rallies/new.html.erb +0 -13
  65. data/lib/mountain-goat/views/mountain_goat/mg/rallies/show.html.erb +0 -14
data/README.md CHANGED
@@ -8,15 +8,15 @@ Add simple hooks in your code to display a/b metrics
8
8
 
9
9
  <%= bd(:homescreen_text, "Welcome here") %>
10
10
 
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.
11
+ This creates a database entry for your a/b test "homescreen_text". Visit "http://yourdomain.com/mg" and you can add / adjust choices for this text. When a user completes a goal, you run the following code.
12
12
 
13
13
  #e.g. users_controller.rb
14
14
  def create
15
- record_conversion(:user_signup)
15
+ rw(:user_signup, 10) # Reward 10 points
16
16
  ...
17
17
  end
18
18
 
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.
19
+ This will track a record not only for the goal, but for the choice of "homescreen_text" that the user was served when he or she came to the home-page.
20
20
 
21
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!
22
22
 
@@ -27,10 +27,10 @@ The best part? The Mountain-Goat Administrative console is located on your serv
27
27
  - Similar to Bayesian learning, Multi-armed bandit solutions will automatically deliver the highest performing variants
28
28
  - This is done while still achieving minimal (logarithmically decreasing) regret (showing of poorer performing variants)
29
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.
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 ...)
30
+ - Visually analyze the your choices; change them on the fly, adding new ones or kicking out poor performers.
31
+ - Watch goals complete in real-time with live-action console (grab the popcorn and watch how your users "sign up" and "view items" and ...)
32
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!")
33
- - Track goals with meta data (record_conversion(:user_signup, :referrer => request.env['HTTP_REFERER']))
33
+ - Track goals with meta data (rw(:user_signup, 10, :referrer => request.env['HTTP_REFERER']))
34
34
  * Mountain goat tracks as much arbitrary meta data as you want
35
35
  * You are presented with charts for each goal, broken down by meta
36
36
  * See what referrers are driving goals, or which of your blog posts are drawing an audience
@@ -40,13 +40,13 @@ For more information, read my blog post on how mountain goat quickly accomplishe
40
40
 
41
41
  ## Upgrade from < 1.0.0
42
42
 
43
- If you are upgrading from Mountain Goat < 1.0.0, please run the following command (please overwrite mountain-goat.yml when prompted):
43
+ If you are upgrading from Mountain Goat < 1.0.1, please run the following command (please overwrite mountain-goat.yml when prompted):
44
+
45
+ ./script/generate mg --update=1.0.0,1.0.1
44
46
 
45
- ./script/generate mg --update=1.0.0
46
-
47
47
  rake db:migrate
48
48
 
49
- This will install new migrations necessary for version 1.0.0.
49
+ This will install new migrations necessary for version 1.0.1. Leave out the 1.0.0 from the command if you are upgrading from version 1.0.0.
50
50
 
51
51
  ## Install
52
52
 
@@ -79,9 +79,9 @@ Run your new migration
79
79
 
80
80
  Mountain Goat hinges around three core concepts:
81
81
 
82
- - Conversions are what you want E.g. "user purchases coffee"
83
- - Metrics are how you draw people to convert E.g. "a banner on the store-front"
84
- - Metric variants are A/B tests for metrics E.g. "free coffee" "chuck norris is inside"
82
+ - Goals are what you want E.g. "user purchases coffee"
83
+ - Tests are how you draw people to complete a goal E.g. "a banner on the store-front"
84
+ - Choices are A/B tests for tests E.g. "free coffee" "chuck norris inside"
85
85
 
86
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.
87
87
 
@@ -91,9 +91,9 @@ The bandit (bd) function takes two parameters:
91
91
 
92
92
  bd(metric_name, default)
93
93
 
94
- This will automatically create a metric and populate a metric variant with the default value. Easy, eh?
94
+ This will automatically create a `test` and populate a `choice` with the default value. Easy, eh?
95
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.
96
+ From here, you can go into the mountain goat admin center and add new `choices` to fit your need. It's all built into *your* application, in house.
97
97
 
98
98
  http://{your_rails_app}/mg (e.g. if you're at railsrocks.com, then visit http://railsrocks.com/mg)
99
99
 
@@ -104,7 +104,7 @@ The other important code you'll need to implement is to tell the system when a g
104
104
  ...
105
105
  end
106
106
 
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.
107
+ This will go in and record a goal (a `record`) for a user purchasing coffee. Further, it will track a hit for any choices served to that user. For example "Chuck Norris works here" might get reward points. You will see which test choices lead to a goal; this is the core of A/B and bandit testing.
108
108
 
109
109
  ## Bandit Testing
110
110
 
@@ -130,34 +130,40 @@ A small variant on this is called epsilon-greedy-decreasing, which reduces epsil
130
130
 
131
131
  You can configure these options in `mountain-goat.yml`.
132
132
 
133
- ## Mountain Goat admin suite
133
+ ## Mountain Goat Admin Suite
134
134
 
135
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.
136
136
 
137
- The front page gives you a breakdown of each of your Goals, and the efficacy of each metric and metric-variant. Select a given metric to drill into its variants. Once you are in a specific metric, you'll be able to add new metric-variants and see what works best for your clients.
137
+ The front page gives you a breakdown of each of your Goals, and the efficacy of each test and its choices. Select a given test to drill into its choices. Once you are in a specific test page, you'll be able to add new choices and see what works best for your clients.
138
138
 
139
139
  ###Goals
140
140
 
141
141
  Goals show you what users are doing. Are they purchasing coffee? Are they logging in? Are they posting flames on your message board? You can measure all of these things!
142
142
 
143
- In the Goals section, you'll get a break down of your goals and what metrics are leading in conversions. Don't see anything here? Add some metrics / goals / metric-variants from the code above. Hint: Add meta data (see below) to see meta data associated with your conversions
143
+ In the Goals section, you'll get a break down of your goals and what tests are leading in reward points. Don't see anything here? Add some tests / goals / choices from the code above. Hint: Add meta data (see below) to see meta data associated with your records.
144
144
 
145
- ###Metrics
145
+ ###Tests
146
146
 
147
- Go to "Metrics" and visit a specific metric. You'll see which metric variants are getting the highest conversion rates. Does having the font on the homescreen large draw more people into signing up, or does it turn people away? This is where you can check and see. Click 'New variant' below to add additional variants for testing.
147
+ Go to `Tests` and visit a specific test. You'll see which choices are getting the highest rewards. Does having a large font on the homescreen draw more people into signing up, or does it turn people away? This is where you can check and see. Click 'New Choice' below to add additional variants for testing.
148
148
 
149
- * Click into a metric to explore (see above on how to create metrics from within your code-base)
150
- * Charts show you visually which variants are doing better than others
151
- * "Add variant" to add new variants for this metric
149
+ * Click into a test to explore (see above on how to create tests from within your code-base)
150
+ * Charts show you visually which choices are doing better than others
151
+ * "Add Choice" to add new variants for this test
152
152
 
153
- ###Rallies
153
+ ###Records
154
154
 
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.
155
+ Records shows you what's going on, in real time. You will see goals being hit by your clients in real time. Grab a bag of popcorn and watch users struggle (or glide) across your site. Add meta data to get further information. This page automatically updates as new records come in.
156
156
 
157
157
  ###Reports
158
158
 
159
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
160
 
161
+ Funnel reports will display your goals as a flow of conversions across the site. For instance, you may choose "hits" then "sign ups" then "purchases" and see how the funnel changes through these different goals.
162
+
163
+ ####Daily / Weekly Reports
164
+
165
+ To get reports delivered to your inbox, first install the following gems:
166
+
161
167
  gem install pdfkit
162
168
  gem install svg-graph
163
169
 
@@ -172,17 +178,17 @@ Finally, simply set up a cron task on your system when you would like your repor
172
178
 
173
179
  ### Meta data
174
180
 
175
- You can track meta-data with any conversion. E.g.
181
+ You can track meta-data with any goal. E.g.
176
182
 
177
- rc(:user_visit, :referring_domain => request.env['HTTP_REFERER'], :user_id => session[:user_id])
183
+ rw(:user_visit, 10, :referring_domain => request.env['HTTP_REFERER'], :user_id => session[:user_id])
178
184
 
179
- These will be stored with the rally for the conversion and can get used for complex analytics down the line. (see Converts.meta)
185
+ These will be stored with the record for the goal and can get used for complex analytics down the line. (see Goals.meta)
180
186
 
181
187
  ### Switch variants
182
188
 
183
189
  Instead of just serving text, you can also serve flow control in Mountain Goat, like so:
184
190
 
185
- sv(:user_discount, :purchase_coffee) do |variant|
191
+ bds(:user_discount, :purchase_coffee) do |variant|
186
192
 
187
193
  variant.ten_percent do # "ten_percent" is the variant-name
188
194
  discount = 0.10
@@ -197,64 +203,72 @@ Instead of just serving text, you can also serve flow control in Mountain Goat,
197
203
  end
198
204
  end
199
205
 
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.
206
+ Mountain goat will automatically break those down into three cases (:ten_percent, :big_winner, :whomp_whomp) and serve them out to users using bandit methodology.
201
207
 
202
208
  ### Meta Options
203
209
 
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().
210
+ There is certain meta data that you may wish to collect for a number of different goals. 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().
205
211
 
206
212
  MountainGoat.add_meta_option(:stats) do |c|
207
213
  { :ip => c.request.remote_ip }
208
214
  end
209
215
 
210
- Then, simply add ':stats => true' to your record_conversion call. This will call into your block and replace the key-pair with the map returned from the block. E.g.
216
+ Then, simply add ':stats => true' to your `rw` call. This will call into your block and replace the key-pair with the map returned from the block. E.g.
211
217
 
212
- record_conversion(:user_login, :login => @user.login, :stats => true)
218
+ rw(:user_login, 20, :login => @user.login, :stats => true)
213
219
 
214
- Then, when we track the conversion, you'll get meta-data for the user's ip-address. You can add any number of "meta-options" that you would like.
220
+ Then, when we track the conversion, you'll get meta-data for the user's IP-address. You can add any number of "meta-options" that you would like.
215
221
 
216
- ## Technical
222
+ ### Mountain-Goat.yml
217
223
 
218
- As mountain goat is a suite that is added into your project dynamically, the following routes and tables are added during setup:
224
+ You can configure Mountain-Goat configuration through `config/mountain-goat.yml`. There are application-wide settings and environment-specific settings. E.g.
219
225
 
220
- - Tables
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
226
+ settings:
227
+ epsilon: 0.85
228
+ strategy: e-greedy
229
+ storage: session
230
+
231
+ development:
232
+ password: your-password
233
+ wkhtmltopdf: /usr/local/bin/wkhtmltopdf
234
+
235
+ - Settings:
236
+ * `Epsilon` - How often should Bandit deliver the best `choice` versus a random `choice`. The idea is that we want to deliver the best choice ("Free Donuts When you Sign-Up") versus new (or less successful) choices ("Now Arsenic Free!"). 0.85 means the system will deliver the best result 85% of the time (for strategy `e-greedy`)
237
+ * `Strategy` - Choice of "e-greedy", "e-greedy-decreasing" or "a/b". For E-Greedy, as above, best case will be delivered epsilon-percent of the time. For E-Greedy decreasing, this will decrease over time (so after a thousand tests, only the best result will be displayed). Finally, a/b will ignore epsilon and always deliver a random choice.
238
+ * `Storage` - Should we store user choices in `session` or through `cookies`? This is used to track the choices delivered to a user so we can reward points to these choices when the user completes a goal. Cookies will also help ensure the page is consistent across many sessions (e.g. if you a/b test the background image, do you want it to be the same the next day for a user)
239
+ - Environment:
240
+ * `Password` - Password to access Mountain Goat Admin Suite on this environment. Choose a very secure password (a mix of letters, numbers, and symbols).
241
+ * `wkhtmltopdf` - Path to wkhtmltopdf executable for reports. E.g. *Nix: `/usr/local/bin/wkhtmltopdf`, Windows: `C:\Program Files (x86)\wkhtmltopdf\wkhtmltopdf.exe`
242
+
243
+ ## Technical
230
244
 
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
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)
245
+ As mountain goat is a suite that is added into your project dynamically, the following models and tables are added during setup:
246
+
247
+ - ActiveRecord Models
248
+ * Mg::GiMeta - Integer-typed meta data for Records (e.g. 'Click Count')
249
+ * Mg::GoalMetaType - Meta-types for Records
250
+ * Mg::Goal - Goals (e.g. 'Page View', 'User Sign-up')
251
+ * Mg::GsMeta - String-typed meta data for Records (e.g. 'Referring domain')
252
+ * Mg::Choice - Variant for a/b testing (e.g. 'Come see our store!')
253
+ * Mg::Test - Test to vary for a/b testing (e.g. 'Homescreen Text')
254
+ * Mg::Record - Instance of a goal completion (e.g. when a user clicks sign up)
254
255
  * Mg::ReportItem - Item to show in a report (e.g. Sign ups by day)
255
256
  * Mg::Report - Report to deliver (e.g. collection of user report items)
257
+
258
+ - Database Tables
259
+ * mg_gi_metas
260
+ * mg_goal_meta_types
261
+ * mg_goals
262
+ * mg_gs_metas
263
+ * mg_choices
264
+ * mg_tests
265
+ * mg_records
266
+ * mg_report_items
267
+ * mg_reports
256
268
 
257
269
  ## Change log
270
+ 1.0.1 - Renamed objects to reflect real-world thinking (e.g. metric => test)
271
+ - Fixed glitch in bandit choice selection
258
272
  1.0.0 - Changed from a/b testing to multi-armed bandit
259
273
  - Added Mountain Goat Reporting
260
274
  - Added extensive test cases for stability
@@ -2,7 +2,7 @@ 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
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}
5
+ opt.on('-u', '--update=1.0.0,1.0.1', String, "If you have previously installed Mountain Goat, use to generate *update* tables.") { |v| options[:update] = v}
6
6
  puts <<-HELPFUL_INSTRUCTIONS
7
7
 
8
8
  Mountain Goat is your home for in-house bandit testing.
@@ -34,12 +34,18 @@ class MgGenerator < Rails::Generator::Base
34
34
  m.template 'mountain_goat_reports.rake', 'lib/tasks/mountain_goat_reports.rake'
35
35
  m.template 'mountain-goat.yml', 'config/mountain-goat.yml', :assigns => { :password => password, :wkhtmltopdf => wkhtmltopdf }
36
36
 
37
- if !update.blank?
37
+ if update.blank?
38
38
  m.migration_template 'create_mountain_goat_tables.rb', 'db/migrate', { :migration_file_name => "create_mountain_goat_tables" }
39
- elsif update == "1.0.0"
39
+ end
40
+
41
+ if update.include?("1.0.0")
40
42
  m.migration_template 'update_mountain_goat_tables.rb', 'db/migrate', { :migration_file_name => "update_mountain_goat_tables" }
41
43
  end
42
44
 
45
+ if update.include?("1.0.1")
46
+ m.migration_template 'update_mountain_goat_tables_v2.rb', 'db/migrate', { :migration_file_name => "update_mountain_goat_tables_v2" }
47
+ end
48
+
43
49
  end
44
50
  end
45
51
 
@@ -103,6 +103,10 @@ class CreateMountainGoatTables < ActiveRecord::Migration
103
103
  t.string "recipients"
104
104
  t.datetime "deleted_at"
105
105
  t.string "theme"
106
+ t.string "report_type", :default => "graph"
107
+ t.text "report_opts"
108
+ t.string "meta"
109
+ t.string "meta2"
106
110
  end
107
111
  end
108
112
 
@@ -19,6 +19,7 @@ settings:
19
19
  epsilon: 0.1
20
20
  strategy: e-greedy
21
21
  storage: cookies
22
+ email-from:
22
23
 
23
24
  test:
24
25
  password: <%= password %>
data/lib/mountain-goat.rb CHANGED
@@ -8,24 +8,24 @@ require File.join([File.dirname(__FILE__), 'mgflotilla'])
8
8
 
9
9
  require File.join([File.dirname(__FILE__), 'mountain-goat/controllers/mg/mg'])
10
10
  require File.join([File.dirname(__FILE__), 'mountain-goat/controllers/mg/mountain_goat_controller'])
11
- require File.join([File.dirname(__FILE__), 'mountain-goat/controllers/mg/metrics_controller'])
12
- require File.join([File.dirname(__FILE__), 'mountain-goat/controllers/mg/metric_variants_controller'])
13
- require File.join([File.dirname(__FILE__), 'mountain-goat/controllers/mg/converts_controller'])
14
- require File.join([File.dirname(__FILE__), 'mountain-goat/controllers/mg/rallies_controller'])
11
+ require File.join([File.dirname(__FILE__), 'mountain-goat/controllers/mg/tests_controller'])
12
+ require File.join([File.dirname(__FILE__), 'mountain-goat/controllers/mg/choices_controller'])
13
+ require File.join([File.dirname(__FILE__), 'mountain-goat/controllers/mg/goals_controller'])
14
+ require File.join([File.dirname(__FILE__), 'mountain-goat/controllers/mg/records_controller'])
15
15
  require File.join([File.dirname(__FILE__), 'mountain-goat/controllers/mg/reports_controller'])
16
16
  require File.join([File.dirname(__FILE__), 'mountain-goat/controllers/mg/report_items_controller'])
17
17
  require File.join([File.dirname(__FILE__), 'mountain-goat/controllers/mg/playground_controller'])
18
18
 
19
19
  require File.join([File.dirname(__FILE__), 'mountain-goat/m_g'])
20
20
  require File.join([File.dirname(__FILE__), 'mountain-goat/analytics'])
21
- require File.join([File.dirname(__FILE__), 'mountain-goat/metric_tracking'])
22
- require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/metric'])
23
- require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/metric_variant'])
24
- require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/convert'])
25
- require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/convert_meta_type'])
26
- require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/cs_meta'])
27
- require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/ci_meta'])
28
- require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/rally'])
21
+ require File.join([File.dirname(__FILE__), 'mountain-goat/mg_core'])
22
+ require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/test'])
23
+ require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/choice'])
24
+ require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/goal'])
25
+ require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/goal_meta_type'])
26
+ require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/gs_meta'])
27
+ require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/gi_meta'])
28
+ require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/record'])
29
29
  require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/report'])
30
30
  require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/report_mailer'])
31
31
  require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/report_item'])
@@ -6,7 +6,7 @@ class Mg::MountainGoatController < Mg
6
6
  def fetch
7
7
  ct = { :png => 'image/png', :css => 'text/css', :html => 'text/html', :js => 'text/javascript' }
8
8
 
9
- raise ArgumentError, "Invalid fetch file" if params[:file].match(/(([_][_])|([^a-z0-9_]))/ix) #extra security
9
+ raise ArgumentError, "Invalid fetch file" if params[:file].match(/(([_][_])|([^a-z0-9_-]))/ix) #extra security
10
10
 
11
11
  #We will only serve files located in the public directory for security reasons
12
12
  Dir.open(File.join([File.dirname(__FILE__), '../../public/'])).each do |file|
@@ -21,8 +21,8 @@ class Mg::ReportItemsController < Mg
21
21
  @report = Mg::Report.find(params[:report_id])
22
22
  raise ArgumentError, "Invalid report" if @report.nil?
23
23
 
24
- @report_item = @report.report_items.new(params[:report_item].clone.delete_if { |k, v| k.intern == :reportable || k.intern == :pivot } )
25
- @report_item.order = @report.report_items.to_a.map { |ri| ri.order }.push(0).max + 1#@report.report_items.maximum(:order) + 1 -- weird sqlite3 bugs
24
+ @report_item = @report.mg_report_items.new(params[:report_item].clone.delete_if { |k, v| k.intern == :reportable || k.intern == :pivot } )
25
+ @report_item.order = @report.mg_report_items.to_a.map { |ri| ri.order }.push(0).max + 1#@report.report_items.maximum(:order) + 1 -- weird sqlite3 bugs
26
26
 
27
27
  if !params[:report_item][:reportable].blank?
28
28
  id, model = params[:report_item][:reportable].split('-')
@@ -38,7 +38,7 @@ class Mg::ReportItemsController < Mg
38
38
  render :json => { :success => true,
39
39
  :close_dialog => true,
40
40
  :result => "<span>Successfully added report item</span>",
41
- :also => [ { :item => ".report-report-items", :result => render_to_string( :partial => "mg/reports/report_report_items", :locals => { :report => @report_item.report } ) } ] }
41
+ :also => [ { :item => ".report-report-items", :result => render_to_string( :partial => "mg/reports/report_report_items", :locals => { :report => @report_item.mg_report } ) } ] }
42
42
  else
43
43
  render :json => { :success => true,
44
44
  :result => render_to_string(:action => :new, :layout => 'xhr') }
@@ -47,7 +47,7 @@ class Mg::ReportItemsController < Mg
47
47
 
48
48
  def edit
49
49
  @report_item = Mg::ReportItem.find(params[:id])
50
- @report = @report_item.report
50
+ @report = @report_item.mg_report
51
51
 
52
52
  render :json => { :success => true,
53
53
  :result => render_to_string(:action => :edit, :layout => 'xhr') }
@@ -71,7 +71,7 @@ class Mg::ReportItemsController < Mg
71
71
  render :json => { :success => true,
72
72
  :close_dialog => true,
73
73
  :result => "<span>Successfully updated report item</span>",
74
- :also => [ { :item => ".report-report-items", :result => render_to_string( :partial => "mg/reports/report_report_items", :locals => { :report => @report_item.report }) } ] }
74
+ :also => [ { :item => ".report-report-items", :result => render_to_string( :partial => "mg/reports/report_report_items", :locals => { :report => @report_item.mg_report }) } ] }
75
75
  else
76
76
  render :json => { :success => true,
77
77
  :result => render_to_string(:action => :edit, :layout => 'xhr') }
@@ -2,7 +2,8 @@ class Mg::ReportsController < Mg
2
2
  # GET /mg_reports
3
3
  # GET /mg_reports.xml
4
4
  def index
5
- @reports = Mg::Report.all
5
+ @reports = Mg::Report.find( :all, :conditions => { :deleted_at => nil } )
6
+ @hidden_reports = Mg::Report.find( :all, :conditions => "deleted_at IS NOT NULL" )
6
7
 
7
8
  respond_to do |format|
8
9
  format.html # index.html.erb
@@ -76,6 +77,32 @@ class Mg::ReportsController < Mg
76
77
  end
77
78
  end
78
79
 
80
+ # GET /mg/reports/1/hide
81
+ # GET /mg/reports/1/hide.xml
82
+ def hide
83
+ @report = Mg::Report.find(params[:id])
84
+ @report.update_attribute(:deleted_at, Time.new)
85
+ flash[:notice] = "Report #{@report.title} has been hidden."
86
+
87
+ respond_to do |format|
88
+ format.html { redirect_to mg_reports_url }
89
+ format.xml { head :ok }
90
+ end
91
+ end
92
+
93
+ # GET /mg/reports/1/unhide
94
+ # GET /mg/reports/1/unhide.xml
95
+ def unhide
96
+ @report = Mg::Report.find(params[:id])
97
+ @report.update_attribute(:deleted_at, nil)
98
+ flash[:notice] = "Report #{@report.title} has been restored."
99
+
100
+ respond_to do |format|
101
+ format.html { redirect_to mg_reports_url }
102
+ format.xml { head :ok }
103
+ end
104
+ end
105
+
79
106
  # DELETE /mg_reports/1
80
107
  # DELETE /mg_reports/1.xml
81
108
  def destroy
@@ -76,14 +76,14 @@ module MetricTracking
76
76
  def mg_apply_strategy(metric)
77
77
  case mg_strategy.downcase
78
78
  when 'e-greedy'
79
- logger.warn Mg::MetricVariant.all(:order => "CASE WHEN served = 0 THEN 1 ELSE 0 END DESC, CASE WHEN #{mg_rand} < #{mg_epsilon.to_f} THEN #{mg_rand} ELSE CASE WHEN served = 0 THEN -1 ELSE reward / served END END DESC, #{mg_rand} DESC", :conditions => { :metric_id => metric.id } )
80
- return Mg::MetricVariant.first(:order => "CASE WHEN served = 0 THEN 1 ELSE 0 END DESC, CASE WHEN #{mg_rand} < #{mg_epsilon.to_f} THEN #{mg_rand} ELSE CASE WHEN served = 0 THEN -1 ELSE reward / served END END DESC, #{mg_rand} DESC", :conditions => { :metric_id => metric.id } )
79
+ logger.warn Mg::MetricVariant.all(:order => "CASE WHEN served = 0 THEN 1 ELSE 0 END DESC, CASE WHEN #{rand.to_f} < #{mg_epsilon.to_f} THEN #{mg_rand} ELSE CASE WHEN served = 0 THEN -1 ELSE reward / served END END DESC, #{mg_rand} DESC", :conditions => { :metric_id => metric.id } )
80
+ return Mg::MetricVariant.first(:order => "CASE WHEN served = 0 THEN 1 ELSE 0 END DESC, CASE WHEN #{rand.to_f} < #{mg_epsilon.to_f} THEN #{mg_rand} ELSE CASE WHEN served = 0 THEN -1 ELSE reward / served END END DESC, #{mg_rand} DESC", :conditions => { :metric_id => metric.id } )
81
81
  when 'e-greedy-decreasing'
82
82
  return Mg::MetricVariant.first(:order => "CASE WHEN served = 0 THEN 1 ELSE 0 END DESC,
83
- CASE WHEN #{mg_rand} < #{mg_epsilon.to_f} / ( select sum(served) from mg_metric_variants where metric_id = #{ metric.id.to_i } ) THEN #{mg_rand} ELSE CASE WHEN served = 0 THEN -1 ELSE reward / served END END DESC,
83
+ CASE WHEN #{rand.to_f} < #{mg_epsilon.to_f} / ( select sum(served) from mg_metric_variants where metric_id = #{ metric.id.to_i } ) THEN #{mg_rand} ELSE CASE WHEN served = 0 THEN -1 ELSE reward / served END END DESC,
84
84
  #{mg_rand} DESC", :conditions => { :metric_id => metric.id } ) # * log( ( select sum(served) from mg_metric_variants where metric_id = #{ metric.id.to_i } ) )
85
85
  when 'a/b'
86
- return Mg::MetricVariant.first(:order => "#{mg_rand} DESC", :conditions => { :metric_id => metric.id } )
86
+ return Mg::MetricVariant.first(:order => "#{rand.to_f} DESC", :conditions => { :metric_id => metric.id } )
87
87
  else
88
88
  raise "Invalid strategy #{mg_strategy}"
89
89
  end