mountain-goat 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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