Commit 005eaf224602e154cd1d233c084597802d100d3c

Authored by Nick Recobra
2 parents 217069e9 567d7e96
Exists in master and in 1 other branch production

Merge branch 'master' of https://github.com/boblail/errbit into boblail_master

Conflicts:
	Gemfile
	Gemfile.lock
	app/models/notice.rb
	app/views/notices/_environment.html.haml
	app/views/notices/_summary.html.haml
	spec/models/notice_spec.rb
Gemfile
... ... @@ -9,6 +9,7 @@ gem 'devise', '~> 1.1.8'
9 9 gem 'lighthouse-api'
10 10 gem 'redmine_client', :git => "git://github.com/oruen/redmine_client.git"
11 11 gem 'mongoid_rails_migrations'
  12 +gem 'useragent', '~> 0.3.1'
12 13  
13 14 platform :ruby do
14 15 gem 'bson_ext', '~> 1.2'
... ...
Gemfile.lock
... ... @@ -114,6 +114,7 @@ GEM
114 114 treetop (1.4.9)
115 115 polyglot (>= 0.3.1)
116 116 tzinfo (0.3.26)
  117 + useragent (0.3.1)
117 118 warden (1.0.3)
118 119 rack (>= 1.0.0)
119 120 webmock (1.6.2)
... ... @@ -138,5 +139,6 @@ DEPENDENCIES
138 139 redmine_client!
139 140 rspec (~> 2.5)
140 141 rspec-rails (~> 2.5)
  142 + useragent (~> 0.3.1)
141 143 webmock
142 144 will_paginate
... ...
app/controllers/notices_controller.rb
... ... @@ -4,7 +4,8 @@ class NoticesController < ApplicationController
4 4 skip_before_filter :authenticate_user!, :only => :create
5 5  
6 6 def create
7   - @notice = Notice.from_xml(request.raw_post)
  7 + # params[:data] if the notice came from a GET request, raw_post if it came via POST
  8 + @notice = Notice.from_xml(params[:data] || request.raw_post)
8 9 respond_with @notice
9 10 end
10 11  
... ...
app/helpers/application_helper.rb
1 1 module ApplicationHelper
  2 +
  3 +
2 4 def lighthouse_tracker? object
3 5 object.issue_tracker_type == "lighthouseapp"
4 6 end
  7 +
  8 +
  9 + def user_agent_graph(error)
  10 + tallies = tally(error.notices) {|notice| pretty_user_agent(notice.user_agent)}
  11 + create_percentage_table(tallies, :total => error.notices.count)
  12 + end
  13 +
  14 + def pretty_user_agent(user_agent)
  15 + (user_agent.nil? || user_agent.none?) ? "N/A" : "#{user_agent.browser} #{user_agent.version}"
  16 + end
  17 +
  18 +
  19 + def tally(collection, &block)
  20 + collection.inject({}) do |tallies, item|
  21 + value = yield item
  22 + tallies[value] = (tallies[value] || 0) + 1
  23 + tallies
  24 + end
  25 + end
  26 +
  27 +
  28 + def create_percentage_table(tallies, options={})
  29 + total = (options[:total] || total_from_tallies(tallies))
  30 + percent = 100.0 / total.to_f
  31 + rows = tallies.map {|value, count| [(count.to_f * percent), value]} \
  32 + .sort {|a, b| a[0] <=> b[0]}
  33 + render :partial => "errs/tally_table", :locals => {:rows => rows}
  34 + end
  35 +
  36 +
  37 +private
  38 +
  39 +
  40 + def total_from_tallies(tallies)
  41 + tallies.values.inject(0) {|sum, n| sum + n}
  42 + end
  43 +
  44 +
5 45 end
... ...
app/models/notice.rb
... ... @@ -51,6 +51,11 @@ class Notice
51 51 })
52 52 end
53 53  
  54 + def user_agent
  55 + agent_string = env_vars['HTTP_USER_AGENT']
  56 + agent_string.blank? ? nil : UserAgent.parse(agent_string)
  57 + end
  58 +
54 59 def request
55 60 read_attribute(:request) || {}
56 61 end
... ...
app/views/errs/_table.html.haml
... ... @@ -11,7 +11,7 @@
11 11 - errs.each do |err|
12 12 %tr{:class => err.resolved? ? 'resolved' : 'unresolved'}
13 13 %td.app
14   - = err.app.name
  14 + = link_to err.app.name, app_path(err.app)
15 15 %span.environment= err.environment
16 16 %td.message
17 17 = link_to err.message, app_err_path(err.app, err)
... ...
app/views/errs/_tally_table.html.haml 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +%table.tally
  2 + - rows.each do |row|
  3 + %tr
  4 + %td.percent= number_to_percentage(row[0], :precision => 1)
  5 + %th.value= row[1]
... ...
app/views/notices/_environment.html.haml
1 1 .window
2 2 %table.environment
3   - - notice.env_vars.each do |key,val|
  3 + - notice.env_vars.sort_by {|pair| pair[0]}.each do |pair|
4 4 %tr
5   - %th= raw key
6   - %td.main= val
7 5 \ No newline at end of file
  6 + %th= pair[0]
  7 + %td.main= pair[1]
... ...
app/views/notices/_summary.html.haml
... ... @@ -15,4 +15,7 @@
15 15 %td= notice.created_at.to_s(:micro)
16 16 %tr
17 17 %th Similar
18   - %td= notice.err.notices_count - 1
19 18 \ No newline at end of file
  19 + %td= notice.err.notices.count - 1
  20 + %tr
  21 + %th Browser
  22 + %td= user_agent_graph(notice.err)
... ...
public/stylesheets/application.css
... ... @@ -562,7 +562,7 @@ table.errs td.app .environment {
562 562 table.errs td.message a {
563 563 width: 420px;
564 564 display: block;
565   - word-wrap: break-word;
  565 + word-wrap: break-word;
566 566 }
567 567 table.errs td.message em {
568 568 color: #727272;
... ... @@ -574,6 +574,27 @@ table.errs tr.resolved td &gt; * {
574 574 -webkit-opacity: 0.5;
575 575 }
576 576  
  577 +/* Tally tables */
  578 +table.tally {
  579 + border:none;
  580 +}
  581 +table.tally td,
  582 +table.tally th {
  583 + border:none !important;
  584 + background:none !important;
  585 + padding:8px 0 0;
  586 +}
  587 +table.tally tbody tr:first-child td,
  588 +table.tally tbody tr:first-child th {
  589 + padding-top:0;
  590 +}
  591 +table.tally td.percent {
  592 + width:4.5em;
  593 +}
  594 +table.tally th.value {
  595 + text-transform:none;
  596 +}
  597 +
577 598 /* Resolve Errs */
578 599 #action-bar a.resolve {
579 600 background: transparent url(images/icons/thumbs-up.png) 6px 5px no-repeat;
... ...
spec/controllers/notices_controller_spec.rb
... ... @@ -2,7 +2,7 @@ require &#39;spec_helper&#39;
2 2  
3 3 describe NoticesController do
4 4  
5   - context 'POST[XML] notices#create' do
  5 + context 'notices API' do
6 6 before do
7 7 @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read
8 8 @app = Factory(:app_with_watcher)
... ... @@ -11,15 +11,21 @@ describe NoticesController do
11 11  
12 12 request.env['Content-type'] = 'text/xml'
13 13 request.env['Accept'] = 'text/xml, application/xml'
14   - request.should_receive(:raw_post).and_return(@xml)
15 14 end
16 15  
17   - it "generates a notice from the xml" do
  16 + it "generates a notice from xml [POST]" do
18 17 Notice.should_receive(:from_xml).with(@xml).and_return(@notice)
  18 + request.should_receive(:raw_post).and_return(@xml)
19 19 post :create
20 20 end
21 21  
  22 + it "generates a notice from xml [GET]" do
  23 + Notice.should_receive(:from_xml).with(@xml).and_return(@notice)
  24 + get :create, {:data => @xml}
  25 + end
  26 +
22 27 it "sends a notification email" do
  28 + request.should_receive(:raw_post).and_return(@xml)
23 29 post :create
24 30 email = ActionMailer::Base.deliveries.last
25 31 email.to.should include(@app.watchers.first.email)
... ...
spec/models/notice_spec.rb
... ... @@ -119,6 +119,19 @@ describe Notice do
119 119 end
120 120 end
121 121  
  122 + describe "user agent" do
  123 + it "should be parsed and human-readable" do
  124 + notice = Factory.build(:notice, :request => {'cgi-data' => {'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16'}})
  125 + notice.user_agent.browser.should == 'Chrome'
  126 + notice.user_agent.version.to_s.should =~ /^10\.0/
  127 + end
  128 +
  129 + it "should be nil if HTTP_USER_AGENT is blank" do
  130 + notice = Factory.build(:notice)
  131 + notice.user_agent.should == nil
  132 + end
  133 + end
  134 +
122 135 describe "email notifications" do
123 136 before do
124 137 @app = Factory(:app_with_watcher)
... ...