mountain-goat 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/Gemfile +3 -0
  2. data/Gemfile.lock +31 -0
  3. data/History.md +4 -0
  4. data/LICENSE +20 -0
  5. data/POST_INSTALL +14 -0
  6. data/README.md +128 -0
  7. data/Rakefile +24 -0
  8. data/init.rb +4 -0
  9. data/install.rb +53 -0
  10. data/lib/mountain_goat/controllers/mountain_goat/mountain_goat_controller.rb +6 -0
  11. data/lib/mountain_goat/controllers/mountain_goat/mountain_goat_converts_controller.rb +107 -0
  12. data/lib/mountain_goat/controllers/mountain_goat/mountain_goat_metric_variants_controller.rb +80 -0
  13. data/lib/mountain_goat/controllers/mountain_goat/mountain_goat_metrics_controller.rb +108 -0
  14. data/lib/mountain_goat/controllers/mountain_goat/mountain_goat_rallies_controller.rb +3 -0
  15. data/lib/mountain_goat/metric_tracking.rb +241 -0
  16. data/lib/mountain_goat/models/ci_meta.rb +9 -0
  17. data/lib/mountain_goat/models/convert.rb +70 -0
  18. data/lib/mountain_goat/models/convert_meta_type.rb +19 -0
  19. data/lib/mountain_goat/models/cs_meta.rb +9 -0
  20. data/lib/mountain_goat/models/metric.rb +10 -0
  21. data/lib/mountain_goat/models/metric_variant.rb +22 -0
  22. data/lib/mountain_goat/models/rally.rb +32 -0
  23. data/lib/mountain_goat/switch_variant.rb +32 -0
  24. data/lib/mountain_goat/version.rb +3 -0
  25. data/lib/mountain_goat/views/mountain_goat/layouts/mountain_goat.html.erb +53 -0
  26. data/lib/mountain_goat/views/mountain_goat/mountain_goat_converts/_convert_form.html.erb +26 -0
  27. data/lib/mountain_goat/views/mountain_goat/mountain_goat_converts/_convert_meta_type_form.html.erb +27 -0
  28. data/lib/mountain_goat/views/mountain_goat/mountain_goat_converts/edit.html.erb +13 -0
  29. data/lib/mountain_goat/views/mountain_goat/mountain_goat_converts/index.html.erb +33 -0
  30. data/lib/mountain_goat/views/mountain_goat/mountain_goat_converts/new.html.erb +13 -0
  31. data/lib/mountain_goat/views/mountain_goat/mountain_goat_converts/show.html.erb +59 -0
  32. data/lib/mountain_goat/views/mountain_goat/mountain_goat_metric_variants/_metric_variant_form.html.erb +27 -0
  33. data/lib/mountain_goat/views/mountain_goat/mountain_goat_metric_variants/edit.html.erb +13 -0
  34. data/lib/mountain_goat/views/mountain_goat/mountain_goat_metric_variants/index.html.erb +35 -0
  35. data/lib/mountain_goat/views/mountain_goat/mountain_goat_metric_variants/new.html.erb +15 -0
  36. data/lib/mountain_goat/views/mountain_goat/mountain_goat_metric_variants/show.html.erb +14 -0
  37. data/lib/mountain_goat/views/mountain_goat/mountain_goat_metrics/_metric_form.html.erb +26 -0
  38. data/lib/mountain_goat/views/mountain_goat/mountain_goat_metrics/edit.html.erb +13 -0
  39. data/lib/mountain_goat/views/mountain_goat/mountain_goat_metrics/index.html.erb +29 -0
  40. data/lib/mountain_goat/views/mountain_goat/mountain_goat_metrics/new.html.erb +14 -0
  41. data/lib/mountain_goat/views/mountain_goat/mountain_goat_metrics/show.html.erb +59 -0
  42. data/lib/mountain_goat.rb +19 -0
  43. data/migrations/20090716093747_create_metric_tracking_tables.rb +85 -0
  44. data/mountain-goat.gemspec +26 -0
  45. metadata +141 -0
@@ -0,0 +1,108 @@
1
+ class MountainGoatMetricsController < MountainGoatController
2
+
3
+ # GET /metrics
4
+ # GET /metrics.xml
5
+ def index
6
+ @metrics = Metric.all
7
+
8
+ respond_to do |format|
9
+ format.html # index.html.erb
10
+ format.xml { render :xml => @metrics }
11
+ end
12
+ end
13
+
14
+ # GET /metrics/1
15
+ # GET /metrics/1.xml
16
+ def show
17
+ @metric = Metric.find(params[:id])
18
+
19
+ @rates = {}
20
+ @rates[:served] = []
21
+ @rates[:conversions] = []
22
+ @rates[:conversion_rates] = []
23
+ @rates[:titles] = {}
24
+ i = 0
25
+ @metric.metric_variants.each do |mv|
26
+ @rates[:served].push( { :variant_type => i, :value => mv.served } )
27
+ @rates[:conversions].push( { :variant_type => i, :value => mv.conversions } )
28
+ @rates[:conversion_rates].push( { :variant_type => i, :value => mv.conversion_rate } )
29
+ @rates[:titles].merge!({i => mv.name})
30
+ i += 1
31
+ end
32
+
33
+ logger.warn @rates[:titles].inspect
34
+
35
+ respond_to do |format|
36
+ format.html # show.html.erb
37
+ format.xml { render :xml => @metric }
38
+ end
39
+ end
40
+
41
+ # GET /metrics/new
42
+ # GET /metrics/new.xml
43
+ def new
44
+ @convert = Convert.find(params[:mountain_goat_convert_id])
45
+ @metric = Metric.new(:convert_id => @convert.id)
46
+
47
+ respond_to do |format|
48
+ format.html # new.html.erb
49
+ format.xml { render :xml => @metric }
50
+ end
51
+ end
52
+
53
+ # GET /metrics/1/edit
54
+ def edit
55
+ @metric = Metric.find(params[:id])
56
+ end
57
+
58
+ # POST /metrics
59
+ # POST /metrics.xml
60
+ def create
61
+ @metric = Metric.new(params[:metric])
62
+
63
+ if @metric.save
64
+ flash[:notice] = 'Metric was successfully created.'
65
+ redirect_to mountain_goat_metric_url :id => @metric.id
66
+ else
67
+ render :action => "new"
68
+ end
69
+ end
70
+
71
+ # PUT /metrics/1
72
+ # PUT /metrics/1.xml
73
+ def update
74
+ @metric = Metric.find(params[:id])
75
+
76
+ if @metric.update_attributes(params[:metric])
77
+ flash[:notice] = 'Metric was successfully updated.'
78
+ redirect_to mountain_goat_metric_url :id => @metric.id
79
+ else
80
+ render :action => "edit"
81
+ end
82
+ end
83
+
84
+ # DELETE /metrics/1
85
+ # DELETE /metrics/1.xml
86
+ def destroy
87
+ @metric = Metric.find(params[:id])
88
+ @metric.destroy
89
+
90
+ respond_to do |format|
91
+ format.html { redirect_to(mountain_goat_metrics_url) }
92
+ format.xml { head :ok }
93
+ end
94
+ end
95
+
96
+ def fresh_metrics
97
+ #clear metrics
98
+ #logger.warn "Headerish #{response.headers['cookie']}"
99
+ cookies.each do |cookie|
100
+ if cookie[0] =~ /metric_([a-z0-9_]+)/
101
+ logger.warn "Deleting cookie #{cookie[0]}"
102
+ cookies.delete cookie[0], :domain => WILD_DOMAIN
103
+ end
104
+ end
105
+ flash[:notice] = "Your metrics have been cleared from cookies."
106
+ redirect_to :back
107
+ end
108
+ end
@@ -0,0 +1,3 @@
1
+ class MountainGoatRalliesController < MountainGoatController
2
+
3
+ end
@@ -0,0 +1,241 @@
1
+ require File.join([File.dirname(__FILE__), 'switch_variant'])
2
+
3
+ module MetricTracking
4
+
5
+ #Metric Tracking routes
6
+ class << ActionController::Routing::Routes;self;end.class_eval do
7
+ define_method :clear!, lambda {}
8
+ end
9
+
10
+ #TODO: Namespace?
11
+ ActionController::Routing::Routes.draw do |map|
12
+ map.mg '/mg', :controller => :mountain_goat_converts, :action => :index
13
+ map.resources :mountain_goat_metric_variants
14
+ map.resources :mountain_goat_converts, :has_many => :mountain_goat_metrics
15
+ map.resources :mountain_goat_metrics, :has_many => :mountain_goat_metric_variants
16
+ map.resources :mountain_goat_upgrade_orders
17
+ map.fresh_metrics '/fresh-metrics', :controller => :mountain_goat_metrics, :action => :fresh_metrics
18
+ end
19
+
20
+ module Controller
21
+
22
+ ######################
23
+ # Metric Tracking #
24
+ ######################
25
+
26
+ def sv(metric_type, convert_type, &block)
27
+ raise ArgumentError, "Switch variant needs block" if !block_given?
28
+ metric, convert = get_metric_convert( metric_type, convert_type, true )
29
+ block.call(SwitchVariant.new( logger, metric, convert, nil ) )
30
+
31
+ var = get_switch_metric_variant(metric_type, convert_type)
32
+ block.call(SwitchVariant.new( logger, metric, convert, var ) )
33
+ end
34
+
35
+ def mv(metric_type, convert_type, default)
36
+ return get_metric_variant(metric_type, convert_type, default)
37
+ end
38
+
39
+ #shorthand
40
+ def rc(convert_type, options = {})
41
+ self.record_conversion(convert_type, options)
42
+ end
43
+
44
+ def record_conversion(convert_type, options = {})
45
+
46
+ #We want some system for easy default parameter setting
47
+ if options.include?(:refs) && options[:refs]
48
+ options = options.merge( :ref_domain => session[:ref_domain], :ref_flyer => session[:ref_flyer], :ref_user => session[:ref_user] )
49
+ options.delete(:refs)
50
+ end
51
+
52
+ if options.include?(:user) && options[:user]
53
+ options = options.merge( :user_id => current_user.id ) if signed_in?
54
+ options.delete(:user)
55
+ end
56
+
57
+ if options.include?(:invitees) && options[:invitees]
58
+ invitee_meta = {}
59
+ if session[:invitee_id]
60
+ invitee = Invitee.find_by_id(session[:invitee_id])
61
+ if invitee.mailer.clique == @clique
62
+ options.merge!( { :mailer_id => invitee.mailer.id } )
63
+ end
64
+ end
65
+ options.delete(:invitees)
66
+ end
67
+
68
+ logger.warn "Recording conversion #{convert_type.to_s} with options #{options.inspect}"
69
+
70
+ convert = Convert.first( :conditions => { :convert_type => convert_type.to_s } )
71
+
72
+ #now, we just create the convert if we don't have one
73
+ convert = Convert.create!( :convert_type => convert_type.to_s, :name => convert_type.to_s ) if convert.nil?
74
+
75
+ #first, let's tally for the conversion itself
76
+ #we need to see what meta information we should fill based on the conversion type
77
+ Rally.create!( { :convert_id => convert.id } ).set_meta_data(options)
78
+
79
+ #we just converted, let's tally each of our metrics (from cookies)
80
+ convert.metrics.each do |metric|
81
+ metric_sym = "metric_#{metric.metric_type}".to_sym
82
+ metric_variant_sym = "metric_#{metric.metric_type}_variant".to_sym
83
+
84
+ value = cookies[metric_sym]
85
+ variant_id = cookies[metric_variant_sym]
86
+
87
+ #logger.warn "Value: #{metric_sym} - #{value}"
88
+ #logger.warn "Value: #{metric_variant_sym} - #{variant_id}"
89
+
90
+ if variant_id.blank? #the user just doesn't have this set
91
+ next
92
+ end
93
+
94
+ variant = MetricVariant.first(:conditions => { :id => variant_id.to_i } )
95
+
96
+ if variant.nil?
97
+ logger.error "Variant #{variant_id} not in metric variants for #{metric.title}"
98
+ next
99
+ end
100
+
101
+ if variant.value != value
102
+ logger.warn "Variant #{variant.name} values differ for metric #{metric.title}. '#{variant.value}' != '#{value}'!"
103
+ end
104
+
105
+ logger.warn "Tallying conversion #{convert.name} for #{metric.title} - #{variant.name} (#{variant.value} - #{variant.id})"
106
+ variant.tally_convert
107
+ end
108
+ end
109
+
110
+ private
111
+
112
+ def get_metric_variant(metric_type, convert_type, default)
113
+ metric_sym = "metric_#{metric_type}".to_sym
114
+ metric_variant_sym = "metric_#{metric_type}_variant".to_sym
115
+
116
+ #first, we'll check for a cookie value
117
+ if cookies[metric_sym] && !cookies[metric_sym].blank?
118
+ #we have the cookie
119
+
120
+ variant_id = cookies[metric_variant_sym]
121
+ variant = MetricVariant.first(:conditions => { :id => variant_id.to_i } )
122
+ if !variant.nil?
123
+ if variant.metric.tally_each_serve
124
+ variant.tally_serve
125
+ end
126
+ else
127
+ logger.warn "Serving metric #{metric_type} without finding / tallying variant."
128
+ end
129
+
130
+ return cookies[metric_sym] #it's the best we can do
131
+ else
132
+ #we don't have the cookie, let's find a value to set
133
+ metric, convert = get_metric_convert( metric_type, convert_type, false )
134
+
135
+ #to use RAND(), let's not use metrics.metric_variants
136
+ sum_priority = MetricVariant.first(:select => "SUM(priority) as sum_priority", :conditions => { :metric_id => metric.id } ).sum_priority.to_f
137
+
138
+ if sum_priority > 0.0
139
+ metric_variant = MetricVariant.first(:order => "RAND() * ( priority / #{sum_priority.to_f} ) DESC", :conditions => { :metric_id => metric.id } )
140
+ end
141
+
142
+ if metric_variant.nil?
143
+ logger.warn "Missing metric variants for #{metric_type}"
144
+ metric_variant = MetricVariant.create!( :metric_id => metric.id, :value => default, :name => default )
145
+ end
146
+
147
+ metric_variant.tally_serve #donate we served this to a user
148
+ logger.debug "Serving #{metric_variant.name} (#{metric_variant.value}) for #{metric_sym}"
149
+ #good, we have a variant, let's store it in session
150
+ cookies[metric_sym] = { :value => metric_variant.value } #, :domain => WILD_DOMAIN
151
+ cookies[metric_variant_sym] = { :value => metric_variant.id } #, :domain => WILD_DOMAIN
152
+
153
+ return metric_variant.value
154
+ end
155
+ end
156
+
157
+ def get_switch_metric_variant(metric_type, convert_type)
158
+ metric_variant_sym = "metric_#{metric_type}_variant".to_sym
159
+
160
+ #first, we'll check for a cookie selection
161
+ if cookies[metric_variant_sym] && !cookies[metric_variant_sym].blank?
162
+ #we have the cookie
163
+
164
+ variant_id = cookies[metric_variant_sym]
165
+ variant = MetricVariant.first(:conditions => { :id => variant_id.to_i } )
166
+ if !variant.nil?
167
+
168
+ if variant.metric.tally_each_serve
169
+ variant.tally_serve
170
+ end
171
+
172
+ return variant
173
+
174
+ end
175
+
176
+ #otherwise, it's a big wtf? let's just move on
177
+ logger.warn "Missing metric variant for #{metric_type} (switch-type), reassigning..."
178
+ end
179
+
180
+ #we don't have the cookie, let's find a value to set
181
+ metric, convert = get_metric_convert( metric_type, convert_type, true )
182
+
183
+ #to use RAND(), let's not use metrics.metric_variants
184
+ sum_priority = MetricVariant.first(:select => "SUM(priority) as sum_priority", :conditions => { :metric_id => metric.id } ).sum_priority.to_f
185
+
186
+ if sum_priority > 0.0
187
+ metric_variant = MetricVariant.first(:order => "RAND() * ( priority / #{sum_priority.to_f} ) DESC", :conditions => { :metric_id => metric.id } )
188
+ end
189
+
190
+ if metric_variant.nil?
191
+ logger.warn "Missing metric variants for #{metric_type}"
192
+ raise ArgumentError, "Missing variants for switch-type #{metric_type}"
193
+ end
194
+
195
+ metric_variant.tally_serve #donate we served this to a user
196
+ logger.debug "Serving #{metric_variant.name} (#{metric_variant.switch_type}) for #{metric.title} (switch-type)"
197
+ #good, we have a variant, let's store it in session (not the value, just the selection)
198
+ cookies[metric_variant_sym] = { :value => metric_variant.id } #, :domain => WILD_DOMAIN
199
+
200
+ return metric_variant
201
+ end
202
+
203
+ def get_metric_convert(metric_type, convert_type, is_switch = false)
204
+
205
+ metric = Metric.first(:conditions => { :metric_type => metric_type.to_s } )
206
+
207
+ conv = Convert.find_by_convert_type( convert_type.to_s )
208
+ if conv.nil?
209
+ logger.warn "Missing convert type #{convert_type.to_s} -- creating"
210
+ conv = Convert.create( :convert_type => convert_type.to_s, :name => convert_type.to_s ) if conv.nil?
211
+ end
212
+
213
+ if metric.nil? #we don't have a metric of this type
214
+ logger.warn "Missing metric type #{metric_type.to_s} -- creating"
215
+ metric = Metric.create( :metric_type => metric_type.to_s, :title => metric_type.to_s, :convert_id => conv.id, :is_switch => is_switch )
216
+ end
217
+
218
+ return metric, conv
219
+ end
220
+ end
221
+
222
+ module View
223
+ def mv(*args, &block)
224
+ @controller.send(:mv, *args, &block)
225
+ end
226
+
227
+ def sv(*args, &block)
228
+ @controller.send(:sv, *args, &block)
229
+ end
230
+ end
231
+ end
232
+
233
+ class ActionController::Base
234
+ include MetricTracking::Controller
235
+ end
236
+
237
+ class ActionView::Base
238
+ include MetricTracking::View
239
+ end
240
+
241
+
@@ -0,0 +1,9 @@
1
+ class CiMeta < ActiveRecord::Base
2
+
3
+ belongs_to :convert_meta_type
4
+ belongs_to :rally
5
+
6
+ validates_presence_of :convert_meta_type_id
7
+ validates_presence_of :rally_id
8
+
9
+ end
@@ -0,0 +1,70 @@
1
+ class Convert < ActiveRecord::Base
2
+
3
+ has_many :metrics
4
+ has_many :rallies
5
+ has_many :convert_meta_types
6
+ has_many :ci_metas, :through => :convert_meta_types
7
+ has_many :cs_metas, :through => :convert_meta_types
8
+
9
+ validates_presence_of :name
10
+ validates_format_of :convert_type, :with => /[a-z0-9_]{3,50}/i, :message => "must be between 3 and 30 characters, alphanumeric with underscores"
11
+ validates_uniqueness_of :convert_type
12
+
13
+ accepts_nested_attributes_for :convert_meta_types, :reject_if => lambda { |a| a[:name].blank? || a[:var].blank? || a[:meta_type].blank? }, :allow_destroy => true
14
+
15
+ def self.by_type(s)
16
+ Convert.find( :first, :conditions => { :convert_type => s.to_s } )
17
+ end
18
+
19
+ def rallies_for_meta(var)
20
+ cmt = self.convert_meta_types.find_by_var( var.to_s )
21
+ return {} if cmt.nil?
22
+ cmt.meta.map { |m| { m.data => m.rally } }
23
+ end
24
+
25
+ def rallies_for_meta_val(var, data)
26
+ cmt = self.convert_meta_types.find_by_var( var.to_s )
27
+ return [] if cmt.nil?
28
+ cmt.meta.find(:all, :conditions => { :data => data } ).map { |m| m.rally }
29
+ end
30
+
31
+ def rallies_for_meta_val_pivot(var, data, pivot)
32
+ res = {}
33
+ cmt = self.convert_meta_types.find_by_var( var.to_s )
34
+ cmt_pivot = self.convert_meta_types.find_by_var( pivot.to_s )
35
+ return {} if cmt.nil? || cmt_pivot.nil?
36
+ cmt.meta.find(:all, :select => "`#{cmt.meta.table_name}`.created_at, cm.data AS pivot", :conditions => { :data => data }, :joins => "LEFT JOIN `#{cmt_pivot.meta.table_name}` cm ON cm.convert_meta_type_id = #{cmt_pivot.id} AND cm.rally_id = `#{cmt.meta.table_name}`.rally_id").each do |c|
37
+ if !res.include?(c.pivot)
38
+ res[c.pivot] = []
39
+ end
40
+
41
+ res[c.pivot].push c.created_at
42
+ end
43
+
44
+ res.each { |k,v| v.sort! }
45
+ res
46
+ end
47
+
48
+ def rallies_for_meta_val_pivot_item(var, data, pivot, item)
49
+ res = {}
50
+ cmt = self.convert_meta_types.find_by_var( var.to_s )
51
+ cmt_pivot = self.convert_meta_types.find_by_var( pivot.to_s )
52
+ cmt_item = self.convert_meta_types.find_by_var( item.to_s )
53
+ return {} if cmt.nil? || cmt_pivot.nil? || cmt_item.nil?
54
+ cmt.meta.find(:all, :select => "`#{cmt.meta.table_name}`.created_at, cm.data AS pivot, ci.data as item", :conditions => { :data => data }, :joins => "LEFT JOIN `#{cmt_pivot.meta.table_name}` cm ON cm.convert_meta_type_id = #{cmt_pivot.id} AND cm.rally_id = `#{cmt.meta.table_name}`.rally_id LEFT JOIN `#{cmt_item.meta.table_name}` ci ON ci.convert_meta_type_id = #{cmt_item.id} AND ci.rally_id = `#{cmt.meta.table_name}`.rally_id").each do |c|
55
+ if !res.include?(c.pivot)
56
+ res[c.pivot] = []
57
+ end
58
+
59
+ if cmt_item.meta_type == 'ci_meta'
60
+ c.item.to_i.times { res[c.pivot].push c.created_at }
61
+ else
62
+ res[c.pivot].push c.created_at if c.item == 'yes' #what else?
63
+ end
64
+ end
65
+
66
+ res.each { |k,v| v.sort! }
67
+ res.delete_if { |k,v| v.count == 0 }
68
+ res
69
+ end
70
+ end
@@ -0,0 +1,19 @@
1
+ class ConvertMetaType < ActiveRecord::Base
2
+
3
+ belongs_to :convert
4
+ has_many :ci_metas, :class_name => 'CiMeta', :dependent => :destroy
5
+ has_many :cs_metas, :class_name => 'CsMeta', :dependent => :destroy
6
+
7
+ validates_presence_of :name
8
+ validates_presence_of :var
9
+ validates_presence_of :meta_type
10
+
11
+ def meta
12
+ case self.meta_type
13
+ when 'ci_meta'
14
+ return self.ci_metas
15
+ when 'cs_meta'
16
+ return self.cs_metas
17
+ end
18
+ end
19
+ end