Commit 40453cc3539b4c689aea482959a7f126727dfec8
Committed by
Karol Hosiawa
1 parent
bd17fa78
Exists in
master
and in
1 other branch
moved Notices to a separate collection
Showing
27 changed files
with
342 additions
and
167 deletions
Show diff stats
.gitignore
Gemfile
... | ... | @@ -2,12 +2,13 @@ source 'http://rubygems.org' |
2 | 2 | |
3 | 3 | gem 'rails', '3.0.5' |
4 | 4 | gem 'nokogiri' |
5 | -gem 'mongoid', '~> 2.0.0.rc.7' | |
5 | +gem 'mongoid', '2.0.0.rc.8' | |
6 | 6 | gem 'haml' |
7 | 7 | gem 'will_paginate' |
8 | 8 | gem 'devise', '~> 1.1.8' |
9 | 9 | gem 'lighthouse-api' |
10 | 10 | gem 'redmine_client', :git => "git://github.com/oruen/redmine_client.git" |
11 | +gem 'mongoid_rails_migrations' | |
11 | 12 | |
12 | 13 | platform :ruby do |
13 | 14 | gem 'bson_ext', '~> 1.2' | ... | ... |
Gemfile.lock
... | ... | @@ -35,15 +35,15 @@ GEM |
35 | 35 | activemodel (= 3.0.5) |
36 | 36 | activesupport (= 3.0.5) |
37 | 37 | activesupport (3.0.5) |
38 | - addressable (2.2.4) | |
38 | + addressable (2.2.5) | |
39 | 39 | arel (2.0.9) |
40 | 40 | bcrypt-ruby (2.1.4) |
41 | - bson (1.2.4) | |
42 | - bson_ext (1.2.4) | |
41 | + bson (1.3.0) | |
42 | + bson_ext (1.3.0) | |
43 | 43 | builder (2.1.2) |
44 | 44 | crack (0.1.8) |
45 | - database_cleaner (0.6.5) | |
46 | - devise (1.1.8) | |
45 | + database_cleaner (0.6.7) | |
46 | + devise (1.1.9) | |
47 | 47 | bcrypt-ruby (~> 2.1.2) |
48 | 48 | warden (~> 1.0.2) |
49 | 49 | diff-lcs (1.1.2) |
... | ... | @@ -58,23 +58,28 @@ GEM |
58 | 58 | lighthouse-api (2.0) |
59 | 59 | activeresource (>= 3.0.0) |
60 | 60 | activesupport (>= 3.0.0) |
61 | - mail (2.2.15) | |
61 | + mail (2.2.17) | |
62 | 62 | activesupport (>= 2.3.6) |
63 | 63 | i18n (>= 0.4.0) |
64 | 64 | mime-types (~> 1.16) |
65 | 65 | treetop (~> 1.4.8) |
66 | 66 | mime-types (1.16) |
67 | - mongo (1.2.4) | |
68 | - bson (>= 1.2.4) | |
69 | - mongoid (2.0.0.rc.7) | |
67 | + mongo (1.3.0) | |
68 | + bson (>= 1.3.0) | |
69 | + mongoid (2.0.0.rc.8) | |
70 | 70 | activemodel (~> 3.0) |
71 | 71 | mongo (~> 1.2) |
72 | 72 | tzinfo (~> 0.3.22) |
73 | 73 | will_paginate (~> 3.0.pre) |
74 | + mongoid_rails_migrations (0.0.10) | |
75 | + activesupport (~> 3.0.0) | |
76 | + bundler (>= 0.9.19) | |
77 | + rails (~> 3.0.0) | |
78 | + railties (~> 3.0.0) | |
74 | 79 | nokogiri (1.4.4) |
75 | 80 | polyglot (0.3.1) |
76 | 81 | rack (1.2.2) |
77 | - rack-mount (0.6.13) | |
82 | + rack-mount (0.6.14) | |
78 | 83 | rack (>= 1.0.0) |
79 | 84 | rack-test (0.5.7) |
80 | 85 | rack (>= 1.0) |
... | ... | @@ -108,7 +113,7 @@ GEM |
108 | 113 | thor (0.14.6) |
109 | 114 | treetop (1.4.9) |
110 | 115 | polyglot (>= 0.3.1) |
111 | - tzinfo (0.3.25) | |
116 | + tzinfo (0.3.26) | |
112 | 117 | warden (1.0.3) |
113 | 118 | rack (>= 1.0.0) |
114 | 119 | webmock (1.6.2) |
... | ... | @@ -126,7 +131,8 @@ DEPENDENCIES |
126 | 131 | factory_girl_rails |
127 | 132 | haml |
128 | 133 | lighthouse-api |
129 | - mongoid (~> 2.0.0.rc.7) | |
134 | + mongoid (= 2.0.0.rc.8) | |
135 | + mongoid_rails_migrations | |
130 | 136 | nokogiri |
131 | 137 | rails (= 3.0.5) |
132 | 138 | redmine_client! | ... | ... |
README.md
... | ... | @@ -89,17 +89,24 @@ for you. Checkout [Hoptoad](http://hoptoadapp.com) from the guys over at |
89 | 89 | |
90 | 90 | 4. Enjoy! |
91 | 91 | |
92 | +Upgrading | |
93 | +--------- | |
94 | +*Note*: If upgrading from a version of Errbit that used Notices embedded in Errs please run: | |
95 | + | |
96 | + 1. git pull origin master ( assuming origin is the github.com/jdpace/errbit repo ) | |
97 | + 2. rake db:migrate | |
98 | + | |
92 | 99 | Lighthouseapp integration |
93 | 100 | ------------------------- |
94 | 101 | |
95 | -* Account is the name of your subdomain, i.e. **litcafe** for project at http://litcafe.lighthouseapp.com/projects/73466-face/overview | |
102 | +* Account is the name of your subdomain, i.e. **litcafe** for project at http://litcafe.lighthouseapp.com/projects/73466-face/overview | |
96 | 103 | * Errbit uses token-based authentication. Get your API Token or visit [http://help.lighthouseapp.com/kb/api/how-do-i-get-an-api-token](http://help.lighthouseapp.com/kb/api/how-do-i-get-an-api-token) to learn how to get it. |
97 | 104 | * Project id is number identifier of your project, i.e. **73466** for project at http://litcafe.lighthouseapp.com/projects/73466-face/overview |
98 | 105 | |
99 | 106 | Redmine integration |
100 | 107 | ------------------------- |
101 | 108 | |
102 | -* Account is the host of your redmine installation, i.e. **http://redmine.org** | |
109 | +* Account is the host of your redmine installation, i.e. **http://redmine.org** | |
103 | 110 | * Errbit uses token-based authentication. Get your API Key or visit [http://www.redmine.org/projects/redmine/wiki/Rest_api#Authentication](http://www.redmine.org/projects/redmine/wiki/Rest_api#Authentication) to learn how to get it. |
104 | 111 | * Project id is an identifier of your project, i.e. **chilliproject** for project at http://www.redmine.org/projects/chilliproject |
105 | 112 | ... | ... |
app/controllers/apps_controller.rb
1 | 1 | class AppsController < ApplicationController |
2 | - | |
2 | + | |
3 | 3 | before_filter :require_admin!, :except => [:index, :show] |
4 | 4 | before_filter :find_app, :except => [:index, :new, :create] |
5 | - | |
5 | + | |
6 | 6 | def index |
7 | 7 | @apps = current_user.admin? ? App.all : current_user.apps.all |
8 | 8 | end |
9 | - | |
9 | + | |
10 | 10 | def show |
11 | 11 | respond_to do |format| |
12 | 12 | format.html do |
... | ... | @@ -18,21 +18,21 @@ class AppsController < ApplicationController |
18 | 18 | end |
19 | 19 | end |
20 | 20 | end |
21 | - | |
21 | + | |
22 | 22 | def new |
23 | 23 | @app = App.new |
24 | 24 | @app.watchers.build |
25 | 25 | @app.issue_tracker = IssueTracker.new |
26 | 26 | end |
27 | - | |
27 | + | |
28 | 28 | def edit |
29 | 29 | @app.watchers.build if @app.watchers.none? |
30 | 30 | @app.issue_tracker = IssueTracker.new if @app.issue_tracker.nil? |
31 | 31 | end |
32 | - | |
32 | + | |
33 | 33 | def create |
34 | 34 | @app = App.new(params[:app]) |
35 | - | |
35 | + | |
36 | 36 | if @app.save |
37 | 37 | flash[:success] = 'Great success! Configure your app with the API key below' |
38 | 38 | redirect_to app_path(@app) |
... | ... | @@ -40,8 +40,8 @@ class AppsController < ApplicationController |
40 | 40 | render :new |
41 | 41 | end |
42 | 42 | end |
43 | - | |
44 | - def update | |
43 | + | |
44 | + def update | |
45 | 45 | if @app.update_attributes(params[:app]) |
46 | 46 | flash[:success] = "Good news everyone! '#{@app.name}' was successfully updated." |
47 | 47 | redirect_to app_path(@app) |
... | ... | @@ -49,18 +49,18 @@ class AppsController < ApplicationController |
49 | 49 | render :edit |
50 | 50 | end |
51 | 51 | end |
52 | - | |
52 | + | |
53 | 53 | def destroy |
54 | 54 | @app.destroy |
55 | 55 | flash[:success] = "'#{@app.name}' was successfully destroyed." |
56 | 56 | redirect_to apps_path |
57 | 57 | end |
58 | - | |
58 | + | |
59 | 59 | protected |
60 | - | |
60 | + | |
61 | 61 | def find_app |
62 | 62 | @app = App.find(params[:id]) |
63 | - | |
63 | + | |
64 | 64 | # Mongoid Bug: could not chain: current_user.apps.find_by_id! |
65 | 65 | # apparently finding by 'watchers.email' and 'id' is broken |
66 | 66 | raise(Mongoid::Errors::DocumentNotFound.new(App,@app.id)) unless current_user.admin? || current_user.watching?(@app) | ... | ... |
app/controllers/errs_controller.rb
1 | 1 | class ErrsController < ApplicationController |
2 | - | |
2 | + | |
3 | 3 | before_filter :find_app, :except => [:index, :all] |
4 | 4 | before_filter :find_err, :except => [:index, :all] |
5 | - | |
5 | + | |
6 | 6 | def index |
7 | 7 | app_scope = current_user.admin? ? App.all : current_user.apps |
8 | 8 | respond_to do |format| |
... | ... | @@ -14,14 +14,14 @@ class ErrsController < ApplicationController |
14 | 14 | end |
15 | 15 | end |
16 | 16 | end |
17 | - | |
17 | + | |
18 | 18 | def all |
19 | 19 | app_scope = current_user.admin? ? App.all : current_user.apps |
20 | 20 | @errs = Err.for_apps(app_scope).ordered.paginate(:page => params[:page], :per_page => current_user.per_page) |
21 | 21 | end |
22 | - | |
22 | + | |
23 | 23 | def show |
24 | - page = (params[:notice] || @err.notices.count) | |
24 | + page = (params[:notice] || @err.notices_count) | |
25 | 25 | page = 1 if page.to_i.zero? |
26 | 26 | @notices = @err.notices.ordered.paginate(:page => page, :per_page => 1) |
27 | 27 | @notice = @notices.first |
... | ... | @@ -46,25 +46,25 @@ class ErrsController < ApplicationController |
46 | 46 | @err.update_attribute :issue_link, nil |
47 | 47 | redirect_to app_err_path(@app, @err) |
48 | 48 | end |
49 | - | |
49 | + | |
50 | 50 | def resolve |
51 | - # Deal with bug in mogoid where find is returning an Enumberable obj | |
51 | + # Deal with bug in mongoid where find is returning an Enumberable obj | |
52 | 52 | @err = @err.first if @err.respond_to?(:first) |
53 | - | |
53 | + | |
54 | 54 | @err.resolve! |
55 | - | |
55 | + | |
56 | 56 | flash[:success] = 'Great news everyone! The err has been resolved.' |
57 | 57 | |
58 | 58 | redirect_to :back |
59 | 59 | rescue ActionController::RedirectBackError |
60 | 60 | redirect_to app_path(@app) |
61 | 61 | end |
62 | - | |
62 | + | |
63 | 63 | protected |
64 | - | |
64 | + | |
65 | 65 | def find_app |
66 | 66 | @app = App.find(params[:app_id]) |
67 | - | |
67 | + | |
68 | 68 | # Mongoid Bug: could not chain: current_user.apps.find_by_id! |
69 | 69 | # apparently finding by 'watchers.email' and 'id' is broken |
70 | 70 | raise(Mongoid::Errors::DocumentNotFound.new(App,@app.id)) unless current_user.admin? || current_user.watching?(@app) |
... | ... | @@ -79,5 +79,5 @@ class ErrsController < ApplicationController |
79 | 79 | IssueTracker.default_url_options[:port] = request.port |
80 | 80 | IssueTracker.default_url_options[:protocol] = request.scheme |
81 | 81 | end |
82 | - | |
82 | + | |
83 | 83 | end | ... | ... |
app/models/app.rb
1 | 1 | class App |
2 | 2 | include Mongoid::Document |
3 | 3 | include Mongoid::Timestamps |
4 | - | |
4 | + | |
5 | 5 | field :name, :type => String |
6 | 6 | field :api_key |
7 | 7 | field :resolve_errs_on_deploy, :type => Boolean, :default => false |
... | ... | @@ -21,29 +21,29 @@ class App |
21 | 21 | embeds_many :deploys |
22 | 22 | embeds_one :issue_tracker |
23 | 23 | references_many :errs, :dependent => :destroy |
24 | - | |
24 | + | |
25 | 25 | before_validation :generate_api_key, :on => :create |
26 | - | |
26 | + | |
27 | 27 | validates_presence_of :name, :api_key |
28 | 28 | validates_uniqueness_of :name, :allow_blank => true |
29 | 29 | validates_uniqueness_of :api_key, :allow_blank => true |
30 | 30 | validates_associated :watchers |
31 | 31 | validate :check_issue_tracker |
32 | - | |
32 | + | |
33 | 33 | accepts_nested_attributes_for :watchers, :allow_destroy => true, |
34 | 34 | :reject_if => proc { |attrs| attrs[:user_id].blank? && attrs[:email].blank? } |
35 | 35 | accepts_nested_attributes_for :issue_tracker, :allow_destroy => true, |
36 | 36 | :reject_if => proc { |attrs| !%w( lighthouseapp redmine ).include?(attrs[:issue_tracker_type]) } |
37 | - | |
37 | + | |
38 | 38 | # Mongoid Bug: find(id) on association proxies returns an Enumerator |
39 | 39 | def self.find_by_id!(app_id) |
40 | 40 | where(:_id => app_id).first || raise(Mongoid::Errors::DocumentNotFound.new(self,app_id)) |
41 | 41 | end |
42 | - | |
42 | + | |
43 | 43 | def self.find_by_api_key!(key) |
44 | 44 | where(:api_key => key).first || raise(Mongoid::Errors::DocumentNotFound.new(self,key)) |
45 | 45 | end |
46 | - | |
46 | + | |
47 | 47 | def last_deploy_at |
48 | 48 | deploys.last && deploys.last.created_at |
49 | 49 | end |
... | ... | @@ -58,9 +58,9 @@ class App |
58 | 58 | !(self[:notify_on_deploys] == false) |
59 | 59 | end |
60 | 60 | alias :notify_on_deploys? :notify_on_deploys |
61 | - | |
61 | + | |
62 | 62 | protected |
63 | - | |
63 | + | |
64 | 64 | def generate_api_key |
65 | 65 | self.api_key ||= ActiveSupport::SecureRandom.hex |
66 | 66 | end | ... | ... |
app/models/err.rb
1 | 1 | class Err |
2 | 2 | include Mongoid::Document |
3 | 3 | include Mongoid::Timestamps |
4 | - | |
4 | + | |
5 | 5 | field :klass |
6 | 6 | field :component |
7 | 7 | field :action |
... | ... | @@ -10,42 +10,44 @@ class Err |
10 | 10 | field :last_notice_at, :type => DateTime |
11 | 11 | field :resolved, :type => Boolean, :default => false |
12 | 12 | field :issue_link, :type => String |
13 | + field :notices_count, :type => Integer, :default => 0 | |
14 | + field :message | |
13 | 15 | |
14 | 16 | index :last_notice_at |
15 | 17 | index :app_id |
16 | 18 | |
17 | 19 | referenced_in :app |
18 | - embeds_many :notices | |
19 | - | |
20 | + references_many :notices | |
21 | + | |
20 | 22 | validates_presence_of :klass, :environment |
21 | - | |
23 | + | |
22 | 24 | scope :resolved, where(:resolved => true) |
23 | 25 | scope :unresolved, where(:resolved => false) |
24 | 26 | scope :ordered, order_by(:last_notice_at.desc) |
25 | 27 | scope :in_env, lambda {|env| where(:environment => env)} |
26 | 28 | scope :for_apps, lambda {|apps| where(:app_id.in => apps.all.map(&:id))} |
27 | - | |
29 | + | |
28 | 30 | def self.for(attrs) |
29 | 31 | app = attrs.delete(:app) |
30 | 32 | app.errs.where(attrs).first || app.errs.create!(attrs) |
31 | 33 | end |
32 | - | |
34 | + | |
33 | 35 | def resolve! |
34 | 36 | self.update_attributes!(:resolved => true) |
35 | 37 | end |
36 | - | |
38 | + | |
37 | 39 | def unresolved? |
38 | 40 | !resolved? |
39 | 41 | end |
40 | - | |
42 | + | |
41 | 43 | def where |
42 | 44 | where = component.dup |
43 | 45 | where << "##{action}" if action.present? |
44 | 46 | where |
45 | 47 | end |
46 | - | |
48 | + | |
47 | 49 | def message |
48 | - notices.first.try(:message) || klass | |
50 | + super || klass | |
49 | 51 | end |
50 | - | |
51 | -end | |
52 | 52 | \ No newline at end of file |
53 | + | |
54 | +end | ... | ... |
app/models/notice.rb
1 | 1 | require 'hoptoad' |
2 | +require 'recurse' | |
2 | 3 | |
3 | 4 | class Notice |
4 | 5 | include Mongoid::Document |
5 | 6 | include Mongoid::Timestamps |
6 | - | |
7 | + | |
7 | 8 | field :message |
8 | 9 | field :backtrace, :type => Array |
9 | 10 | field :server_environment, :type => Hash |
10 | 11 | field :request, :type => Hash |
11 | 12 | field :notifier, :type => Hash |
12 | - | |
13 | - embedded_in :err, :inverse_of => :notices | |
14 | - | |
13 | + | |
14 | + referenced_in :err | |
15 | + index :err_id | |
16 | + | |
15 | 17 | after_create :cache_last_notice_at |
16 | 18 | after_create :deliver_notification, :if => :should_notify? |
17 | - | |
19 | + before_create :increase_counter_cache, :cache_message | |
20 | + before_save :sanitize | |
21 | + before_destroy :decrease_counter_cache | |
22 | + | |
18 | 23 | validates_presence_of :backtrace, :server_environment, :notifier |
19 | - | |
24 | + | |
20 | 25 | scope :ordered, order_by(:created_at.asc) |
21 | - | |
26 | + | |
22 | 27 | def self.from_xml(hoptoad_xml) |
23 | 28 | hoptoad_notice = Hoptoad::V2.parse_xml(hoptoad_xml) |
24 | 29 | app = App.find_by_api_key!(hoptoad_notice['api-key']) |
25 | - | |
30 | + | |
26 | 31 | hoptoad_notice['request'] ||= {} |
27 | 32 | hoptoad_notice['request']['component'] = 'unknown' if hoptoad_notice['request']['component'].blank? |
28 | 33 | hoptoad_notice['request']['action'] = nil if hoptoad_notice['request']['action'].blank? |
29 | - | |
34 | + | |
30 | 35 | err = Err.for({ |
31 | 36 | :app => app, |
32 | 37 | :klass => hoptoad_notice['error']['class'], |
... | ... | @@ -36,7 +41,7 @@ class Notice |
36 | 41 | :fingerprint => hoptoad_notice['fingerprint'] |
37 | 42 | }) |
38 | 43 | err.update_attributes(:resolved => false) if err.resolved? |
39 | - | |
44 | + | |
40 | 45 | err.notices.create!({ |
41 | 46 | :message => hoptoad_notice['error']['message'], |
42 | 47 | :backtrace => hoptoad_notice['error']['backtrace']['line'], |
... | ... | @@ -45,35 +50,67 @@ class Notice |
45 | 50 | :notifier => hoptoad_notice['notifier'] |
46 | 51 | }) |
47 | 52 | end |
48 | - | |
53 | + | |
49 | 54 | def request |
50 | 55 | read_attribute(:request) || {} |
51 | 56 | end |
52 | - | |
57 | + | |
53 | 58 | def env_vars |
54 | 59 | request['cgi-data'] || {} |
55 | 60 | end |
56 | - | |
61 | + | |
57 | 62 | def params |
58 | 63 | request['params'] || {} |
59 | 64 | end |
60 | - | |
65 | + | |
61 | 66 | def session |
62 | 67 | request['session'] || {} |
63 | 68 | end |
64 | - | |
69 | + | |
65 | 70 | def deliver_notification |
66 | 71 | Mailer.err_notification(self).deliver |
67 | 72 | end |
68 | - | |
73 | + | |
69 | 74 | def cache_last_notice_at |
70 | 75 | err.update_attributes(:last_notice_at => created_at) |
71 | 76 | end |
72 | - | |
77 | + | |
73 | 78 | protected |
74 | - | |
75 | - def should_notify? | |
76 | - err.app.notify_on_errs? && Errbit::Config.email_at_notices.include?(err.notices.count) && err.app.watchers.any? | |
79 | + | |
80 | + def should_notify? | |
81 | + err.app.notify_on_errs? && Errbit::Config.email_at_notices.include?(err.notices.count) && err.app.watchers.any? | |
82 | + end | |
83 | + | |
84 | + | |
85 | + def increase_counter_cache | |
86 | + err.inc(:notices_count,1) | |
87 | + end | |
88 | + | |
89 | + def decrease_counter_cache | |
90 | + err.inc(:notices_count,-1) | |
91 | + end | |
92 | + | |
93 | + def cache_message | |
94 | + err.update_attribute(:message, message) if err.notices_count == 1 | |
95 | + end | |
96 | + | |
97 | + def sanitize | |
98 | + [:server_environment, :request, :notifier].each do |h| | |
99 | + send("#{h}=",sanitize_hash(send(h))) | |
77 | 100 | end |
78 | - | |
79 | -end | |
80 | 101 | \ No newline at end of file |
102 | + end | |
103 | + | |
104 | + def sanitize_hash(h) | |
105 | + h.recurse do | |
106 | + |h| h.inject({}) do |h,(k,v)| | |
107 | + if k.is_a?(String) | |
108 | + h[k.gsub(/\./,'.').gsub(/^\$/,'$')] = v | |
109 | + else | |
110 | + h[k] = v | |
111 | + end | |
112 | + h | |
113 | + end | |
114 | + end | |
115 | + end | |
116 | +end | |
117 | + | ... | ... |
app/views/apps/index.html.haml
... | ... | @@ -14,7 +14,7 @@ |
14 | 14 | %td.name= link_to app.name, app_path(app) |
15 | 15 | %td.deploy= app.last_deploy_at ? link_to( app.last_deploy_at.to_s(:micro), app_deploys_path(app)) : 'n/a' |
16 | 16 | %td.count |
17 | - - if app.errs.any? | |
17 | + - if app.errs.count > 0 | |
18 | 18 | = link_to app.errs.unresolved.count, app_errs_path(app) |
19 | 19 | - else |
20 | 20 | \- | ... | ... |
app/views/apps/show.html.haml
app/views/errs/_table.html.haml
... | ... | @@ -18,7 +18,7 @@ |
18 | 18 | %em= err.where |
19 | 19 | %td.latest #{time_ago_in_words(last_notice_at err)} ago |
20 | 20 | %td.deploy= err.app.last_deploy_at ? err.app.last_deploy_at.to_s(:micro) : 'n/a' |
21 | - %td.count= link_to err.notices.count, app_err_path(err.app, err) | |
21 | + %td.count= link_to err.notices_count, app_err_path(err.app, err) | |
22 | 22 | %td.resolve= link_to image_tag("thumbs-up.png"), resolve_app_err_path(err.app, err), :title => "Resolve", :method => :put, :confirm => err_confirm, :class => 'resolve' if err.unresolved? |
23 | 23 | - if errs.none? |
24 | 24 | %tr | ... | ... |
app/views/errs/lighthouseapp_body.txt.erb
app/views/errs/redmine_body.txt.erb
app/views/errs/show.html.haml
... | ... | @@ -20,7 +20,7 @@ |
20 | 20 | %span= link_to 'resolve', resolve_app_err_path(@app, @err), :method => :put, :confirm => err_confirm, :class => 'resolve' |
21 | 21 | |
22 | 22 | %h4= @notice.try(:message) |
23 | - | |
23 | + | |
24 | 24 | = will_paginate @notices, :param_name => :notice, :page_links => false, :class => 'notice-pagination' |
25 | 25 | viewing occurrence #{@notices.current_page} of #{@notices.total_pages} |
26 | 26 | |
... | ... | @@ -36,19 +36,19 @@ viewing occurrence #{@notices.current_page} of #{@notices.total_pages} |
36 | 36 | #summary |
37 | 37 | %h3 Summary |
38 | 38 | = render 'notices/summary', :notice => @notice |
39 | - | |
39 | + | |
40 | 40 | #backtrace |
41 | 41 | %h3 Backtrace |
42 | 42 | = render 'notices/backtrace', :lines => @notice.backtrace |
43 | - | |
43 | + | |
44 | 44 | #environment |
45 | 45 | %h3 Environment |
46 | 46 | = render 'notices/environment', :notice => @notice |
47 | - | |
47 | + | |
48 | 48 | #params |
49 | 49 | %h3 Parameters |
50 | 50 | = render 'notices/params', :notice => @notice |
51 | - | |
51 | + | |
52 | 52 | #session |
53 | 53 | %h3 Session |
54 | 54 | = render 'notices/session', :notice => @notice | ... | ... |
app/views/mailer/err_notification.text.erb
1 | 1 | An err has just occurred in <%= @notice.err.environment %>: <%= @notice.err.message %> |
2 | 2 | |
3 | -This err has occurred <%= pluralize @notice.err.notices.count, 'time' %>. You should really look into it here: | |
3 | +This err has occurred <%= pluralize @notice.err.notices_count, 'time' %>. You should really look into it here: | |
4 | 4 | |
5 | 5 | <%= app_err_url(@app, @notice.err) %> |
6 | - | |
7 | -<%= render :partial => 'signature' %> | |
8 | 6 | \ No newline at end of file |
7 | + | |
8 | +<%= render :partial => 'signature' %> | ... | ... |
app/views/notices/_atom_entry.html.haml
... | ... | @@ -6,13 +6,13 @@ |
6 | 6 | = link_to(notice.request['url'], notice.request['url']) |
7 | 7 | %p |
8 | 8 | %strong Where: |
9 | - = notice.err.where | |
9 | + = notice.err.where | |
10 | 10 | %p |
11 | 11 | %strong Occured: |
12 | 12 | = notice.created_at.to_s(:micro) |
13 | 13 | %p |
14 | 14 | %strong Similar: |
15 | - = notice.err.notices.count - 1 | |
15 | + = notice.err.notices_count - 1 | |
16 | 16 | |
17 | 17 | %h3 Params |
18 | 18 | %p= pretty_hash(notice.params) | ... | ... |
app/views/notices/_environment.html.haml
app/views/notices/_summary.html.haml
db/migrate/20110422152027_move_notices_to_separate_collection.rb
0 → 100644
... | ... | @@ -0,0 +1,22 @@ |
1 | +class MoveNoticesToSeparateCollection < Mongoid::Migration | |
2 | + def self.up | |
3 | + # copy embedded Notices into a separate collection | |
4 | + mongo_db = Err.db | |
5 | + errs = mongo_db.collection("errs").find({ }, :fields => ["notices"]) | |
6 | + errs.each do |err| | |
7 | + next unless err['notices'] | |
8 | + e = Err.find(err['_id']) | |
9 | + puts "Copying notices for Err #{err['_id']}" | |
10 | + err['notices'].each do |notice| | |
11 | + e.notices.create!(notice) | |
12 | + end | |
13 | + mongo_db.collection("errs").update({ "_id" => err['_id']}, { "$unset" => { "notices" => 1}}) | |
14 | + end | |
15 | + Rake::Task["errbit:db:update_notices_count"].invoke | |
16 | + Rake::Task["errbit:db:update_err_message"].invoke | |
17 | + end | |
18 | + | |
19 | + def self.down | |
20 | + end | |
21 | + | |
22 | +end | ... | ... |
lib/hoptoad.rb
1 | 1 | module Hoptoad |
2 | 2 | module V2 |
3 | 3 | require 'digest/md5' |
4 | - | |
4 | + | |
5 | 5 | class ApiVersionError < StandardError |
6 | 6 | def initialize |
7 | 7 | super "Wrong API Version: Expecting v2.0" |
8 | 8 | end |
9 | 9 | end |
10 | - | |
10 | + | |
11 | 11 | def self.parse_xml(xml) |
12 | 12 | parsed = ActiveSupport::XmlMini.backend.parse(xml)['notice'] |
13 | 13 | raise ApiVersionError unless parsed && parsed['version'] == '2.0' |
... | ... | @@ -15,9 +15,9 @@ module Hoptoad |
15 | 15 | rekeyed['fingerprint'] = Digest::MD5.hexdigest(rekeyed['error']['backtrace'].to_s) |
16 | 16 | rekeyed |
17 | 17 | end |
18 | - | |
18 | + | |
19 | 19 | private |
20 | - | |
20 | + | |
21 | 21 | def self.rekey(node) |
22 | 22 | if node.is_a?(Hash) && node.has_key?('var') && node.has_key?('key') |
23 | 23 | {node['key'] => rekey(node['var'])} |
... | ... | @@ -42,4 +42,4 @@ module Hoptoad |
42 | 42 | end |
43 | 43 | end |
44 | 44 | end |
45 | -end | |
46 | 45 | \ No newline at end of file |
46 | +end | ... | ... |
... | ... | @@ -0,0 +1,24 @@ |
1 | +class Hash | |
2 | + | |
3 | + # Apply a block to hash, and recursively apply that block | |
4 | + # to each sub-hash or +types+. | |
5 | + # | |
6 | + # h = {:a=>1, :b=>{:b1=>1, :b2=>2}} | |
7 | + # g = h.recurse{|h| h.inject({}){|h,(k,v)| h[k.to_s] = v; h} } | |
8 | + # g #=> {"a"=>1, "b"=>{"b1"=>1, "b2"=>2}} | |
9 | + # | |
10 | + def recurse(*types, &block) | |
11 | + types = [self.class] if types.empty? | |
12 | + h = inject({}) do |hash, (key, value)| | |
13 | + case value | |
14 | + when *types | |
15 | + hash[key] = value.recurse(*types, &block) | |
16 | + else | |
17 | + hash[key] = value | |
18 | + end | |
19 | + hash | |
20 | + end | |
21 | + yield h | |
22 | + end | |
23 | + | |
24 | +end | ... | ... |
... | ... | @@ -0,0 +1,12 @@ |
1 | +namespace :errbit do | |
2 | + | |
3 | + namespace :db do | |
4 | + desc "Updates Err#notices_count" | |
5 | + task :update_err_message => :environment do | |
6 | + puts "Updating err.message" | |
7 | + Err.all.each do |e| | |
8 | + e.update_attributes(:message => e.notices.first.message) if e.notices.first | |
9 | + end | |
10 | + end | |
11 | + end | |
12 | +end | ... | ... |
... | ... | @@ -0,0 +1,12 @@ |
1 | +namespace :errbit do | |
2 | + | |
3 | + namespace :db do | |
4 | + desc "Updates Err#notices_count" | |
5 | + task :update_notices_count => :environment do | |
6 | + puts "Updating err.notices_count" | |
7 | + Err.all.each do |e| | |
8 | + e.update_attributes(:notices_count => e.notices.count) | |
9 | + end | |
10 | + end | |
11 | + end | |
12 | +end | ... | ... |
spec/controllers/errs_controller_spec.rb
1 | 1 | require 'spec_helper' |
2 | 2 | |
3 | 3 | describe ErrsController do |
4 | - | |
4 | + | |
5 | 5 | it_requires_authentication :for => { |
6 | 6 | :index => :get, :all => :get, :show => :get, :resolve => :put |
7 | 7 | }, |
8 | 8 | :params => {:app_id => 'dummyid', :id => 'dummyid'} |
9 | - | |
9 | + | |
10 | 10 | let(:app) { Factory(:app) } |
11 | 11 | let(:err) { Factory(:err, :app => app) } |
12 | - | |
12 | + | |
13 | 13 | describe "GET /errs" do |
14 | 14 | render_views |
15 | 15 | context 'when logged in as an admin' do |
... | ... | @@ -31,7 +31,7 @@ describe ErrsController do |
31 | 31 | response.should be_success |
32 | 32 | response.body.should match(@err.message) |
33 | 33 | end |
34 | - | |
34 | + | |
35 | 35 | it "should handle lots of errors" do |
36 | 36 | pending "Turning off long running spec" |
37 | 37 | 1000.times { Factory :notice } |
... | ... | @@ -55,7 +55,7 @@ describe ErrsController do |
55 | 55 | end |
56 | 56 | end |
57 | 57 | end |
58 | - | |
58 | + | |
59 | 59 | context 'when logged in as a user' do |
60 | 60 | it 'gets a paginated list of unresolved errs for the users apps' do |
61 | 61 | sign_in(user = Factory(:user)) |
... | ... | @@ -68,7 +68,7 @@ describe ErrsController do |
68 | 68 | end |
69 | 69 | end |
70 | 70 | end |
71 | - | |
71 | + | |
72 | 72 | describe "GET /errs/all" do |
73 | 73 | context 'when logged in as an admin' do |
74 | 74 | it "gets a paginated list of all errs" do |
... | ... | @@ -83,7 +83,7 @@ describe ErrsController do |
83 | 83 | assigns(:errs).should == errs |
84 | 84 | end |
85 | 85 | end |
86 | - | |
86 | + | |
87 | 87 | context 'when logged in as a user' do |
88 | 88 | it 'gets a paginated list of all errs for the users apps' do |
89 | 89 | sign_in(user = Factory(:user)) |
... | ... | @@ -96,29 +96,29 @@ describe ErrsController do |
96 | 96 | end |
97 | 97 | end |
98 | 98 | end |
99 | - | |
99 | + | |
100 | 100 | describe "GET /apps/:app_id/errs/:id" do |
101 | 101 | render_views |
102 | - | |
102 | + | |
103 | 103 | before do |
104 | 104 | 3.times { Factory(:notice, :err => err)} |
105 | 105 | end |
106 | - | |
106 | + | |
107 | 107 | context 'when logged in as an admin' do |
108 | 108 | before do |
109 | 109 | sign_in Factory(:admin) |
110 | 110 | end |
111 | - | |
111 | + | |
112 | 112 | it "finds the app" do |
113 | 113 | get :show, :app_id => app.id, :id => err.id |
114 | 114 | assigns(:app).should == app |
115 | 115 | end |
116 | - | |
116 | + | |
117 | 117 | it "finds the err" do |
118 | 118 | get :show, :app_id => app.id, :id => err.id |
119 | 119 | assigns(:err).should == err |
120 | 120 | end |
121 | - | |
121 | + | |
122 | 122 | it "successfully render page" do |
123 | 123 | get :show, :app_id => app.id, :id => err.id |
124 | 124 | response.should be_success |
... | ... | @@ -131,9 +131,9 @@ describe ErrsController do |
131 | 131 | err = Factory :err |
132 | 132 | get :show, :app_id => err.app.id, :id => err.id |
133 | 133 | |
134 | - response.body.should_not button_matcher | |
134 | + response.body.should_not button_matcher | |
135 | 135 | end |
136 | - | |
136 | + | |
137 | 137 | it "should exist for err's app with issue tracker" do |
138 | 138 | tracker = Factory(:lighthouseapp_tracker) |
139 | 139 | err = Factory(:err, :app => tracker.app) |
... | ... | @@ -141,7 +141,7 @@ describe ErrsController do |
141 | 141 | |
142 | 142 | response.body.should button_matcher |
143 | 143 | end |
144 | - | |
144 | + | |
145 | 145 | it "should not exist for err with issue_link" do |
146 | 146 | tracker = Factory(:lighthouseapp_tracker) |
147 | 147 | err = Factory(:err, :app => tracker.app, :issue_link => "http://some.host") |
... | ... | @@ -151,7 +151,7 @@ describe ErrsController do |
151 | 151 | end |
152 | 152 | end |
153 | 153 | end |
154 | - | |
154 | + | |
155 | 155 | context 'when logged in as a user' do |
156 | 156 | before do |
157 | 157 | sign_in(@user = Factory(:user)) |
... | ... | @@ -160,12 +160,12 @@ describe ErrsController do |
160 | 160 | @watcher = Factory(:user_watcher, :user => @user, :app => @watched_app) |
161 | 161 | @watched_err = Factory(:err, :app => @watched_app) |
162 | 162 | end |
163 | - | |
163 | + | |
164 | 164 | it 'finds the err if the user is watching the app' do |
165 | 165 | get :show, :app_id => @watched_app.to_param, :id => @watched_err.id |
166 | 166 | assigns(:err).should == @watched_err |
167 | 167 | end |
168 | - | |
168 | + | |
169 | 169 | it 'raises a DocumentNotFound error if the user is not watching the app' do |
170 | 170 | lambda { |
171 | 171 | get :show, :app_id => @unwatched_err.app_id, :id => @unwatched_err.id |
... | ... | @@ -173,17 +173,17 @@ describe ErrsController do |
173 | 173 | end |
174 | 174 | end |
175 | 175 | end |
176 | - | |
176 | + | |
177 | 177 | describe "PUT /apps/:app_id/errs/:id/resolve" do |
178 | 178 | before do |
179 | 179 | sign_in Factory(:admin) |
180 | - | |
180 | + | |
181 | 181 | @err = Factory(:err) |
182 | 182 | App.stub(:find).with(@err.app.id).and_return(@err.app) |
183 | 183 | @err.app.errs.stub(:find).and_return(@err) |
184 | 184 | @err.stub(:resolve!) |
185 | 185 | end |
186 | - | |
186 | + | |
187 | 187 | it 'finds the app and the err' do |
188 | 188 | App.should_receive(:find).with(@err.app.id).and_return(@err.app) |
189 | 189 | @err.app.errs.should_receive(:find).and_return(@err) |
... | ... | @@ -191,17 +191,17 @@ describe ErrsController do |
191 | 191 | assigns(:app).should == @err.app |
192 | 192 | assigns(:err).should == @err |
193 | 193 | end |
194 | - | |
194 | + | |
195 | 195 | it "should resolve the issue" do |
196 | 196 | @err.should_receive(:resolve!).and_return(true) |
197 | 197 | put :resolve, :app_id => @err.app.id, :id => @err.id |
198 | 198 | end |
199 | - | |
199 | + | |
200 | 200 | it "should display a message" do |
201 | 201 | put :resolve, :app_id => @err.app.id, :id => @err.id |
202 | 202 | request.flash[:success].should match(/Great news/) |
203 | 203 | end |
204 | - | |
204 | + | |
205 | 205 | it "should redirect to the app page" do |
206 | 206 | put :resolve, :app_id => @err.app.id, :id => @err.id |
207 | 207 | response.should redirect_to(app_path(@err.app)) | ... | ... |
spec/models/err_spec.rb
1 | 1 | require 'spec_helper' |
2 | 2 | |
3 | 3 | describe Err do |
4 | - | |
4 | + | |
5 | 5 | context 'validations' do |
6 | 6 | it 'requires a klass' do |
7 | 7 | err = Factory.build(:err, :klass => nil) |
8 | 8 | err.should_not be_valid |
9 | 9 | err.errors[:klass].should include("can't be blank") |
10 | 10 | end |
11 | - | |
11 | + | |
12 | 12 | it 'requires an environment' do |
13 | 13 | err = Factory.build(:err, :environment => nil) |
14 | 14 | err.should_not be_valid |
15 | 15 | err.errors[:environment].should include("can't be blank") |
16 | 16 | end |
17 | 17 | end |
18 | - | |
18 | + | |
19 | 19 | context '#for' do |
20 | 20 | before do |
21 | 21 | @app = Factory(:app) |
... | ... | @@ -27,16 +27,16 @@ describe Err do |
27 | 27 | :environment => 'production' |
28 | 28 | } |
29 | 29 | end |
30 | - | |
30 | + | |
31 | 31 | it 'returns the correct err if one already exists' do |
32 | 32 | existing = Err.create(@conditions) |
33 | 33 | Err.for(@conditions).should == existing |
34 | 34 | end |
35 | - | |
35 | + | |
36 | 36 | it 'assigns the returned err to the given app' do |
37 | 37 | Err.for(@conditions).app.should == @app |
38 | 38 | end |
39 | - | |
39 | + | |
40 | 40 | it 'creates a new err if a matching one does not already exist' do |
41 | 41 | Err.where(@conditions.except(:app)).exists?.should == false |
42 | 42 | lambda { |
... | ... | @@ -44,36 +44,47 @@ describe Err do |
44 | 44 | }.should change(Err,:count).by(1) |
45 | 45 | end |
46 | 46 | end |
47 | - | |
47 | + | |
48 | 48 | context '#last_notice_at' do |
49 | 49 | it "returns the created_at timestamp of the latest notice" do |
50 | 50 | err = Factory(:err) |
51 | 51 | err.last_notice_at.should be_nil |
52 | - | |
52 | + | |
53 | 53 | notice1 = Factory(:notice, :err => err) |
54 | 54 | err.last_notice_at.should == notice1.created_at |
55 | - | |
55 | + | |
56 | 56 | notice2 = Factory(:notice, :err => err) |
57 | 57 | err.last_notice_at.should == notice2.created_at |
58 | 58 | end |
59 | 59 | end |
60 | - | |
60 | + | |
61 | 61 | context '#message' do |
62 | + it "returns klass by default" do | |
63 | + err = Factory(:err) | |
64 | + err.message.should == err.klass | |
65 | + end | |
66 | + | |
62 | 67 | it 'returns the message from the first notice' do |
63 | 68 | err = Factory(:err) |
64 | 69 | notice1 = Factory(:notice, :err => err, :message => 'ERR 1') |
65 | 70 | notice2 = Factory(:notice, :err => err, :message => 'ERR 2') |
66 | 71 | err.message.should == notice1.message |
67 | 72 | end |
73 | + | |
74 | + it "adding a notice caches its message" do | |
75 | + err = Factory(:err) | |
76 | + lambda { | |
77 | + notice1 = Factory(:notice, :err => err, :message => 'ERR 1')}.should change(err, :message).from(err.klass).to('ERR 1') | |
78 | + end | |
68 | 79 | end |
69 | - | |
80 | + | |
70 | 81 | context "#resolved?" do |
71 | 82 | it "should start out as unresolved" do |
72 | 83 | err = Err.new |
73 | 84 | err.should_not be_resolved |
74 | 85 | err.should be_unresolved |
75 | 86 | end |
76 | - | |
87 | + | |
77 | 88 | it "should be able to be resolved" do |
78 | 89 | err = Factory(:err) |
79 | 90 | err.should_not be_resolved |
... | ... | @@ -81,7 +92,7 @@ describe Err do |
81 | 92 | err.reload.should be_resolved |
82 | 93 | end |
83 | 94 | end |
84 | - | |
95 | + | |
85 | 96 | context "resolve!" do |
86 | 97 | it "marks the err as resolved" do |
87 | 98 | err = Factory(:err) |
... | ... | @@ -89,7 +100,7 @@ describe Err do |
89 | 100 | err.resolve! |
90 | 101 | err.should be_resolved |
91 | 102 | end |
92 | - | |
103 | + | |
93 | 104 | it "should throw an err if it's not successful" do |
94 | 105 | err = Factory(:err) |
95 | 106 | err.should_not be_resolved |
... | ... | @@ -100,7 +111,7 @@ describe Err do |
100 | 111 | }.should raise_error(Mongoid::Errors::Validations) |
101 | 112 | end |
102 | 113 | end |
103 | - | |
114 | + | |
104 | 115 | context "Scopes" do |
105 | 116 | context "resolved" do |
106 | 117 | it 'only finds resolved Errs' do |
... | ... | @@ -110,7 +121,7 @@ describe Err do |
110 | 121 | Err.resolved.all.should_not include(unresolved) |
111 | 122 | end |
112 | 123 | end |
113 | - | |
124 | + | |
114 | 125 | context "unresolved" do |
115 | 126 | it 'only finds unresolved Errs' do |
116 | 127 | resolved = Factory(:err, :resolved => true) |
... | ... | @@ -130,4 +141,30 @@ describe Err do |
130 | 141 | end |
131 | 142 | end |
132 | 143 | end |
133 | -end | |
134 | 144 | \ No newline at end of file |
145 | + | |
146 | + context "notice counter cache" do | |
147 | + | |
148 | + before do | |
149 | + @app = Factory(:app) | |
150 | + @err = Factory(:err, :app => @app) | |
151 | + end | |
152 | + | |
153 | + it "#notices_count returns 0 by default" do | |
154 | + @err.notices_count.should == 0 | |
155 | + end | |
156 | + | |
157 | + it "adding a notice increases #notices_count by 1" do | |
158 | + lambda { | |
159 | + notice1 = Factory(:notice, :err => @err, :message => 'ERR 1')}.should change(@err, :notices_count).from(0).to(1) | |
160 | + end | |
161 | + | |
162 | + it "removing a notice decreases #notices_count by 1" do | |
163 | + notice1 = Factory(:notice, :err => @err, :message => 'ERR 1') | |
164 | + lambda { | |
165 | + @err.notices.first.destroy | |
166 | + }.should change(@err, :notices_count).from(1).to(0) | |
167 | + end | |
168 | + end | |
169 | + | |
170 | + | |
171 | +end | ... | ... |
spec/models/notice_spec.rb
1 | 1 | require 'spec_helper' |
2 | 2 | |
3 | 3 | describe Notice do |
4 | - | |
4 | + | |
5 | 5 | context 'validations' do |
6 | 6 | it 'requires a backtrace' do |
7 | 7 | notice = Factory.build(:notice, :backtrace => nil) |
8 | 8 | notice.should_not be_valid |
9 | 9 | notice.errors[:backtrace].should include("can't be blank") |
10 | 10 | end |
11 | - | |
11 | + | |
12 | 12 | it 'requires the server_environment' do |
13 | 13 | notice = Factory.build(:notice, :server_environment => nil) |
14 | 14 | notice.should_not be_valid |
15 | 15 | notice.errors[:server_environment].should include("can't be blank") |
16 | 16 | end |
17 | - | |
17 | + | |
18 | 18 | it 'requires the notifier' do |
19 | 19 | notice = Factory.build(:notice, :notifier => nil) |
20 | 20 | notice.should_not be_valid |
21 | 21 | notice.errors[:notifier].should include("can't be blank") |
22 | 22 | end |
23 | 23 | end |
24 | - | |
24 | + | |
25 | 25 | context '#from_xml' do |
26 | 26 | before do |
27 | 27 | @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read |
28 | 28 | @app = Factory(:app, :api_key => 'APIKEY') |
29 | 29 | Digest::MD5.stub(:hexdigest).and_return('fingerprintdigest') |
30 | 30 | end |
31 | - | |
31 | + | |
32 | 32 | it 'finds the correct app' do |
33 | 33 | @notice = Notice.from_xml(@xml) |
34 | 34 | @notice.err.app.should == @app |
35 | 35 | end |
36 | - | |
36 | + | |
37 | 37 | it 'finds the correct err for the notice' do |
38 | 38 | Err.should_receive(:for).with({ |
39 | 39 | :app => @app, |
... | ... | @@ -46,7 +46,7 @@ describe Notice do |
46 | 46 | err.notices.stub(:create!) |
47 | 47 | @notice = Notice.from_xml(@xml) |
48 | 48 | end |
49 | - | |
49 | + | |
50 | 50 | it 'marks the err as unresolve if it was previously resolved' do |
51 | 51 | Err.should_receive(:for).with({ |
52 | 52 | :app => @app, |
... | ... | @@ -61,56 +61,70 @@ describe Notice do |
61 | 61 | @notice.err.should == err |
62 | 62 | @notice.err.should_not be_resolved |
63 | 63 | end |
64 | - | |
64 | + | |
65 | 65 | it 'should create a new notice' do |
66 | 66 | @notice = Notice.from_xml(@xml) |
67 | 67 | @notice.should be_persisted |
68 | 68 | end |
69 | - | |
69 | + | |
70 | 70 | it 'assigns an err to the notice' do |
71 | 71 | @notice = Notice.from_xml(@xml) |
72 | 72 | @notice.err.should be_a(Err) |
73 | 73 | end |
74 | - | |
74 | + | |
75 | 75 | it 'captures the err message' do |
76 | 76 | @notice = Notice.from_xml(@xml) |
77 | 77 | @notice.message.should == 'HoptoadTestingException: Testing hoptoad via "rake hoptoad:test". If you can see this, it works.' |
78 | 78 | end |
79 | - | |
79 | + | |
80 | 80 | it 'captures the backtrace' do |
81 | 81 | @notice = Notice.from_xml(@xml) |
82 | 82 | @notice.backtrace.size.should == 73 |
83 | 83 | @notice.backtrace.last['file'].should == '[GEM_ROOT]/bin/rake' |
84 | 84 | end |
85 | - | |
85 | + | |
86 | 86 | it 'captures the server_environment' do |
87 | 87 | @notice = Notice.from_xml(@xml) |
88 | 88 | @notice.server_environment['environment-name'].should == 'development' |
89 | 89 | end |
90 | - | |
90 | + | |
91 | 91 | it 'captures the request' do |
92 | 92 | @notice = Notice.from_xml(@xml) |
93 | 93 | @notice.request['url'].should == 'http://example.org/verify' |
94 | 94 | @notice.request['params']['controller'].should == 'application' |
95 | 95 | end |
96 | - | |
96 | + | |
97 | 97 | it 'captures the notifier' do |
98 | 98 | @notice = Notice.from_xml(@xml) |
99 | 99 | @notice.notifier['name'].should == 'Hoptoad Notifier' |
100 | 100 | end |
101 | 101 | |
102 | - it "should handle params withour 'request' section" do | |
102 | + it "should handle params without 'request' section" do | |
103 | 103 | @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice_without_request_section.xml').read |
104 | 104 | lambda { Notice.from_xml(@xml) }.should_not raise_error |
105 | 105 | end |
106 | 106 | end |
107 | - | |
107 | + | |
108 | + describe "key sanitization" do | |
109 | + before do | |
110 | + @hash = { "some.key" => { "$nested.key" => {"$Path" => "/", "some$key" => "key"}}} | |
111 | + @hash_sanitized = { "some.key" => { "$nested.key" => {"$Path" => "/", "some$key" => "key"}}} | |
112 | + end | |
113 | + [:server_environment, :request, :notifier].each do |key| | |
114 | + it "replaces . with . and $ with $ in keys used in #{key}" do | |
115 | + err = Factory(:err) | |
116 | + notice = Factory(:notice, :err => err, key => @hash) | |
117 | + notice.send(key).should == @hash_sanitized | |
118 | + end | |
119 | + end | |
120 | + end | |
121 | + | |
108 | 122 | describe "email notifications" do |
109 | 123 | before do |
110 | 124 | @app = Factory(:app_with_watcher) |
111 | 125 | @err = Factory(:err, :app => @app) |
112 | 126 | end |
113 | - | |
127 | + | |
114 | 128 | Errbit::Config.email_at_notices.each do |threshold| |
115 | 129 | it "sends an email notification after #{threshold} notice(s)" do |
116 | 130 | @err.notices.stub(:count).and_return(threshold) |
... | ... | @@ -120,5 +134,5 @@ describe Notice do |
120 | 134 | end |
121 | 135 | end |
122 | 136 | end |
123 | - | |
124 | -end | |
125 | 137 | \ No newline at end of file |
138 | + | |
139 | +end | ... | ... |