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.
- data/README.md +84 -70
- data/generators/mg/mg_generator.rb +9 -3
- data/generators/mg/templates/create_mountain_goat_tables.rb +4 -0
- data/generators/mg/templates/mountain-goat.yml +1 -0
- data/lib/mountain-goat.rb +12 -12
- data/lib/mountain-goat/controllers/mg/mountain_goat_controller.rb +1 -1
- data/lib/mountain-goat/controllers/mg/report_items_controller.rb +5 -5
- data/lib/mountain-goat/controllers/mg/reports_controller.rb +28 -1
- data/lib/mountain-goat/metric_tracking.rb +4 -4
- data/lib/mountain-goat/models/mg/report.rb +4 -4
- data/lib/mountain-goat/models/mg/report_item.rb +2 -2
- data/lib/mountain-goat/models/mg/report_mailer.rb +7 -0
- data/lib/mountain-goat/public/jquery.raphael.js +25 -1
- data/lib/mountain-goat/public/mg.css +307 -316
- data/lib/mountain-goat/public/mg.js +17 -16
- data/lib/mountain-goat/public/mg.png +0 -0
- data/lib/mountain-goat/version.rb +1 -1
- data/lib/mountain-goat/views/mountain_goat/layouts/mountain_goat.html.erb +21 -20
- data/lib/mountain-goat/views/mountain_goat/mg/mountain_goat/login.html.erb +11 -19
- data/lib/mountain-goat/views/mountain_goat/mg/report_items/_report_item_form.html.erb +1 -1
- data/lib/mountain-goat/views/mountain_goat/mg/report_items/_report_item_pivot_form.html.erb +4 -4
- data/lib/mountain-goat/views/mountain_goat/mg/reports/_report_form.html.erb +7 -2
- data/lib/mountain-goat/views/mountain_goat/mg/reports/_report_report_items.html.erb +1 -1
- data/lib/mountain-goat/views/mountain_goat/mg/reports/edit.html.erb +27 -30
- data/lib/mountain-goat/views/mountain_goat/mg/reports/index.html.erb +24 -20
- data/lib/mountain-goat/views/mountain_goat/mg/reports/new.html.erb +14 -13
- data/lib/mountain-goat/views/mountain_goat/mg/reports/show.html.erb +10 -13
- data/mountain-goat.gemspec +2 -2
- metadata +31 -54
- data/lib/mountain-goat/controllers/mg/metric_variants_controller.rb +0 -81
- data/lib/mountain-goat/controllers/mg/metrics_controller.rb +0 -110
- data/lib/mountain-goat/controllers/mg/rallies_controller.rb +0 -43
- data/lib/mountain-goat/models/mg/ci_meta.rb +0 -10
- data/lib/mountain-goat/models/mg/convert.rb +0 -147
- data/lib/mountain-goat/models/mg/convert_meta_type.rb +0 -20
- data/lib/mountain-goat/models/mg/cs_meta.rb +0 -10
- data/lib/mountain-goat/models/mg/metric.rb +0 -8
- data/lib/mountain-goat/models/mg/metric_variant.rb +0 -25
- data/lib/mountain-goat/models/mg/rally.rb +0 -59
- data/lib/mountain-goat/views/mountain_goat/mg/converts/.tmp_show.html.erb.4433~ +0 -69
- data/lib/mountain-goat/views/mountain_goat/mg/converts/_convert_form.html.erb +0 -26
- data/lib/mountain-goat/views/mountain_goat/mg/converts/_convert_meta_type_form.html.erb +0 -27
- data/lib/mountain-goat/views/mountain_goat/mg/converts/edit.html.erb +0 -13
- data/lib/mountain-goat/views/mountain_goat/mg/converts/index.html.erb +0 -25
- data/lib/mountain-goat/views/mountain_goat/mg/converts/new.html.erb +0 -13
- data/lib/mountain-goat/views/mountain_goat/mg/converts/show.html.erb +0 -44
- data/lib/mountain-goat/views/mountain_goat/mg/metric_variants/_metric_variant_form.html.erb +0 -37
- data/lib/mountain-goat/views/mountain_goat/mg/metric_variants/edit.html.erb +0 -13
- data/lib/mountain-goat/views/mountain_goat/mg/metric_variants/index.html.erb +0 -34
- data/lib/mountain-goat/views/mountain_goat/mg/metric_variants/new.html.erb +0 -15
- data/lib/mountain-goat/views/mountain_goat/mg/metric_variants/show.html.erb +0 -30
- data/lib/mountain-goat/views/mountain_goat/mg/metrics/.tmp_show.html.erb.21270~ +0 -68
- data/lib/mountain-goat/views/mountain_goat/mg/metrics/_metric_form.html.erb +0 -24
- data/lib/mountain-goat/views/mountain_goat/mg/metrics/edit.html.erb +0 -13
- data/lib/mountain-goat/views/mountain_goat/mg/metrics/index.html.erb +0 -14
- data/lib/mountain-goat/views/mountain_goat/mg/metrics/new.html.erb +0 -14
- data/lib/mountain-goat/views/mountain_goat/mg/metrics/show.html.erb +0 -67
- data/lib/mountain-goat/views/mountain_goat/mg/rallies/.tmp__rally.html.erb.40484~ +0 -12
- data/lib/mountain-goat/views/mountain_goat/mg/rallies/_rallies.html.erb +0 -5
- data/lib/mountain-goat/views/mountain_goat/mg/rallies/_rallies_form.html.erb +0 -21
- data/lib/mountain-goat/views/mountain_goat/mg/rallies/_rally.html.erb +0 -12
- data/lib/mountain-goat/views/mountain_goat/mg/rallies/edit.html.erb +0 -13
- data/lib/mountain-goat/views/mountain_goat/mg/rallies/index.html.erb +0 -19
- data/lib/mountain-goat/views/mountain_goat/mg/rallies/new.html.erb +0 -13
- 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
|
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
|
-
|
15
|
+
rw(:user_signup, 10) # Reward 10 points
|
16
16
|
...
|
17
17
|
end
|
18
18
|
|
19
|
-
This will track a
|
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
|
31
|
-
- Watch
|
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 (
|
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.
|
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
|
-
-
|
83
|
-
-
|
84
|
-
-
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
-
###
|
145
|
+
###Tests
|
146
146
|
|
147
|
-
Go to
|
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
|
150
|
-
* Charts show you visually which
|
151
|
-
* "Add
|
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
|
-
###
|
153
|
+
###Records
|
154
154
|
|
155
|
-
|
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
|
181
|
+
You can track meta-data with any goal. E.g.
|
176
182
|
|
177
|
-
|
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
|
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
|
-
|
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
|
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
|
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
|
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
|
-
|
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
|
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
|
-
|
222
|
+
### Mountain-Goat.yml
|
217
223
|
|
218
|
-
|
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
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
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
|
-
|
232
|
-
|
233
|
-
|
234
|
-
*
|
235
|
-
*
|
236
|
-
*
|
237
|
-
*
|
238
|
-
*
|
239
|
-
*
|
240
|
-
*
|
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
|
37
|
+
if update.blank?
|
38
38
|
m.migration_template 'create_mountain_goat_tables.rb', 'db/migrate', { :migration_file_name => "create_mountain_goat_tables" }
|
39
|
-
|
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
|
|
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/
|
12
|
-
require File.join([File.dirname(__FILE__), 'mountain-goat/controllers/mg/
|
13
|
-
require File.join([File.dirname(__FILE__), 'mountain-goat/controllers/mg/
|
14
|
-
require File.join([File.dirname(__FILE__), 'mountain-goat/controllers/mg/
|
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/
|
22
|
-
require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/
|
23
|
-
require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/
|
24
|
-
require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/
|
25
|
-
require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/
|
26
|
-
require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/
|
27
|
-
require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/
|
28
|
-
require File.join([File.dirname(__FILE__), 'mountain-goat/models/mg/
|
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.
|
25
|
-
@report_item.order = @report.
|
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.
|
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.
|
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.
|
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 #{
|
80
|
-
return Mg::MetricVariant.first(:order => "CASE WHEN served = 0 THEN 1 ELSE 0 END DESC, CASE WHEN #{
|
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 #{
|
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 => "#{
|
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
|