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,9 @@
1
+ class CsMeta < 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,10 @@
1
+ class Metric < ActiveRecord::Base
2
+
3
+ has_many :metric_variants
4
+ belongs_to :convert
5
+
6
+ validates_presence_of :convert_id
7
+
8
+ validates_format_of :metric_type, :with => /[a-z0-9_]{3,50}/i, :message => "must be between 3 and 30 characters, alphanumeric with underscores"
9
+ validates_uniqueness_of :metric_type
10
+ end
@@ -0,0 +1,22 @@
1
+ class MetricVariant < ActiveRecord::Base
2
+
3
+ belongs_to :metric
4
+
5
+ validates_presence_of :name
6
+ validates_presence_of :metric_id
7
+
8
+ def tally_serve(count = 1)
9
+ MetricVariant.update_counters(self.id, :served => count)
10
+ self.reload
11
+ end
12
+
13
+ def tally_convert(count = 1)
14
+ MetricVariant.update_counters(self.id, :conversions => count)
15
+ self.reload
16
+ end
17
+
18
+ def conversion_rate
19
+ return nil if self.served == 0
20
+ return self.conversions / self.served.to_f * 100
21
+ end
22
+ end
@@ -0,0 +1,32 @@
1
+ class Rally < ActiveRecord::Base
2
+
3
+ belongs_to :convert
4
+ has_many :convert_meta_types, :through => :convert
5
+ #has_many :ci_metas, :through => :convert_meta_types
6
+ #has_many :cs_metas, :through => :convert_meta_types
7
+
8
+ #Set meta data for applicable options-- don't store nil data (waste of space)
9
+ def set_meta_data(options)
10
+ options.each do |option|
11
+ cmt = convert_meta_types.find_by_var(option[0].to_s)
12
+
13
+ #Create cmt if it doesn't current exist (unless nil)
14
+ if cmt.nil? && !option[1].nil?
15
+ #infer type
16
+ meta_type = "ci_meta" if option[1].is_a?(Integer)
17
+ meta_type = "cs_meta" if option[1].is_a?(String)
18
+ cmt = convert.convert_meta_types.create!(:name => option[0].to_s, :var => option[0].to_s, :meta_type => meta_type)
19
+ end
20
+
21
+ cmt.meta.create!(:rally_id => self.id, :data => option[1]) if !option[1].nil?
22
+ end
23
+ end
24
+
25
+ def meta_for( var )
26
+ cmt = convert_meta_types.find_by_var( var.to_s )
27
+ return nil if cmt.nil?
28
+ m = cmt.meta.find_by_rally_id( self.id )
29
+ return nil if m.nil?
30
+ return m.data
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+
2
+ class SwitchVariant
3
+ def initialize(logger, metric, convert, chosen_variant)
4
+ @chosen_variant = chosen_variant
5
+ @metric, @convert = metric, convert
6
+ @logger = logger
7
+ raise ArgumentError, "Metric type must be switch-type" if !@metric.is_switch
8
+ end
9
+
10
+ def method_missing(sym, *args, &block)
11
+ priority = ( args.first || 1.0 ).to_f
12
+
13
+ if @chosen_variant.nil?
14
+ #If we have not chosen a variant, we are going to look through
15
+ # each option and make sure we have a back-end entry in metric_variants
16
+ # for the type
17
+ @logger.warn "Looking at option #{sym.to_s}"
18
+ if @metric.metric_variants.find( :first, :conditions => { :switch_type => sym.to_s } ).nil?
19
+ @logger.warn "Creating switch-type metric-variant #{sym.to_s}"
20
+ @metric.metric_variants.create!( :name => sym.to_s, :switch_type => sym.to_s, :priority => priority, :value => nil )
21
+ end
22
+ else
23
+ if @chosen_variant.switch_type.to_s == sym.to_s
24
+ @logger.warn "Executing option #{sym.to_s}"
25
+ yield
26
+ else
27
+ @logger.warn "Bypassing option #{sym.to_s}"
28
+ end
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,3 @@
1
+ class MountainGoat
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,53 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
+
3
+ <% is_mobile = false if local_assigns[:is_mobile].nil? %>
4
+
5
+ <html xmlns="http://www.w3.org/1999/xhtml">
6
+
7
+ <head>
8
+ <title>Mountain Goat <% if @title %> - <%=h @title %><% end %></title>
9
+
10
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
11
+
12
+ <%# These are mobile specific settings. We are disabling zooming and fixing the width. %>
13
+ <meta content='True' name='HandheldFriendly' />
14
+ <!--<meta content='width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;' name='viewport' />-->
15
+ <meta name="viewport" content="width=device-width" />
16
+
17
+
18
+ </head>
19
+
20
+ <body>
21
+
22
+ <% if !flash[:message].blank? %>
23
+ <div class="ocelot-flash message">
24
+ <div class="message"><%= flash[:message] %></div>
25
+ </div>
26
+ <% end %>
27
+
28
+ <% if !flash[:model].blank? %>
29
+ <div class="ocelot-flash-model model">
30
+ <div class="jqmWindow" id="dialog">
31
+ <a href="#" class="jqmClose">Close</a>
32
+ <div class="model"><%= flash[:model] %></div>
33
+ </div>
34
+ </div>
35
+ <% end %>
36
+
37
+ <% if !flash[:notice].blank? || !flash[:error].blank? %>
38
+ <div class="ocelot-flash">
39
+ <% if !flash[:error].blank? %><div class="error"><%= flash[:error] %></div><% end %>
40
+ <% if !flash[:notice].blank? %><div class="notice"><%= flash[:notice] %></div><% end %>
41
+ </div>
42
+ <% end %>
43
+
44
+ <div id="ocelot-main">
45
+ <%= yield %>
46
+
47
+ <div id="container-footer">
48
+
49
+ </div>
50
+ </div>
51
+ </body>
52
+
53
+ </html>
@@ -0,0 +1,26 @@
1
+ <%# locals => f %>
2
+
3
+ <% if f.object.new_record? %>
4
+ <div class="item type">
5
+ <%= f.label :convert_type, 'Type' %><br />
6
+ <%= f.text_field :convert_type %>
7
+ </div>
8
+ <% else %>
9
+ <span class="convert-type"><%= f.object.convert_type %></span>
10
+ <% end %>
11
+
12
+ <div class="item name">
13
+ <%= f.label :name, 'Goal title' %><br />
14
+ <%= f.text_field :name %>
15
+ </div>
16
+
17
+ <div class="metas">
18
+ <% f.fields_for :convert_meta_types do |builder| %>
19
+ <%= render "convert_meta_type_form", :cmt_f => builder %>
20
+ <% end %>
21
+ </div>
22
+
23
+ <div class="item submit">
24
+ <%= f.submit 'Submit' %>
25
+ </div>
26
+
@@ -0,0 +1,27 @@
1
+ <%# locals => cmt_f %>
2
+
3
+ <div class="meta">
4
+ <span class="title">Meta Item</span>
5
+ <div class="item meta-name">
6
+ <%= cmt_f.label :name, 'Name' %><br />
7
+ <%= cmt_f.text_field :name %>
8
+ </div>
9
+ <% if cmt_f.object.new_record? %>
10
+ <div class="item meta-var">
11
+ <%= cmt_f.label :var, 'Var' %><br />
12
+ <%= cmt_f.text_field :var %>
13
+ </div>
14
+ <% else %>
15
+ <span class="metric-variant-name"><%=h cmt_f.object.var %></span>
16
+ <% end %>
17
+
18
+ <div class="item meta-meta-type">
19
+ <%= cmt_f.label :meta_type, 'Meta Type' %><br />
20
+ <%= cmt_f.collection_select :meta_type, [ { :val => "ci_meta", :text => "Integer" }, { :val => "cs_meta", :text => "String" } ], :val, :text %>
21
+ </div>
22
+
23
+ <div class="item meta-destroy">
24
+ <%= cmt_f.check_box :_destroy %>
25
+ <%= cmt_f.label :_destroy, "Remove Meta" %>
26
+ </div>
27
+ </div>
@@ -0,0 +1,13 @@
1
+
2
+
3
+ <div id="container-main" class="mt-converts">
4
+ <div class="mountain-goat-panel centered">
5
+ <h2>Editing conversion goal</h2>
6
+
7
+ <% form_for :convert, @convert, :url => mountain_goat_convert_url(:id => @convert.id), :html => { :method => :put } do |f| %>
8
+ <%= f.error_messages %>
9
+
10
+ <%= render :partial => 'mountain_goat_converts/convert_form', :locals => { :f => f } %>
11
+ <% end %>
12
+ </div>
13
+ </div>
@@ -0,0 +1,33 @@
1
+
2
+
3
+ <div id="container-main" class="mt-converts">
4
+ <div class="mountain-goat-panel centered">
5
+
6
+ <h2>Conversion Goals</h2>
7
+ <span class="explanation">Below are a list of conversion goals. Each goal has a set of metrics designed to achieve that goal. For each metric, there is a list of variants, each coupled with its conversation rate.</span>
8
+ <% @converts.each do |convert| %>
9
+ <h2><a href="<%= mountain_goat_convert_url :id => convert.id %>"><%=h convert.name %></a></h2>
10
+ <% convert.metrics.each do |metric| %>
11
+ <div class="metric-category">
12
+ <span class="metric"><a href="<%= mountain_goat_metric_url :id => metric.id %>"><%= metric.title %></a></span>
13
+ <% if metric.metric_variants.count == 0 %>
14
+ <span class="none">No metric variants [<a href="<%= new_mountain_goat_metric_mountain_goat_metric_variant_url :mountain_goat_metric_id => metric.id %>">Add one</a>]</span>
15
+ <% else %>
16
+ <% metric.metric_variants.each do |mv| %>
17
+ <div class="item rate">
18
+ <span class="rates"><%=h mv.name %></span>:
19
+ <span class="conversions"><%= mv.conversions %></span> /
20
+ <span class="served"><%= mv.served %></span>
21
+ (<span class="val"><%= number_to_percentage(mv.conversion_rate, :precision => 0) %></span>)
22
+ </div>
23
+ <% end %>
24
+ <% end %>
25
+ </div>
26
+ <% end %>
27
+ <% end %>
28
+
29
+ <div class="new">
30
+ <a href="<%= new_mountain_goat_convert_url %>">New conversation type</a>
31
+ </div>
32
+ </div>
33
+ </div>
@@ -0,0 +1,13 @@
1
+
2
+
3
+ <div id="container-main" class="mt-converts">
4
+ <div class="mountain-goat-panel centered">
5
+ <h2>New convert</h2>
6
+
7
+ <% form_for :convert, @convert, :url => mountain_goat_converts_url do |f| %>
8
+ <%= f.error_messages %>
9
+
10
+ <%= render :partial => 'mountain_goat_converts/convert_form', :locals => { :f => f } %>
11
+ <% end %>
12
+ </div>
13
+ </div>
@@ -0,0 +1,59 @@
1
+
2
+ <% if !respond_to?(:chart) %>
3
+ <% def chart(*args); 'Please install Flotilla plugin (http://flotilla.rubyforge.org/) for charts'; end; %>
4
+ <% end %>
5
+
6
+ <div id="container-main" class="mt-converts">
7
+ <div class="mountain-goat-panel centered">
8
+ <a href="<%= mountain_goat_converts_url %>">Goals</a> > <span class="type"><%=h @convert.convert_type %></span> |
9
+ <a href="<%= edit_mountain_goat_convert_url :id => @convert.id %>">Edit</a>
10
+
11
+ <h2><%=h @convert.name %></h2>
12
+
13
+ <div class="graphs">
14
+ <div class="graph">
15
+ <span class="title">Conversions by day</span>
16
+ <%= chart("analysis_graph#{@convert.id}",
17
+ { "Conversions" => { :collection => @results_per_day, :x => :date, :y => :val } },
18
+ { :xaxis => {:autoscaleMargin => 0.1, :mode => 'time', :timeformat => "%b %d" }, :yaxis => { :min => 0, :minTickSize => 1 }, :lines => { :show => true }, :legend => { :position => "ne" } }, :placeholder_size => "300x200") %>
19
+ </div>
20
+
21
+ <% @convert.convert_meta_types.each do |cmt| %>
22
+
23
+ <div class="graph">
24
+ <span class="title">Conversions by <%=h cmt.name %></span>
25
+
26
+ <% if cmt.meta_type == 'ci_meta' %>
27
+ <%= chart("analysis_graph#{cmt.id}",
28
+ { "Conversions" => { :collection => @results_by_cmt[cmt.id], :x => :name, :y => :val } },
29
+ { :xaxis => {:autoscaleMargin => 1, :minTickSize => 1 }, :yaxis => { :min => 0, :minTickSize => 1 }, :bars => { :show => true, :align => 'center', :barWidth => 1 }, :legend => { :position => "ne" } }, :placeholder_size => "300x200") %>
30
+ <% elsif cmt.meta_type == 'cs_meta' %>
31
+ <%= chart("analysis_graph#{cmt.id}",
32
+ { "Conversions" => { :collection => @results_by_cmt[cmt.id], :x => :name, :y => :val } },
33
+ { :xaxis => {:autoscaleMargin => 0.1, :ticks => @results_by_cmt_titles[cmt.id].map { |i,t| [i + 0.5, t] }.sort }, :yaxis => { :min => 0, :minTickSize => 1 }, :bars => { :show => true }, :legend => { :position => "ne" } }, :placeholder_size => "300x200") %>
34
+ <% end %>
35
+ </div>
36
+ <% end %>
37
+ </div>
38
+
39
+ <% @convert.metrics.each do |metric| %>
40
+ <div class="metric-category">
41
+ <span class="metric"><a href="<%= mountain_goat_metric_url :id => metric.id %>"><%= metric.title %></a></span>
42
+ <% if metric.metric_variants.count == 0 %>
43
+ <span class="none">No metric variants [<a href="<%= new_mountain_goat_metric_mountain_goat_metric_variant_url :mountain_goat_metric_id => metric.id %>">Add one</a>]</span>
44
+ <% else %>
45
+ <% metric.metric_variants.each do |mv| %>
46
+ <div class="item rate">
47
+ <span class="rates"><%=h mv.name %></span>:
48
+ <span class="conversions"><%= mv.conversions %></span> /
49
+ <span class="served"><%= mv.served %></span>
50
+ (<span class="val"><%= number_to_percentage(mv.conversion_rate, :precision => 0) %></span>)
51
+ </div>
52
+ <% end %>
53
+ <% end %>
54
+ </div>
55
+ <% end %>
56
+
57
+ <a href="<%= new_mountain_goat_convert_mountain_goat_metric_url :mountain_goat_convert_id => @convert.id %>">New Metric</a>
58
+ </div>
59
+ </div>
@@ -0,0 +1,27 @@
1
+ <%# locals => f %>
2
+
3
+ <%= f.hidden_field :metric_id %>
4
+
5
+ <% if f.object.metric.is_switch %>
6
+ <span class="switch-type"><%=h f.object.switch_type %></span>
7
+ <% end %>
8
+
9
+ <div class="item name">
10
+ <%= f.label :name, 'Variant name' %><br />
11
+ <%= f.text_field :name %>
12
+ </div>
13
+
14
+ <% if !f.object.metric.is_switch %>
15
+ <div class="item value">
16
+ <%= f.label :value, 'Metric value' %><br />
17
+ <%= f.text_area :value %>
18
+ </div>
19
+ <% end %>
20
+ <div class="item priority">
21
+ <%= f.label :priority, 'Priority' %><br />
22
+ <%= f.text_field :priority %>
23
+ </div>
24
+
25
+ <div class="item submit">
26
+ <%= f.submit 'Submit' %>
27
+ </div>
@@ -0,0 +1,13 @@
1
+
2
+
3
+ <div id="container-main" class="mt-metric-variants">
4
+ <div class="mountain-goat-panel centered">
5
+ <h2>Edit metric variant</h2>
6
+
7
+ <% form_for :metric_variant, @metric_variant, :url => mountain_goat_metric_variant_url(:id => @metric_variant.id), :html => { :method => :put } do |f| %>
8
+ <%= f.error_messages %>
9
+
10
+ <%= render :partial => 'mountain_goat_metric_variants/metric_variant_form', :locals => { :f => f } %>
11
+ <% end %>
12
+ </div>
13
+ </div>
@@ -0,0 +1,35 @@
1
+
2
+
3
+ <div id="container-main" class="mt-metric-variants">
4
+ <div class="mountain-goat-panel cenetered">
5
+ <h1>Listing Metric Variants</h1>
6
+
7
+ <table>
8
+ <tr>
9
+ <th>Metric</th>
10
+ <th>Value</th>
11
+ <th>Opt1</th>
12
+ <th>Opt2</th>
13
+ <th>Served</th>
14
+ <th>Conversions</th>
15
+ </tr>
16
+
17
+ <% @metric_variants.each do |metric_variant| %>
18
+ <tr>
19
+ <td><%=h metric_variant.metric_id %></td>
20
+ <td><%=h metric_variant.value %></td>
21
+ <td><%=h metric_variant.opt1 %></td>
22
+ <td><%=h metric_variant.opt2 %></td>
23
+ <td><%=h metric_variant.served %></td>
24
+ <td><%=h metric_variant.conversions %></td>
25
+ <td><a href="<%= mountain_goat_metric_variant_url metric_variant %>">Show</a></td>
26
+ <td><a href="<%= edit_mountain_goat_metric_variant_url metric_variant %>">Edit</a></td>
27
+ </tr>
28
+ <% end %>
29
+ </table>
30
+
31
+ <br />
32
+
33
+ <%= link_to 'New metric_variant', new_mountain_goat_metric_variant_url %>
34
+ </div>
35
+ </div>
@@ -0,0 +1,15 @@
1
+
2
+
3
+ <div id="container-main" class="mt-metric-variants">
4
+ <div class="mountain-goat-panel cenetered">
5
+ <h2>New metric variant for</br> <%=h @metric.title %></h2>
6
+
7
+ <% form_for :metric_variant, @metric_variant, :url => mountain_goat_metric_variants_url do |f| %>
8
+ <%= f.error_messages %>
9
+
10
+ <%= render :partial => 'mountain_goat_metric_variants/metric_variant_form', :locals => { :f => f } %>
11
+ <% end %>
12
+
13
+ <a href="<%= mountain_goat_metric_url :id => @metric.id %>">Back to <%=h @metric.title %></a>
14
+ </div>
15
+ </div>