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