Commit 89595a9aee8be6126ab1e3178bd23c7ee71389eb
Exists in
master
and in
1 other branch
Merge remote branch 'boblail/merge-rebase' into boblail. Tweaked some changed fi…
…les, fixed whitespace, upgraded RSpec to 2.6.0 Conflicts: app/helpers/errs_helper.rb app/mailers/mailer.rb app/models/notice.rb app/views/errs/_table.html.haml app/views/errs/show.html.haml app/views/notices/_summary.html.haml spec/models/issue_trackers/redmine_tracker_spec.rb spec/views/errs/show.html.haml_spec.rb
Showing
96 changed files
with
1540 additions
and
820 deletions
Show diff stats
.gitignore
Gemfile
@@ -24,7 +24,7 @@ platform :ruby do | @@ -24,7 +24,7 @@ platform :ruby do | ||
24 | end | 24 | end |
25 | 25 | ||
26 | group :development, :test do | 26 | group :development, :test do |
27 | - gem 'rspec-rails', '~> 2.5' | 27 | + gem 'rspec-rails', '~> 2.6' |
28 | gem 'webmock', :require => false | 28 | gem 'webmock', :require => false |
29 | gem 'factory_girl_rails' | 29 | gem 'factory_girl_rails' |
30 | unless ENV['TRAVIS'] | 30 | unless ENV['TRAVIS'] |
@@ -34,7 +34,7 @@ group :development, :test do | @@ -34,7 +34,7 @@ group :development, :test do | ||
34 | end | 34 | end |
35 | 35 | ||
36 | group :test do | 36 | group :test do |
37 | - gem 'rspec', '~> 2.5' | 37 | + gem 'rspec', '~> 2.6' |
38 | gem 'database_cleaner', '~> 0.6.0' | 38 | gem 'database_cleaner', '~> 0.6.0' |
39 | gem 'email_spec' | 39 | gem 'email_spec' |
40 | end | 40 | end |
Gemfile.lock
@@ -57,7 +57,7 @@ GEM | @@ -57,7 +57,7 @@ GEM | ||
57 | bcrypt-ruby (~> 2.1.2) | 57 | bcrypt-ruby (~> 2.1.2) |
58 | orm_adapter (~> 0.0.3) | 58 | orm_adapter (~> 0.0.3) |
59 | warden (~> 1.0.3) | 59 | warden (~> 1.0.3) |
60 | - diff-lcs (1.1.2) | 60 | + diff-lcs (1.1.3) |
61 | email_spec (1.1.1) | 61 | email_spec (1.1.1) |
62 | rspec (~> 2.0) | 62 | rspec (~> 2.0) |
63 | erubis (2.6.6) | 63 | erubis (2.6.6) |
@@ -158,19 +158,19 @@ GEM | @@ -158,19 +158,19 @@ GEM | ||
158 | responders (0.6.4) | 158 | responders (0.6.4) |
159 | rest-client (1.5.1) | 159 | rest-client (1.5.1) |
160 | mime-types (>= 1.16) | 160 | mime-types (>= 1.16) |
161 | - rspec (2.5.0) | ||
162 | - rspec-core (~> 2.5.0) | ||
163 | - rspec-expectations (~> 2.5.0) | ||
164 | - rspec-mocks (~> 2.5.0) | ||
165 | - rspec-core (2.5.1) | ||
166 | - rspec-expectations (2.5.0) | 161 | + rspec (2.6.0) |
162 | + rspec-core (~> 2.6.0) | ||
163 | + rspec-expectations (~> 2.6.0) | ||
164 | + rspec-mocks (~> 2.6.0) | ||
165 | + rspec-core (2.6.4) | ||
166 | + rspec-expectations (2.6.0) | ||
167 | diff-lcs (~> 1.1.2) | 167 | diff-lcs (~> 1.1.2) |
168 | - rspec-mocks (2.5.0) | ||
169 | - rspec-rails (2.5.0) | 168 | + rspec-mocks (2.6.0) |
169 | + rspec-rails (2.6.1) | ||
170 | actionpack (~> 3.0) | 170 | actionpack (~> 3.0) |
171 | activesupport (~> 3.0) | 171 | activesupport (~> 3.0) |
172 | railties (~> 3.0) | 172 | railties (~> 3.0) |
173 | - rspec (~> 2.5.0) | 173 | + rspec (~> 2.6.0) |
174 | ruby-debug (0.10.4) | 174 | ruby-debug (0.10.4) |
175 | columnize (>= 0.1) | 175 | columnize (>= 0.1) |
176 | ruby-debug-base (~> 0.10.4.0) | 176 | ruby-debug-base (~> 0.10.4.0) |
@@ -232,8 +232,8 @@ DEPENDENCIES | @@ -232,8 +232,8 @@ DEPENDENCIES | ||
232 | pivotal-tracker | 232 | pivotal-tracker |
233 | rails (= 3.0.10) | 233 | rails (= 3.0.10) |
234 | redmine_client! | 234 | redmine_client! |
235 | - rspec (~> 2.5) | ||
236 | - rspec-rails (~> 2.5) | 235 | + rspec (~> 2.6) |
236 | + rspec-rails (~> 2.6) | ||
237 | ruby-debug | 237 | ruby-debug |
238 | ruby-debug19 | 238 | ruby-debug19 |
239 | ruby-fogbugz | 239 | ruby-fogbugz |
app/controllers/application_controller.rb
@@ -10,11 +10,20 @@ class ApplicationController < ActionController::Base | @@ -10,11 +10,20 @@ class ApplicationController < ActionController::Base | ||
10 | (location == root_path && App.count == 1) ? app_path(App.first) : location | 10 | (location == root_path && App.count == 1) ? app_path(App.first) : location |
11 | end | 11 | end |
12 | 12 | ||
13 | - protected | 13 | + rescue_from ActionController::RedirectBackError, :with => :redirect_to_root |
14 | + | ||
15 | + | ||
16 | +protected | ||
17 | + | ||
18 | + | ||
19 | + def require_admin! | ||
20 | + redirect_to_root unless user_signed_in? && current_user.admin? | ||
21 | + end | ||
22 | + | ||
23 | + def redirect_to_root | ||
24 | + redirect_to(root_path) | ||
25 | + end | ||
14 | 26 | ||
15 | - def require_admin! | ||
16 | - redirect_to root_path unless user_signed_in? && current_user.admin? | ||
17 | - end | ||
18 | 27 | ||
19 | end | 28 | end |
20 | 29 |
app/controllers/apps_controller.rb
1 | class AppsController < InheritedResources::Base | 1 | class AppsController < InheritedResources::Base |
2 | - | ||
3 | before_filter :require_admin!, :except => [:index, :show] | 2 | before_filter :require_admin!, :except => [:index, :show] |
4 | before_filter :parse_email_at_notices_or_set_default, :only => [:create, :update] | 3 | before_filter :parse_email_at_notices_or_set_default, :only => [:create, :update] |
5 | respond_to :html | 4 | respond_to :html |
6 | 5 | ||
6 | + | ||
7 | def show | 7 | def show |
8 | respond_to do |format| | 8 | respond_to do |format| |
9 | format.html do | 9 | format.html do |
10 | @all_errs = !!params[:all_errs] | 10 | @all_errs = !!params[:all_errs] |
11 | 11 | ||
12 | - @errs = resource.errs | ||
13 | - @errs = @errs.unresolved unless @all_errs | ||
14 | - @errs = @errs.in_env(params[:environment]).ordered.paginate(:page => params[:page], :per_page => current_user.per_page) | 12 | + @problems = resource.problems |
13 | + @problems = @problems.unresolved unless @all_errs | ||
14 | + @problems = @problems.in_env(params[:environment]).ordered.paginate(:page => params[:page], :per_page => current_user.per_page) | ||
15 | 15 | ||
16 | + @selected_problems = params[:problems] || [] | ||
16 | @deploys = @app.deploys.order_by(:created_at.desc).limit(5) | 17 | @deploys = @app.deploys.order_by(:created_at.desc).limit(5) |
17 | end | 18 | end |
18 | format.atom do | 19 | format.atom do |
19 | - @errs = resource.errs.unresolved.ordered | 20 | + @problems = resource.problems.unresolved.ordered |
20 | end | 21 | end |
21 | end | 22 | end |
22 | end | 23 | end |
@@ -49,7 +50,7 @@ class AppsController < InheritedResources::Base | @@ -49,7 +50,7 @@ class AppsController < InheritedResources::Base | ||
49 | # Caches the unresolved err counts while performing the sort. | 50 | # Caches the unresolved err counts while performing the sort. |
50 | @unresolved_counts = {} | 51 | @unresolved_counts = {} |
51 | @apps ||= end_of_association_chain.all.sort{|a,b| | 52 | @apps ||= end_of_association_chain.all.sort{|a,b| |
52 | - [a,b].each{|app| @unresolved_counts[app.id] ||= app.errs.unresolved.count } | 53 | + [a,b].each{|app| @unresolved_counts[app.id] ||= app.problems.unresolved.count } |
53 | @unresolved_counts[b.id] <=> @unresolved_counts[a.id] | 54 | @unresolved_counts[b.id] <=> @unresolved_counts[a.id] |
54 | } | 55 | } |
55 | end | 56 | end |
app/controllers/deploys_controller.rb
@@ -18,9 +18,9 @@ class DeploysController < ApplicationController | @@ -18,9 +18,9 @@ class DeploysController < ApplicationController | ||
18 | 18 | ||
19 | @deploys = app.deploys.order_by(:created_at.desc).paginate(:page => params[:page], :per_page => 10) | 19 | @deploys = app.deploys.order_by(:created_at.desc).paginate(:page => params[:page], :per_page => 10) |
20 | end | 20 | end |
21 | - | 21 | + |
22 | private | 22 | private |
23 | - | 23 | + |
24 | def default_deploy | 24 | def default_deploy |
25 | if params[:deploy] | 25 | if params[:deploy] |
26 | { | 26 | { |
@@ -32,7 +32,7 @@ class DeploysController < ApplicationController | @@ -32,7 +32,7 @@ class DeploysController < ApplicationController | ||
32 | } | 32 | } |
33 | end | 33 | end |
34 | end | 34 | end |
35 | - | 35 | + |
36 | # handle Heroku's HTTP post deployhook format | 36 | # handle Heroku's HTTP post deployhook format |
37 | def heroku_deploy | 37 | def heroku_deploy |
38 | { | 38 | { |
@@ -42,5 +42,6 @@ class DeploysController < ApplicationController | @@ -42,5 +42,6 @@ class DeploysController < ApplicationController | ||
42 | :revision => params[:head], | 42 | :revision => params[:head], |
43 | } | 43 | } |
44 | end | 44 | end |
45 | - | 45 | + |
46 | end | 46 | end |
47 | + |
app/controllers/errs_controller.rb
1 | class ErrsController < ApplicationController | 1 | class ErrsController < ApplicationController |
2 | + include ActionView::Helpers::TextHelper | ||
3 | + | ||
4 | + before_filter :find_app, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several] | ||
5 | + before_filter :find_problem, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several] | ||
6 | + before_filter :find_selected_problems, :only => [:destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several] | ||
7 | + | ||
2 | 8 | ||
3 | - before_filter :find_app, :except => [:index, :all] | ||
4 | - before_filter :find_err, :except => [:index, :all] | ||
5 | 9 | ||
6 | def index | 10 | def index |
7 | app_scope = current_user.admin? ? App.all : current_user.apps | 11 | app_scope = current_user.admin? ? App.all : current_user.apps |
8 | - @errs = Err.for_apps(app_scope).in_env(params[:environment]).unresolved.ordered | 12 | + @problems = Problem.for_apps(app_scope).in_env(params[:environment]).unresolved.ordered |
13 | + @selected_problems = params[:problems] || [] | ||
9 | respond_to do |format| | 14 | respond_to do |format| |
10 | format.html do | 15 | format.html do |
11 | - @errs = @errs.paginate(:page => params[:page], :per_page => current_user.per_page) | 16 | + @problems = @problems.paginate(:page => params[:page], :per_page => current_user.per_page) |
12 | end | 17 | end |
13 | format.atom | 18 | format.atom |
14 | end | 19 | end |
15 | end | 20 | end |
16 | 21 | ||
22 | + | ||
17 | def all | 23 | def all |
18 | app_scope = current_user.admin? ? App.all : current_user.apps | 24 | app_scope = current_user.admin? ? App.all : current_user.apps |
19 | - @errs = Err.for_apps(app_scope).ordered.paginate(:page => params[:page], :per_page => current_user.per_page) | 25 | + @problems = Problem.for_apps(app_scope).ordered.paginate(:page => params[:page], :per_page => current_user.per_page) |
26 | + @selected_problems = params[:problems] || [] | ||
20 | end | 27 | end |
21 | 28 | ||
29 | + | ||
22 | def show | 30 | def show |
23 | - page = (params[:notice] || @err.notices_count) | 31 | + page = (params[:notice] || @problem.notices_count) |
24 | page = 1 if page.to_i.zero? | 32 | page = 1 if page.to_i.zero? |
25 | - @notices = @err.notices.ordered.paginate(:page => page, :per_page => 1) | 33 | + @notices = @problem.notices.paginate(:page => page, :per_page => 1) |
26 | @notice = @notices.first | 34 | @notice = @notices.first |
27 | @comment = Comment.new | 35 | @comment = Comment.new |
28 | end | 36 | end |
29 | 37 | ||
38 | + | ||
30 | def create_issue | 39 | def create_issue |
31 | set_tracker_params | 40 | set_tracker_params |
32 | 41 | ||
33 | if @app.issue_tracker | 42 | if @app.issue_tracker |
34 | - @app.issue_tracker.create_issue @err | 43 | + @app.issue_tracker.create_issue @problem |
35 | else | 44 | else |
36 | flash[:error] = "This app has no issue tracker setup." | 45 | flash[:error] = "This app has no issue tracker setup." |
37 | end | 46 | end |
38 | - redirect_to app_err_path(@app, @err) | 47 | + redirect_to app_err_path(@app, @problem) |
39 | rescue ActiveResource::ConnectionError => e | 48 | rescue ActiveResource::ConnectionError => e |
40 | Rails.logger.error e.to_s | 49 | Rails.logger.error e.to_s |
41 | flash[:error] = "There was an error during issue creation. Check your tracker settings or try again later." | 50 | flash[:error] = "There was an error during issue creation. Check your tracker settings or try again later." |
42 | - redirect_to app_err_path(@app, @err) | 51 | + redirect_to app_err_path(@app, @problem) |
43 | end | 52 | end |
44 | 53 | ||
54 | + | ||
45 | def unlink_issue | 55 | def unlink_issue |
46 | - @err.update_attribute :issue_link, nil | ||
47 | - redirect_to app_err_path(@app, @err) | 56 | + @problem.update_attribute :issue_link, nil |
57 | + redirect_to app_err_path(@app, @problem) | ||
48 | end | 58 | end |
49 | 59 | ||
60 | + | ||
50 | def resolve | 61 | def resolve |
51 | # Deal with bug in mongoid where find is returning an Enumberable obj | 62 | # Deal with bug in mongoid where find is returning an Enumberable obj |
52 | - @err = @err.first if @err.respond_to?(:first) | ||
53 | - | ||
54 | - @err.resolve! | 63 | + @problem = @problem.first if @problem.respond_to?(:first) |
55 | 64 | ||
65 | + @problem.resolve! | ||
56 | flash[:success] = 'Great news everyone! The err has been resolved.' | 66 | flash[:success] = 'Great news everyone! The err has been resolved.' |
57 | - | ||
58 | redirect_to :back | 67 | redirect_to :back |
59 | rescue ActionController::RedirectBackError | 68 | rescue ActionController::RedirectBackError |
60 | redirect_to app_path(@app) | 69 | redirect_to app_path(@app) |
@@ -64,15 +73,16 @@ class ErrsController < ApplicationController | @@ -64,15 +73,16 @@ class ErrsController < ApplicationController | ||
64 | def create_comment | 73 | def create_comment |
65 | @comment = Comment.new(params[:comment].merge(:user_id => current_user.id)) | 74 | @comment = Comment.new(params[:comment].merge(:user_id => current_user.id)) |
66 | if @comment.valid? | 75 | if @comment.valid? |
67 | - @err.comments << @comment | ||
68 | - @err.save | 76 | + @problem.comments << @comment |
77 | + @problem.save | ||
69 | flash[:success] = "Comment saved!" | 78 | flash[:success] = "Comment saved!" |
70 | else | 79 | else |
71 | flash[:error] = "I'm sorry, your comment was blank! Try again?" | 80 | flash[:error] = "I'm sorry, your comment was blank! Try again?" |
72 | end | 81 | end |
73 | - redirect_to app_err_path(@app, @err) | 82 | + redirect_to app_err_path(@app, @problem) |
74 | end | 83 | end |
75 | 84 | ||
85 | + | ||
76 | def destroy_comment | 86 | def destroy_comment |
77 | @comment = Comment.find(params[:comment_id]) | 87 | @comment = Comment.find(params[:comment_id]) |
78 | if @comment.destroy | 88 | if @comment.destroy |
@@ -80,29 +90,86 @@ class ErrsController < ApplicationController | @@ -80,29 +90,86 @@ class ErrsController < ApplicationController | ||
80 | else | 90 | else |
81 | flash[:error] = "Sorry, I couldn't delete your comment for some reason. I hope you don't have any sensitive information in there!" | 91 | flash[:error] = "Sorry, I couldn't delete your comment for some reason. I hope you don't have any sensitive information in there!" |
82 | end | 92 | end |
83 | - redirect_to app_err_path(@app, @err) | 93 | + redirect_to app_err_path(@app, @problem) |
94 | + end | ||
95 | + | ||
96 | + | ||
97 | + def resolve_several | ||
98 | + @selected_problems.each(&:resolve!) | ||
99 | + flash[:success] = "Great news everyone! #{pluralize(@selected_problems.count, 'err has', 'errs have')} been resolved." | ||
100 | + redirect_to :back | ||
84 | end | 101 | end |
85 | 102 | ||
86 | 103 | ||
87 | - protected | 104 | + def unresolve_several |
105 | + @selected_problems.each(&:unresolve!) | ||
106 | + flash[:success] = "#{pluralize(@selected_problems.count, 'err has', 'errs have')} been unresolved." | ||
107 | + redirect_to :back | ||
108 | + end | ||
88 | 109 | ||
89 | - def find_app | ||
90 | - @app = App.find(params[:app_id]) | ||
91 | 110 | ||
92 | - # Mongoid Bug: could not chain: current_user.apps.find_by_id! | ||
93 | - # apparently finding by 'watchers.email' and 'id' is broken | ||
94 | - raise(Mongoid::Errors::DocumentNotFound.new(App,@app.id)) unless current_user.admin? || current_user.watching?(@app) | 111 | + def merge_several |
112 | + if @selected_problems.length < 2 | ||
113 | + flash[:notice] = "You must select at least two errors to merge" | ||
114 | + else | ||
115 | + @merged_problem = Problem.merge!(@selected_problems) | ||
116 | + flash[:notice] = "#{@selected_problems.count} errors have been merged." | ||
95 | end | 117 | end |
118 | + redirect_to :back | ||
119 | + end | ||
96 | 120 | ||
97 | - def find_err | ||
98 | - @err = @app.errs.find(params[:id]) | ||
99 | - end | ||
100 | 121 | ||
101 | - def set_tracker_params | ||
102 | - IssueTracker.default_url_options[:host] = request.host | ||
103 | - IssueTracker.default_url_options[:port] = request.port | ||
104 | - IssueTracker.default_url_options[:protocol] = request.scheme | 122 | + def unmerge_several |
123 | + all = @selected_problems.map(&:unmerge!).flatten | ||
124 | + flash[:success] = "#{pluralize(all.length, 'err has', 'errs have')} been unmerged." | ||
125 | + redirect_to :back | ||
126 | + end | ||
127 | + | ||
128 | + | ||
129 | + def destroy_several | ||
130 | + @selected_problems.each(&:destroy) | ||
131 | + flash[:notice] = "#{pluralize(@selected_problems.count, 'err has', 'errs have')} been deleted." | ||
132 | + redirect_to :back | ||
133 | + end | ||
134 | + | ||
135 | + | ||
136 | +protected | ||
137 | + | ||
138 | + | ||
139 | + def find_app | ||
140 | + @app = App.find(params[:app_id]) | ||
141 | + | ||
142 | + # Mongoid Bug: could not chain: current_user.apps.find_by_id! | ||
143 | + # apparently finding by 'watchers.email' and 'id' is broken | ||
144 | + raise(Mongoid::Errors::DocumentNotFound.new(App,@app.id)) unless current_user.admin? || current_user.watching?(@app) | ||
145 | + end | ||
146 | + | ||
147 | + | ||
148 | + def find_problem | ||
149 | + @problem = @app.problems.find(params[:id]) | ||
150 | + | ||
151 | + # Deal with bug in mogoid where find is returning an Enumberable obj | ||
152 | + @problem = @problem.first if @problem.respond_to?(:first) | ||
153 | + end | ||
154 | + | ||
155 | + | ||
156 | + def set_tracker_params | ||
157 | + IssueTracker.default_url_options[:host] = request.host | ||
158 | + IssueTracker.default_url_options[:port] = request.port | ||
159 | + IssueTracker.default_url_options[:protocol] = request.scheme | ||
160 | + end | ||
161 | + | ||
162 | + | ||
163 | + def find_selected_problems | ||
164 | + err_ids = (params[:problems] || []).compact | ||
165 | + if err_ids.empty? | ||
166 | + flash[:notice] = "You have not selected any errors" | ||
167 | + redirect_to :back | ||
168 | + else | ||
169 | + @selected_problems = Array(Problem.find(err_ids)) | ||
105 | end | 170 | end |
171 | + end | ||
172 | + | ||
106 | 173 | ||
107 | end | 174 | end |
108 | 175 |
app/controllers/notices_controller.rb
@@ -5,8 +5,9 @@ class NoticesController < ApplicationController | @@ -5,8 +5,9 @@ class NoticesController < ApplicationController | ||
5 | 5 | ||
6 | def create | 6 | def create |
7 | # params[:data] if the notice came from a GET request, raw_post if it came via POST | 7 | # params[:data] if the notice came from a GET request, raw_post if it came via POST |
8 | - @notice = Notice.from_xml(params[:data] || request.raw_post) | 8 | + @notice = App.report_error!(params[:data] || request.raw_post) |
9 | respond_with @notice | 9 | respond_with @notice |
10 | end | 10 | end |
11 | 11 | ||
12 | end | 12 | end |
13 | + |
app/controllers/users_controller.rb
app/helpers/errs_helper.rb
app/helpers/form_helper.rb
app/helpers/hash_helper.rb
app/helpers/navigation_helper.rb
app/mailers/mailer.rb
@@ -7,10 +7,10 @@ class Mailer < ActionMailer::Base | @@ -7,10 +7,10 @@ class Mailer < ActionMailer::Base | ||
7 | 7 | ||
8 | def err_notification(notice) | 8 | def err_notification(notice) |
9 | @notice = notice | 9 | @notice = notice |
10 | - @app = notice.err.app | 10 | + @app = notice.app |
11 | 11 | ||
12 | mail :to => @app.notification_recipients, | 12 | mail :to => @app.notification_recipients, |
13 | - :subject => "[#{@app.name}][#{@notice.err.environment}] #{@notice.err.message}" | 13 | + :subject => "[#{@app.name}][#{@notice.environment_name}] #{@notice.message}" |
14 | end | 14 | end |
15 | 15 | ||
16 | def deploy_notification(deploy) | 16 | def deploy_notification(deploy) |
app/models/app.rb
@@ -13,6 +13,7 @@ class App | @@ -13,6 +13,7 @@ class App | ||
13 | 13 | ||
14 | # Some legacy apps may have string as key instead of BSON::ObjectID | 14 | # Some legacy apps may have string as key instead of BSON::ObjectID |
15 | identity :type => String | 15 | identity :type => String |
16 | + | ||
16 | # There seems to be a Mongoid bug making it impossible to use String identity with references_many feature: | 17 | # There seems to be a Mongoid bug making it impossible to use String identity with references_many feature: |
17 | # https://github.com/mongoid/mongoid/issues/703 | 18 | # https://github.com/mongoid/mongoid/issues/703 |
18 | # Using 32 character string as a workaround. | 19 | # Using 32 character string as a workaround. |
@@ -23,7 +24,7 @@ class App | @@ -23,7 +24,7 @@ class App | ||
23 | embeds_many :watchers | 24 | embeds_many :watchers |
24 | embeds_many :deploys | 25 | embeds_many :deploys |
25 | embeds_one :issue_tracker | 26 | embeds_one :issue_tracker |
26 | - has_many :errs, :inverse_of => :app, :dependent => :destroy | 27 | + has_many :problems, :inverse_of => :app, :dependent => :destroy |
27 | 28 | ||
28 | before_validation :generate_api_key, :on => :create | 29 | before_validation :generate_api_key, :on => :create |
29 | before_save :normalize_github_url | 30 | before_save :normalize_github_url |
@@ -39,6 +40,50 @@ class App | @@ -39,6 +40,50 @@ class App | ||
39 | accepts_nested_attributes_for :issue_tracker, :allow_destroy => true, | 40 | accepts_nested_attributes_for :issue_tracker, :allow_destroy => true, |
40 | :reject_if => proc { |attrs| !IssueTracker.subclasses.map(&:to_s).include?(attrs[:type].to_s) } | 41 | :reject_if => proc { |attrs| !IssueTracker.subclasses.map(&:to_s).include?(attrs[:type].to_s) } |
41 | 42 | ||
43 | + | ||
44 | + # Processes a new error report. | ||
45 | + # | ||
46 | + # Accepts either XML or a hash with the following attributes: | ||
47 | + # | ||
48 | + # * <tt>:klass</tt> - the class of error | ||
49 | + # * <tt>:message</tt> - the error message | ||
50 | + # * <tt>:backtrace</tt> - an array of stack trace lines | ||
51 | + # | ||
52 | + # * <tt>:request</tt> - a hash of values describing the request | ||
53 | + # * <tt>:server_environment</tt> - a hash of values describing the server environment | ||
54 | + # | ||
55 | + # * <tt>:api_key</tt> - the API key with which the error was reported | ||
56 | + # * <tt>:notifier</tt> - information to identify the source of the error report | ||
57 | + # | ||
58 | + def self.report_error!(*args) | ||
59 | + report = ErrorReport.new(*args) | ||
60 | + report.generate_notice! | ||
61 | + end | ||
62 | + | ||
63 | + | ||
64 | + # Processes a new error report. | ||
65 | + # | ||
66 | + # Accepts a hash with the following attributes: | ||
67 | + # | ||
68 | + # * <tt>:klass</tt> - the class of error | ||
69 | + # * <tt>:message</tt> - the error message | ||
70 | + # * <tt>:backtrace</tt> - an array of stack trace lines | ||
71 | + # | ||
72 | + # * <tt>:request</tt> - a hash of values describing the request | ||
73 | + # * <tt>:server_environment</tt> - a hash of values describing the server environment | ||
74 | + # | ||
75 | + # * <tt>:notifier</tt> - information to identify the source of the error report | ||
76 | + # | ||
77 | + def report_error!(hash) | ||
78 | + report = ErrorReport.new(hash.merge(:api_key => api_key)) | ||
79 | + report.generate_notice! | ||
80 | + end | ||
81 | + | ||
82 | + def find_or_create_err!(attrs) | ||
83 | + Err.where(attrs).first || problems.create!.errs.create!(attrs) | ||
84 | + end | ||
85 | + | ||
86 | + # Mongoid Bug: find(id) on association proxies returns an Enumerator | ||
42 | def self.find_by_id!(app_id) | 87 | def self.find_by_id!(app_id) |
43 | find app_id | 88 | find app_id |
44 | end | 89 | end |
@@ -51,6 +96,7 @@ class App | @@ -51,6 +96,7 @@ class App | ||
51 | deploys.last && deploys.last.created_at | 96 | deploys.last && deploys.last.created_at |
52 | end | 97 | end |
53 | 98 | ||
99 | + | ||
54 | # Legacy apps don't have notify_on_errs and notify_on_deploys params | 100 | # Legacy apps don't have notify_on_errs and notify_on_deploys params |
55 | def notify_on_errs | 101 | def notify_on_errs |
56 | !(self[:notify_on_errs] == false) | 102 | !(self[:notify_on_errs] == false) |
@@ -62,6 +108,7 @@ class App | @@ -62,6 +108,7 @@ class App | ||
62 | end | 108 | end |
63 | alias :notify_on_deploys? :notify_on_deploys | 109 | alias :notify_on_deploys? :notify_on_deploys |
64 | 110 | ||
111 | + | ||
65 | def github_url? | 112 | def github_url? |
66 | self.github_url.present? | 113 | self.github_url.present? |
67 | end | 114 | end |
app/models/deploy.rb
@@ -22,7 +22,7 @@ class Deploy | @@ -22,7 +22,7 @@ class Deploy | ||
22 | end | 22 | end |
23 | 23 | ||
24 | def resolve_app_errs | 24 | def resolve_app_errs |
25 | - app.errs.unresolved.in_env(environment).each {|err| err.resolve!} | 25 | + app.problems.unresolved.in_env(environment).each {|problem| problem.resolve!} |
26 | end | 26 | end |
27 | 27 | ||
28 | def short_revision | 28 | def short_revision |
app/models/err.rb
1 | +# Represents a set of Notices which can be automatically | ||
2 | +# determined to refer to the same Error (Errbit groups | ||
3 | +# notices into errs by a notice's fingerprint.) | ||
4 | + | ||
1 | class Err | 5 | class Err |
2 | include Mongoid::Document | 6 | include Mongoid::Document |
3 | include Mongoid::Timestamps | 7 | include Mongoid::Timestamps |
@@ -7,53 +11,18 @@ class Err | @@ -7,53 +11,18 @@ class Err | ||
7 | field :action | 11 | field :action |
8 | field :environment | 12 | field :environment |
9 | field :fingerprint | 13 | field :fingerprint |
10 | - field :last_notice_at, :type => DateTime | ||
11 | - field :resolved, :type => Boolean, :default => false | ||
12 | - field :issue_link, :type => String | ||
13 | - field :notices_count, :type => Integer, :default => 0 | ||
14 | - field :message | ||
15 | 14 | ||
16 | - index :last_notice_at | ||
17 | - index :app_id | ||
18 | - index :notices | 15 | + belongs_to :problem |
16 | + index :problem_id | ||
19 | 17 | ||
20 | - belongs_to :app | ||
21 | - has_many :notices | ||
22 | - has_many :comments, :inverse_of => :err, :dependent => :destroy | 18 | + has_many :notices, :inverse_of => :err, :dependent => :destroy |
23 | 19 | ||
24 | validates_presence_of :klass, :environment | 20 | validates_presence_of :klass, :environment |
25 | 21 | ||
26 | - scope :resolved, where(:resolved => true) | ||
27 | - scope :unresolved, where(:resolved => false) | ||
28 | - scope :ordered, order_by(:last_notice_at.desc) | ||
29 | - scope :for_apps, lambda {|apps| where(:app_id.in => apps.all.map(&:id))} | ||
30 | - | ||
31 | - def self.in_env(env) | ||
32 | - env.present? ? where(:environment => env) : scoped | ||
33 | - end | ||
34 | - | ||
35 | - def self.for(attrs) | ||
36 | - app = attrs.delete(:app) | ||
37 | - app.errs.where(attrs).first || app.errs.create!(attrs) | ||
38 | - end | ||
39 | - | ||
40 | - def resolve! | ||
41 | - self.update_attributes!(:resolved => true) | ||
42 | - end | ||
43 | - | ||
44 | - def unresolved? | ||
45 | - !resolved? | ||
46 | - end | ||
47 | - | ||
48 | - def where | ||
49 | - where = component.dup | ||
50 | - where << "##{action}" if action.present? | ||
51 | - where | ||
52 | - end | ||
53 | - | ||
54 | - def message | ||
55 | - super || klass | ||
56 | - end | 22 | + delegate :app, |
23 | + :resolved?, | ||
24 | + :to => :problem | ||
25 | + | ||
57 | 26 | ||
58 | end | 27 | end |
59 | 28 |
@@ -0,0 +1,50 @@ | @@ -0,0 +1,50 @@ | ||
1 | +require 'digest/md5' | ||
2 | + | ||
3 | +class ErrorReport | ||
4 | + attr_reader :klass, :message, :backtrace, :request, :server_environment, :api_key, :notifier | ||
5 | + | ||
6 | + def initialize(xml_or_attributes) | ||
7 | + @attributes = (xml_or_attributes.is_a?(String) ? Hoptoad.parse_xml!(xml_or_attributes) : xml_or_attributes).with_indifferent_access | ||
8 | + @attributes.each{|k, v| instance_variable_set(:"@#{k}", v) } | ||
9 | + end | ||
10 | + | ||
11 | + def fingerprint | ||
12 | + @fingerprint ||= Digest::MD5.hexdigest(backtrace[0].to_s) | ||
13 | + end | ||
14 | + | ||
15 | + def rails_env | ||
16 | + server_environment['environment-name'] || 'development' | ||
17 | + end | ||
18 | + | ||
19 | + def component | ||
20 | + request['component'] || 'unknown' | ||
21 | + end | ||
22 | + | ||
23 | + def action | ||
24 | + request['action'] | ||
25 | + end | ||
26 | + | ||
27 | + def app | ||
28 | + @app ||= App.find_by_api_key!(api_key) | ||
29 | + end | ||
30 | + | ||
31 | + def generate_notice! | ||
32 | + notice = Notice.new( | ||
33 | + :message => message, | ||
34 | + :backtrace => backtrace, | ||
35 | + :request => request, | ||
36 | + :server_environment => server_environment, | ||
37 | + :notifier => notifier) | ||
38 | + | ||
39 | + err = app.find_or_create_err!( | ||
40 | + :klass => klass, | ||
41 | + :component => component, | ||
42 | + :action => action, | ||
43 | + :environment => rails_env, | ||
44 | + :fingerprint => fingerprint) | ||
45 | + | ||
46 | + err.notices << notice | ||
47 | + notice | ||
48 | + end | ||
49 | +end | ||
50 | + |
app/models/issue_tracker.rb
@@ -20,8 +20,8 @@ class IssueTracker | @@ -20,8 +20,8 @@ class IssueTracker | ||
20 | # Subclasses are responsible for overwriting this method. | 20 | # Subclasses are responsible for overwriting this method. |
21 | def check_params; true; end | 21 | def check_params; true; end |
22 | 22 | ||
23 | - def issue_title err | ||
24 | - "[#{ err.environment }][#{ err.where }] #{err.message.to_s.truncate(100)}" | 23 | + def issue_title(problem) |
24 | + "[#{ problem.environment }][#{ problem.where }] #{problem.message.to_s.truncate(100)}" | ||
25 | end | 25 | end |
26 | 26 | ||
27 | # Allows us to set the issue tracker class from a single form. | 27 | # Allows us to set the issue tracker class from a single form. |
app/models/issue_trackers/fogbugz_tracker.rb
@@ -22,19 +22,19 @@ class IssueTrackers::FogbugzTracker < IssueTracker | @@ -22,19 +22,19 @@ class IssueTrackers::FogbugzTracker < IssueTracker | ||
22 | end | 22 | end |
23 | end | 23 | end |
24 | 24 | ||
25 | - def create_issue(err) | 25 | + def create_issue(problem) |
26 | fogbugz = Fogbugz::Interface.new(:email => username, :password => password, :uri => "https://#{account}.fogbugz.com") | 26 | fogbugz = Fogbugz::Interface.new(:email => username, :password => password, :uri => "https://#{account}.fogbugz.com") |
27 | fogbugz.authenticate | 27 | fogbugz.authenticate |
28 | 28 | ||
29 | issue = {} | 29 | issue = {} |
30 | - issue['sTitle'] = issue_title err | 30 | + issue['sTitle'] = issue_title problem |
31 | issue['sArea'] = project_id | 31 | issue['sArea'] = project_id |
32 | issue['sEvent'] = body_template.result(binding) | 32 | issue['sEvent'] = body_template.result(binding) |
33 | issue['sTags'] = ['errbit'].join(',') | 33 | issue['sTags'] = ['errbit'].join(',') |
34 | issue['cols'] = ['ixBug'].join(',') | 34 | issue['cols'] = ['ixBug'].join(',') |
35 | 35 | ||
36 | fb_resp = fogbugz.command(:new, issue) | 36 | fb_resp = fogbugz.command(:new, issue) |
37 | - err.update_attribute :issue_link, "https://#{account}.fogbugz.com/default.asp?#{fb_resp['case']['ixBug']}" | 37 | + problem.update_attribute :issue_link, "https://#{account}.fogbugz.com/default.asp?#{fb_resp['case']['ixBug']}" |
38 | end | 38 | end |
39 | 39 | ||
40 | def body_template | 40 | def body_template |
app/models/issue_trackers/lighthouse_tracker.rb
@@ -18,20 +18,20 @@ class IssueTrackers::LighthouseTracker < IssueTracker | @@ -18,20 +18,20 @@ class IssueTrackers::LighthouseTracker < IssueTracker | ||
18 | end | 18 | end |
19 | end | 19 | end |
20 | 20 | ||
21 | - def create_issue(err) | 21 | + def create_issue(problem) |
22 | Lighthouse.account = account | 22 | Lighthouse.account = account |
23 | Lighthouse.token = api_token | 23 | Lighthouse.token = api_token |
24 | # updating lighthouse account | 24 | # updating lighthouse account |
25 | Lighthouse::Ticket.site | 25 | Lighthouse::Ticket.site |
26 | 26 | ||
27 | ticket = Lighthouse::Ticket.new(:project_id => project_id) | 27 | ticket = Lighthouse::Ticket.new(:project_id => project_id) |
28 | - ticket.title = issue_title err | 28 | + ticket.title = issue_title problem |
29 | 29 | ||
30 | ticket.body = body_template.result(binding) | 30 | ticket.body = body_template.result(binding) |
31 | 31 | ||
32 | ticket.tags << "errbit" | 32 | ticket.tags << "errbit" |
33 | ticket.save! | 33 | ticket.save! |
34 | - err.update_attribute :issue_link, "#{Lighthouse::Ticket.site.to_s.sub(/#{Lighthouse::Ticket.site.path}$/, '')}#{Lighthouse::Ticket.element_path(ticket.id, :project_id => project_id)}".sub(/\.xml$/, '') | 34 | + problem.update_attribute :issue_link, "#{Lighthouse::Ticket.site.to_s.sub(/#{Lighthouse::Ticket.site.path}$/, '')}#{Lighthouse::Ticket.element_path(ticket.id, :project_id => project_id)}".sub(/\.xml$/, '') |
35 | end | 35 | end |
36 | 36 | ||
37 | def body_template | 37 | def body_template |
app/models/issue_trackers/mingle_tracker.rb
@@ -27,21 +27,21 @@ class IssueTrackers::MingleTracker < IssueTracker | @@ -27,21 +27,21 @@ class IssueTrackers::MingleTracker < IssueTracker | ||
27 | end | 27 | end |
28 | end | 28 | end |
29 | 29 | ||
30 | - def create_issue(err) | 30 | + def create_issue(problem) |
31 | properties = ticket_properties_hash | 31 | properties = ticket_properties_hash |
32 | basic_auth = account.gsub(/https?:\/\//, "https://#{username}:#{password}@") | 32 | basic_auth = account.gsub(/https?:\/\//, "https://#{username}:#{password}@") |
33 | Mingle.set_site "#{basic_auth}/api/v1/projects/#{project_id}/" | 33 | Mingle.set_site "#{basic_auth}/api/v1/projects/#{project_id}/" |
34 | 34 | ||
35 | card = Mingle::Card.new | 35 | card = Mingle::Card.new |
36 | card.card_type_name = properties.delete("card_type") | 36 | card.card_type_name = properties.delete("card_type") |
37 | - card.name = issue_title(err) | 37 | + card.name = issue_title(problem) |
38 | card.description = body_template.result(binding) | 38 | card.description = body_template.result(binding) |
39 | properties.each do |property, value| | 39 | properties.each do |property, value| |
40 | card.send("cp_#{property}=", value) | 40 | card.send("cp_#{property}=", value) |
41 | end | 41 | end |
42 | 42 | ||
43 | card.save! | 43 | card.save! |
44 | - err.update_attribute :issue_link, URI.parse("#{account}/projects/#{project_id}/cards/#{card.id}").to_s | 44 | + problem.update_attribute :issue_link, URI.parse("#{account}/projects/#{project_id}/cards/#{card.id}").to_s |
45 | end | 45 | end |
46 | 46 | ||
47 | def body_template | 47 | def body_template |
app/models/issue_trackers/pivotal_labs_tracker.rb
@@ -13,12 +13,12 @@ class IssueTrackers::PivotalLabsTracker < IssueTracker | @@ -13,12 +13,12 @@ class IssueTrackers::PivotalLabsTracker < IssueTracker | ||
13 | end | 13 | end |
14 | end | 14 | end |
15 | 15 | ||
16 | - def create_issue(err) | 16 | + def create_issue(problem) |
17 | PivotalTracker::Client.token = api_token | 17 | PivotalTracker::Client.token = api_token |
18 | PivotalTracker::Client.use_ssl = true | 18 | PivotalTracker::Client.use_ssl = true |
19 | project = PivotalTracker::Project.find project_id.to_i | 19 | project = PivotalTracker::Project.find project_id.to_i |
20 | - story = project.stories.create :name => issue_title(err), :story_type => 'bug', :description => body_template.result(binding) | ||
21 | - err.update_attribute :issue_link, "https://www.pivotaltracker.com/story/show/#{story.id}" | 20 | + story = project.stories.create :name => issue_title(problem), :story_type => 'bug', :description => body_template.result(binding) |
21 | + problem.update_attribute :issue_link, "https://www.pivotaltracker.com/story/show/#{story.id}" | ||
22 | end | 22 | end |
23 | 23 | ||
24 | def body_template | 24 | def body_template |
app/models/issue_trackers/redmine_tracker.rb
@@ -25,7 +25,7 @@ class IssueTrackers::RedmineTracker < IssueTracker | @@ -25,7 +25,7 @@ class IssueTrackers::RedmineTracker < IssueTracker | ||
25 | end | 25 | end |
26 | end | 26 | end |
27 | 27 | ||
28 | - def create_issue(err) | 28 | + def create_issue(problem) |
29 | token = api_token | 29 | token = api_token |
30 | acc = account | 30 | acc = account |
31 | RedmineClient::Base.configure do | 31 | RedmineClient::Base.configure do |
@@ -33,10 +33,10 @@ class IssueTrackers::RedmineTracker < IssueTracker | @@ -33,10 +33,10 @@ class IssueTrackers::RedmineTracker < IssueTracker | ||
33 | self.site = acc | 33 | self.site = acc |
34 | end | 34 | end |
35 | issue = RedmineClient::Issue.new(:project_id => project_id) | 35 | issue = RedmineClient::Issue.new(:project_id => project_id) |
36 | - issue.subject = issue_title err | 36 | + issue.subject = issue_title problem |
37 | issue.description = body_template.result(binding) | 37 | issue.description = body_template.result(binding) |
38 | issue.save! | 38 | issue.save! |
39 | - err.update_attribute :issue_link, "#{RedmineClient::Issue.site.to_s.sub(/#{RedmineClient::Issue.site.path}$/, '')}#{RedmineClient::Issue.element_path(issue.id, :project_id => project_id)}".sub(/\.xml\?project_id=#{project_id}$/, "\?project_id=#{project_id}") | 39 | + problem.update_attribute :issue_link, "#{RedmineClient::Issue.site.to_s.sub(/#{RedmineClient::Issue.site.path}$/, '')}#{RedmineClient::Issue.element_path(issue.id, :project_id => project_id)}".sub(/\.xml\?project_id=#{project_id}$/, "\?project_id=#{project_id}") |
40 | end | 40 | end |
41 | 41 | ||
42 | def url_to_file(file_path, line_number = nil) | 42 | def url_to_file(file_path, line_number = nil) |
app/models/notice.rb
@@ -10,53 +10,47 @@ class Notice | @@ -10,53 +10,47 @@ class Notice | ||
10 | field :server_environment, :type => Hash | 10 | field :server_environment, :type => Hash |
11 | field :request, :type => Hash | 11 | field :request, :type => Hash |
12 | field :notifier, :type => Hash | 12 | field :notifier, :type => Hash |
13 | + field :klass | ||
13 | 14 | ||
14 | belongs_to :err | 15 | belongs_to :err |
15 | index :err_id | 16 | index :err_id |
17 | + index :created_at | ||
16 | 18 | ||
17 | - after_create :cache_last_notice_at | 19 | + after_create :increase_counter_cache, :cache_attributes_on_problem, :unresolve_problem |
18 | after_create :deliver_notification, :if => :should_notify? | 20 | after_create :deliver_notification, :if => :should_notify? |
19 | - before_create :increase_counter_cache, :cache_message | ||
20 | before_save :sanitize | 21 | before_save :sanitize |
21 | before_destroy :decrease_counter_cache | 22 | before_destroy :decrease_counter_cache |
22 | 23 | ||
23 | validates_presence_of :backtrace, :server_environment, :notifier | 24 | validates_presence_of :backtrace, :server_environment, :notifier |
24 | 25 | ||
25 | scope :ordered, order_by(:created_at.asc) | 26 | scope :ordered, order_by(:created_at.asc) |
26 | - index :created_at | 27 | + scope :for_errs, lambda {|errs| where(:err_id.in => errs.all.map(&:id))} |
27 | 28 | ||
28 | - def self.from_xml(hoptoad_xml) | ||
29 | - hoptoad_notice = Hoptoad::V2.parse_xml(hoptoad_xml) | ||
30 | - app = App.find_by_api_key!(hoptoad_notice['api-key']) | ||
31 | - | ||
32 | - hoptoad_notice['request'] ||= {} | ||
33 | - hoptoad_notice['request']['component'] = 'unknown' if hoptoad_notice['request']['component'].blank? | ||
34 | - hoptoad_notice['request']['action'] = nil if hoptoad_notice['request']['action'].blank? | ||
35 | - | ||
36 | - err = Err.for({ | ||
37 | - :app => app, | ||
38 | - :klass => hoptoad_notice['error']['class'], | ||
39 | - :component => hoptoad_notice['request']['component'], | ||
40 | - :action => hoptoad_notice['request']['action'], | ||
41 | - :environment => hoptoad_notice['server-environment']['environment-name'], | ||
42 | - :fingerprint => hoptoad_notice['fingerprint'] | ||
43 | - }) | ||
44 | - err.update_attributes(:resolved => false) if err.resolved? | ||
45 | - | ||
46 | - err.notices.create!({ | ||
47 | - :message => hoptoad_notice['error']['message'], | ||
48 | - :backtrace => [hoptoad_notice['error']['backtrace']['line']].flatten, | ||
49 | - :server_environment => hoptoad_notice['server-environment'], | ||
50 | - :request => hoptoad_notice['request'], | ||
51 | - :notifier => hoptoad_notice['notifier'] | ||
52 | - }) | ||
53 | - end | 29 | + delegate :app, :problem, :to => :err |
54 | 30 | ||
55 | def user_agent | 31 | def user_agent |
56 | agent_string = env_vars['HTTP_USER_AGENT'] | 32 | agent_string = env_vars['HTTP_USER_AGENT'] |
57 | agent_string.blank? ? nil : UserAgent.parse(agent_string) | 33 | agent_string.blank? ? nil : UserAgent.parse(agent_string) |
58 | end | 34 | end |
59 | 35 | ||
36 | + def environment_name | ||
37 | + server_environment['server-environment'] || server_environment['environment-name'] | ||
38 | + end | ||
39 | + | ||
40 | + def component | ||
41 | + request['component'] | ||
42 | + end | ||
43 | + | ||
44 | + def action | ||
45 | + request['action'] | ||
46 | + end | ||
47 | + | ||
48 | + def where | ||
49 | + where = component.to_s.dup | ||
50 | + where << "##{action}" if action.present? | ||
51 | + where | ||
52 | + end | ||
53 | + | ||
60 | def self.in_app_backtrace_line?(line) | 54 | def self.in_app_backtrace_line?(line) |
61 | !!(line['file'] =~ %r{^\[PROJECT_ROOT\]/(?!(vendor))}) | 55 | !!(line['file'] =~ %r{^\[PROJECT_ROOT\]/(?!(vendor))}) |
62 | end | 56 | end |
@@ -81,10 +75,6 @@ class Notice | @@ -81,10 +75,6 @@ class Notice | ||
81 | Mailer.err_notification(self).deliver | 75 | Mailer.err_notification(self).deliver |
82 | end | 76 | end |
83 | 77 | ||
84 | - def cache_last_notice_at | ||
85 | - err.update_attributes(:last_notice_at => created_at) | ||
86 | - end | ||
87 | - | ||
88 | # Backtrace containing only files from the app itself (ignore gems) | 78 | # Backtrace containing only files from the app itself (ignore gems) |
89 | def app_backtrace | 79 | def app_backtrace |
90 | backtrace.select { |l| l && l['file'] && l['file'].include?("[PROJECT_ROOT]") } | 80 | backtrace.select { |l| l && l['file'] && l['file'].include?("[PROJECT_ROOT]") } |
@@ -93,20 +83,24 @@ class Notice | @@ -93,20 +83,24 @@ class Notice | ||
93 | protected | 83 | protected |
94 | 84 | ||
95 | def should_notify? | 85 | def should_notify? |
96 | - err.app.notify_on_errs? && (Errbit::Config.per_app_email_at_notices && err.app.email_at_notices || Errbit::Config.email_at_notices).include?(err.notices.count) && err.app.watchers.any? | 86 | + app.notify_on_errs? && (Errbit::Config.per_app_email_at_notices && app.email_at_notices || Errbit::Config.email_at_notices).include?(problem.notices_count) && app.watchers.any? |
97 | end | 87 | end |
98 | 88 | ||
99 | - | ||
100 | def increase_counter_cache | 89 | def increase_counter_cache |
101 | - err.inc(:notices_count,1) | 90 | + problem.inc(:notices_count, 1) |
102 | end | 91 | end |
103 | 92 | ||
104 | def decrease_counter_cache | 93 | def decrease_counter_cache |
105 | - err.inc(:notices_count,-1) | 94 | + problem.inc(:notices_count, -1) |
106 | end | 95 | end |
107 | 96 | ||
108 | - def cache_message | ||
109 | - err.update_attribute(:message, message) if err.notices_count == 1 | 97 | + def unresolve_problem |
98 | + problem.update_attribute(:resolved, false) if problem.resolved? | ||
99 | + end | ||
100 | + | ||
101 | + | ||
102 | + def cache_attributes_on_problem | ||
103 | + problem.cache_notice_attributes(self) if problem.notices_count == 1 | ||
110 | end | 104 | end |
111 | 105 | ||
112 | def sanitize | 106 | def sanitize |
@@ -129,5 +123,6 @@ class Notice | @@ -129,5 +123,6 @@ class Notice | ||
129 | end | 123 | end |
130 | end | 124 | end |
131 | end | 125 | end |
126 | + | ||
132 | end | 127 | end |
133 | 128 |
@@ -0,0 +1,103 @@ | @@ -0,0 +1,103 @@ | ||
1 | +# Represents a single Problem. The problem may have been | ||
2 | +# reported as various Errs, but the user has grouped the | ||
3 | +# Errs together as belonging to the same problem. | ||
4 | + | ||
5 | +class Problem | ||
6 | + include Mongoid::Document | ||
7 | + include Mongoid::Timestamps | ||
8 | + | ||
9 | + field :last_notice_at, :type => DateTime | ||
10 | + field :resolved, :type => Boolean, :default => false | ||
11 | + field :issue_link, :type => String | ||
12 | + | ||
13 | + # Cached fields | ||
14 | + field :notices_count, :type => Integer, :default => 0 | ||
15 | + field :message | ||
16 | + field :environment | ||
17 | + field :klass | ||
18 | + field :where | ||
19 | + | ||
20 | + index :last_notice_at | ||
21 | + index :app_id | ||
22 | + | ||
23 | + belongs_to :app | ||
24 | + has_many :errs, :inverse_of => :problem, :dependent => :destroy | ||
25 | + has_many :comments, :inverse_of => :err, :dependent => :destroy | ||
26 | + | ||
27 | + scope :resolved, where(:resolved => true) | ||
28 | + scope :unresolved, where(:resolved => false) | ||
29 | + scope :ordered, order_by(:last_notice_at.desc) | ||
30 | + scope :for_apps, lambda {|apps| where(:app_id.in => apps.all.map(&:id))} | ||
31 | + | ||
32 | + def self.in_env(env) | ||
33 | + env.present? ? where(:environment => env) : scoped | ||
34 | + end | ||
35 | + | ||
36 | + | ||
37 | + | ||
38 | + def notices | ||
39 | + Notice.for_errs(errs).ordered | ||
40 | + end | ||
41 | + | ||
42 | + | ||
43 | + | ||
44 | + def resolve! | ||
45 | + self.update_attributes!(:resolved => true) | ||
46 | + end | ||
47 | + | ||
48 | + def unresolve! | ||
49 | + self.update_attributes!(:resolved => false) | ||
50 | + end | ||
51 | + | ||
52 | + def unresolved? | ||
53 | + !resolved? | ||
54 | + end | ||
55 | + | ||
56 | + | ||
57 | + | ||
58 | + def self.merge!(*problems) | ||
59 | + problems = problems.flatten.uniq | ||
60 | + merged_problem = problems.shift | ||
61 | + problems.each do |problem| | ||
62 | + merged_problem.errs.concat Err.where(:problem_id => problem.id) | ||
63 | + problem.errs(true) # reload problem.errs (should be empty) before problem.destroy | ||
64 | + problem.destroy | ||
65 | + end | ||
66 | + merged_problem.reset_cached_attributes | ||
67 | + merged_problem | ||
68 | + end | ||
69 | + | ||
70 | + def merged? | ||
71 | + errs.length > 1 | ||
72 | + end | ||
73 | + | ||
74 | + def unmerge! | ||
75 | + [self] + errs[1..-1].map(&:id).map do |err_id| | ||
76 | + err = Err.find(err_id) | ||
77 | + app.problems.create.tap do |new_problem| | ||
78 | + err.update_attribute(:problem_id, new_problem.id) | ||
79 | + new_problem.reset_cached_attributes | ||
80 | + end | ||
81 | + end | ||
82 | + end | ||
83 | + | ||
84 | + | ||
85 | + | ||
86 | + def reset_cached_attributes | ||
87 | + update_attribute(:notices_count, notices.count) | ||
88 | + cache_notice_attributes | ||
89 | + end | ||
90 | + | ||
91 | + def cache_notice_attributes(notice=nil) | ||
92 | + notice ||= notices.first | ||
93 | + attrs = {:last_notice_at => notices.max(:created_at)} | ||
94 | + attrs.merge!( | ||
95 | + :message => notice.message, | ||
96 | + :environment => notice.environment_name, | ||
97 | + :klass => notice.klass, | ||
98 | + :where => notice.where) if notice | ||
99 | + update_attributes!(attrs) | ||
100 | + end | ||
101 | + | ||
102 | + | ||
103 | +end |
app/models/watcher.rb
app/views/apps/index.html.haml
@@ -19,7 +19,7 @@ | @@ -19,7 +19,7 @@ | ||
19 | - else | 19 | - else |
20 | n/a | 20 | n/a |
21 | %td.count | 21 | %td.count |
22 | - - if app.errs.count > 0 | 22 | + - if app.problems.count > 0 |
23 | - unresolved = @unresolved_counts[app.id] | 23 | - unresolved = @unresolved_counts[app.id] |
24 | = link_to unresolved, app_path(app), :class => (unresolved == 0 ? "resolved" : nil) | 24 | = link_to unresolved, app_path(app), :class => (unresolved == 0 ? "resolved" : nil) |
25 | - else | 25 | - else |
app/views/apps/show.html.haml
@@ -4,7 +4,7 @@ | @@ -4,7 +4,7 @@ | ||
4 | = javascript_include_tag 'apps.show' | 4 | = javascript_include_tag 'apps.show' |
5 | - content_for :meta do | 5 | - content_for :meta do |
6 | %strong Errs Caught: | 6 | %strong Errs Caught: |
7 | - = @app.errs.count | 7 | + = @app.problems.count |
8 | %strong Deploy Count: | 8 | %strong Deploy Count: |
9 | = @app.deploys.count | 9 | = @app.deploys.count |
10 | %strong API Key: | 10 | %strong API Key: |
@@ -82,9 +82,9 @@ | @@ -82,9 +82,9 @@ | ||
82 | - else | 82 | - else |
83 | %h3 No deploys | 83 | %h3 No deploys |
84 | 84 | ||
85 | -- if @app.errs.count > 0 | 85 | +- if @app.problems.any? |
86 | %h3.clear Errs | 86 | %h3.clear Errs |
87 | - = render 'errs/table', :errs => @errs | 87 | + = render 'errs/table', :errs => @problems |
88 | - else | 88 | - else |
89 | %h3.clear No errs have been caught yet, make sure you setup your app | 89 | %h3.clear No errs have been caught yet, make sure you setup your app |
90 | = render 'configuration_instructions', :app => @app | 90 | = render 'configuration_instructions', :app => @app |
app/views/errs/_list.atom.builder
1 | -feed.updated(@errs.first.created_at) | 1 | +feed.updated(@problems.first.created_at) |
2 | 2 | ||
3 | -for err in @errs | ||
4 | - notice = err.notices.first | 3 | +for problem in @problems |
4 | + notice = problem.notices.first | ||
5 | 5 | ||
6 | - feed.entry(err, :url => app_err_url(err.app, err)) do |entry| | ||
7 | - entry.title "[#{ err.where }] #{err.message.to_s.truncate(27)}" | 6 | + feed.entry(problem, :url => app_err_url(problem.app, problem)) do |entry| |
7 | + entry.title "[#{ problem.where }] #{problem.message.to_s.truncate(27)}" | ||
8 | entry.author do |author| | 8 | entry.author do |author| |
9 | - author.name "#{ err.app.name } [#{ err.environment }]" | 9 | + author.name "#{ problem.app.name } [#{ problem.environment }]" |
10 | end | 10 | end |
11 | if notice | 11 | if notice |
12 | entry.summary(notice_atom_summary(notice), :type => "html") | 12 | entry.summary(notice_atom_summary(notice), :type => "html") |
app/views/errs/_table.html.haml
1 | -- any_issue_links = errs.any?{|e| e.issue_link.present? } | ||
2 | -%table.errs | ||
3 | - %thead | ||
4 | - %tr | ||
5 | - %th App | ||
6 | - %th What & Where | ||
7 | - %th Latest | ||
8 | - %th Deploy | ||
9 | - %th Count | ||
10 | - - if any_issue_links | ||
11 | - %th Issue | ||
12 | - %th Resolve | ||
13 | - %tbody | ||
14 | - - errs.each do |err| | ||
15 | - %tr{:class => err.resolved? ? 'resolved' : 'unresolved'} | ||
16 | - %td.app | ||
17 | - = link_to err.app.name, app_path(err.app) | ||
18 | - - if current_page?(:controller => 'errs') | ||
19 | - %span.environment= link_to err.environment, errs_path(:environment => err.environment) | ||
20 | - - else | ||
21 | - %span.environment= link_to err.environment, app_path(err.app, :environment => err.environment) | ||
22 | - %td.message | ||
23 | - = link_to truncate(err.message, :length => 230), app_err_path(err.app, err) | ||
24 | - %em= err.where | ||
25 | - - if err.comments.any? | ||
26 | - - comment = err.comments.last | ||
27 | - %br | ||
28 | - .inline_comment | ||
29 | - %em.commenter= (Errbit::Config.user_has_username ? comment.user.username : comment.user.email).to_s << ":" | ||
30 | - %em= truncate(comment.body, :length => 100, :separator => ' ') | ||
31 | - %td.latest #{time_ago_in_words(last_notice_at err)} ago | ||
32 | - %td.deploy= err.app.last_deploy_at ? err.app.last_deploy_at.to_s(:micro) : 'n/a' | ||
33 | - %td.count= link_to err.notices_count, app_err_path(err.app, err) | ||
34 | - - if any_issue_links | ||
35 | - %td.issue_link | ||
36 | - - if err.issue_link.present? | ||
37 | - = link_to image_tag("#{err.app.issue_tracker.class::Label}_goto.png"), err.issue_link, :target => "_blank" | ||
38 | - %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? | ||
39 | - - if errs.none? | 1 | +=form_tag do |
2 | + %table.errs.selectable | ||
3 | + %thead | ||
40 | %tr | 4 | %tr |
41 | - %td{:colspan => 6} | ||
42 | - %em No errs here | ||
43 | -= will_paginate @errs, :previous_label => '« Previous', :next_label => 'Next »' | ||
44 | - | 5 | + %th |
6 | + %th App | ||
7 | + %th What & Where | ||
8 | + %th Latest | ||
9 | + %th Deploy | ||
10 | + %th Count | ||
11 | + %th Resolve | ||
12 | + %tbody | ||
13 | + - errs.each do |problem| | ||
14 | + %tr{:class => problem.resolved? ? 'resolved' : 'unresolved'} | ||
15 | + %td.select | ||
16 | + = check_box_tag "problems[]", problem.id, @selected_problems.member?(problem.id.to_s) | ||
17 | + %td.app | ||
18 | + = link_to problem.app.name, app_path(problem.app) | ||
19 | + - if current_page?(:controller => 'errs') | ||
20 | + %span.environment= link_to problem.environment, errs_path(environment: problem.environment) | ||
21 | + - else | ||
22 | + %span.environment= link_to problem.environment, app_path(problem.app, environment: problem.environment) | ||
23 | + %td.message | ||
24 | + = link_to problem.message, app_err_path(problem.app, problem) | ||
25 | + %em= problem.where | ||
26 | + %td.latest #{time_ago_in_words(last_notice_at problem)} ago | ||
27 | + %td.deploy= problem.app.last_deploy_at ? problem.app.last_deploy_at.to_s(:micro) : 'n/a' | ||
28 | + %td.count= link_to problem.notices.count, app_err_path(problem.app, problem) | ||
29 | + %td.resolve= link_to image_tag("thumbs-up.png"), resolve_app_err_path(problem.app, problem), :title => "Resolve", :method => :put, :confirm => err_confirm, :class => 'resolve' if problem.unresolved? | ||
30 | + - if errs.none? | ||
31 | + %tr | ||
32 | + %td{:colspan => (@app ? 5 : 6)} | ||
33 | + %em No errs here | ||
34 | + = will_paginate @problems, :previous_label => '« Previous', :next_label => 'Next »' | ||
35 | + .tab-bar | ||
36 | + %ul | ||
37 | + %li= submit_tag 'Merge', :id => 'merge_errs', :class => 'button', 'data-action' => merge_several_errs_path | ||
38 | + %li= submit_tag 'Unmerge', :id => 'unmerge_errs', :class => 'button', 'data-action' => unmerge_several_errs_path | ||
39 | + %li= submit_tag 'Resolve', :id => 'resolve_errs', :class => 'button', 'data-action' => resolve_several_errs_path | ||
40 | + %li= submit_tag 'Unresolve', :id => 'unresolve_errs', :class => 'button', 'data-action' => unresolve_several_errs_path | ||
41 | + %li= submit_tag 'Delete', :id => 'delete_errs', :class => 'button', 'data-action' => destroy_several_errs_path |
app/views/errs/all.html.haml
1 | - content_for :title, 'All Errs' | 1 | - content_for :title, 'All Errs' |
2 | - content_for :action_bar do | 2 | - content_for :action_bar do |
3 | = link_to 'hide resolved', errs_path, :class => 'button' | 3 | = link_to 'hide resolved', errs_path, :class => 'button' |
4 | -= render 'table', :errs => @errs | ||
5 | \ No newline at end of file | 4 | \ No newline at end of file |
5 | += render 'table', :errs => @problems | ||
6 | \ No newline at end of file | 6 | \ No newline at end of file |
app/views/errs/index.html.haml
@@ -3,4 +3,4 @@ | @@ -3,4 +3,4 @@ | ||
3 | = auto_discovery_link_tag :atom, errs_url(User.token_authentication_key => current_user.authentication_token, :format => "atom"), :title => "Errbit notices at #{root_url}" | 3 | = auto_discovery_link_tag :atom, errs_url(User.token_authentication_key => current_user.authentication_token, :format => "atom"), :title => "Errbit notices at #{root_url}" |
4 | - content_for :action_bar do | 4 | - content_for :action_bar do |
5 | = link_to 'show resolved', all_errs_path, :class => 'button' | 5 | = link_to 'show resolved', all_errs_path, :class => 'button' |
6 | -= render 'table', :errs => @errs | ||
7 | \ No newline at end of file | 6 | \ No newline at end of file |
7 | += render 'table', :errs => @problems | ||
8 | \ No newline at end of file | 8 | \ No newline at end of file |
app/views/errs/show.html.haml
1 | -- content_for :page_title, @err.message | ||
2 | -- content_for :title, @err.klass | 1 | +- content_for :page_title, @problem.message |
2 | +- content_for :title, @problem.klass | ||
3 | - content_for :meta do | 3 | - content_for :meta do |
4 | %strong App: | 4 | %strong App: |
5 | = link_to @app.name, app_path(@app) | 5 | = link_to @app.name, app_path(@app) |
6 | %strong Where: | 6 | %strong Where: |
7 | - = @err.where | 7 | + = @problem.where |
8 | %br | 8 | %br |
9 | %strong Environment: | 9 | %strong Environment: |
10 | - = @err.environment | 10 | + = @problem.environment |
11 | %strong Last Notice: | 11 | %strong Last Notice: |
12 | - = last_notice_at(@err).to_s(:micro) | 12 | + = last_notice_at(@problem).to_s(:micro) |
13 | - content_for :action_bar do | 13 | - content_for :action_bar do |
14 | - - if @err.app.issue_tracker_configured? | ||
15 | - - if @err.issue_link.blank? | ||
16 | - %span= link_to 'create issue', create_issue_app_err_path(@app, @err), :method => :post, :class => "#{@app.issue_tracker.class::Label}_create create-issue" | 14 | + - if @problem.app.issue_tracker_configured? |
15 | + - if @problem.issue_link.blank? | ||
16 | + %span= link_to 'create issue', create_issue_app_err_path(@app, @problem), :method => :post, :class => "#{@app.issue_tracker.class::Label}_create create-issue" | ||
17 | - else | 17 | - else |
18 | - %span= link_to 'go to issue', @err.issue_link, :target => "_blank", :class => "#{@app.issue_tracker.class::Label}_goto goto-issue" | ||
19 | - = link_to 'unlink issue', unlink_issue_app_err_path(@app, @err), :method => :delete, :confirm => "Unlink err issues?", :class => "unlink-issue" | ||
20 | - - if @err.unresolved? | ||
21 | - %span= link_to 'resolve', resolve_app_err_path(@app, @err), :method => :put, :confirm => err_confirm, :class => 'resolve' | 18 | + %span= link_to 'go to issue', @problem.issue_link, :class => "#{@app.issue_tracker.class::Label}_goto goto-issue" |
19 | + = link_to 'unlink issue', unlink_issue_app_err_path(@app, @problem), :method => :delete, :confirm => "Unlink err issues?", :class => "unlink-issue" | ||
20 | + - if @problem.unresolved? | ||
21 | + %span= link_to 'resolve', resolve_app_err_path(@app, @problem), :method => :put, :confirm => err_confirm, :class => 'resolve' | ||
22 | 22 | ||
23 | -- if Errbit::Config.allow_comments_with_issue_tracker || !@app.issue_tracker_configured? || @err.comments.any? | 23 | +- if Errbit::Config.allow_comments_with_issue_tracker || !@app.issue_tracker_configured? || @problem.comments.any? |
24 | - content_for :comments do | 24 | - content_for :comments do |
25 | %h3 Comments on this Err | 25 | %h3 Comments on this Err |
26 | - - @err.comments.each do |comment| | 26 | + - @problem.comments.each do |comment| |
27 | .window | 27 | .window |
28 | %table.comment | 28 | %table.comment |
29 | %tr | 29 | %tr |
30 | %th | 30 | %th |
31 | - %span= link_to '✘'.html_safe, destroy_comment_app_err_path(@app, @err) << "?comment_id=#{comment.id}", :method => :delete, :confirm => "Are sure you don't need this comment?", :class => "destroy-comment" | 31 | + %span= link_to '✘'.html_safe, destroy_comment_app_err_path(@app, @problem) << "?comment_id=#{comment.id}", :method => :delete, :confirm => "Are sure you don't need this comment?", :class => "destroy-comment" |
32 | = time_ago_in_words(comment.created_at, true) << " ago by " | 32 | = time_ago_in_words(comment.created_at, true) << " ago by " |
33 | = link_to comment.user.email, user_path(comment.user) | 33 | = link_to comment.user.email, user_path(comment.user) |
34 | %tr | 34 | %tr |
35 | %td= comment.body.gsub("\n", "<br>").html_safe | 35 | %td= comment.body.gsub("\n", "<br>").html_safe |
36 | - if Errbit::Config.allow_comments_with_issue_tracker || !@app.issue_tracker_configured? | 36 | - if Errbit::Config.allow_comments_with_issue_tracker || !@app.issue_tracker_configured? |
37 | - = form_for @comment, :url => create_comment_app_err_path(@app, @err) do |comment_form| | 37 | + = form_for @comment, :url => create_comment_app_err_path(@app, @problem) do |comment_form| |
38 | %p Add a comment | 38 | %p Add a comment |
39 | = comment_form.text_area :body, :style => "width: 420px; height: 80px;" | 39 | = comment_form.text_area :body, :style => "width: 420px; height: 80px;" |
40 | = comment_form.submit "Save Comment" | 40 | = comment_form.submit "Save Comment" |
app/views/issue_trackers/fogbugz_body.txt.erb
1 | -"See this exception on Errbit": <%= app_err_url(err.app, err) %> | ||
2 | -<% if notice = err.notices.first %> | 1 | +"See this exception on Errbit": <%= app_err_url(problem.app, problem) %> |
2 | +<% if notice = problem.notices.first %> | ||
3 | <%= notice.message %> | 3 | <%= notice.message %> |
4 | 4 | ||
5 | Summary | 5 | Summary |
6 | - Where | 6 | - Where |
7 | - <%= notice.err.where %> | 7 | + <%= notice.where %> |
8 | 8 | ||
9 | - Occured | 9 | - Occured |
10 | <%= notice.created_at.to_s(:micro) %> | 10 | <%= notice.created_at.to_s(:micro) %> |
11 | 11 | ||
12 | - Similar | 12 | - Similar |
13 | - <%= (notice.err.notices_count - 1).to_s %> | 13 | + <%= (notice.problem.notices_count - 1).to_s %> |
14 | 14 | ||
15 | Params | 15 | Params |
16 | <%= pretty_hash(notice.params) %> | 16 | <%= pretty_hash(notice.params) %> |
app/views/issue_trackers/github_issues_body.txt.erb
@@ -7,13 +7,13 @@ | @@ -7,13 +7,13 @@ | ||
7 | [<%= notice.request['url'] %>](<%= notice.request['url'] %>)" | 7 | [<%= notice.request['url'] %>](<%= notice.request['url'] %>)" |
8 | <% end %> | 8 | <% end %> |
9 | ### Where ### | 9 | ### Where ### |
10 | -<%= notice.err.where %> | 10 | +<%= notice.where %> |
11 | 11 | ||
12 | ### Occured ### | 12 | ### Occured ### |
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.problem.notices_count - 1).to_s %> |
17 | 17 | ||
18 | ## Params ## | 18 | ## Params ## |
19 | ``` | 19 | ``` |
app/views/issue_trackers/lighthouseapp_body.txt.erb
1 | -[See this exception on Errbit](<%= app_err_url err.app, err %> "See this exception on Errbit") | ||
2 | -<% if notice = err.notices.first %> | 1 | +[See this exception on Errbit](<%= app_err_url problem.app, problem %> "See this exception on Errbit") |
2 | +<% if notice = problem.notices.first %> | ||
3 | # <%= notice.message %> # | 3 | # <%= notice.message %> # |
4 | ## Summary ## | 4 | ## Summary ## |
5 | <% if notice.request['url'].present? %> | 5 | <% if notice.request['url'].present? %> |
@@ -7,13 +7,13 @@ | @@ -7,13 +7,13 @@ | ||
7 | [<%= notice.request['url'] %>](<%= notice.request['url'] %>)" | 7 | [<%= notice.request['url'] %>](<%= notice.request['url'] %>)" |
8 | <% end %> | 8 | <% end %> |
9 | ### Where ### | 9 | ### Where ### |
10 | - <%= notice.err.where %> | 10 | + <%= notice.where %> |
11 | 11 | ||
12 | ### Occured ### | 12 | ### Occured ### |
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.problem.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/issue_trackers/pivotal_body.txt.erb
1 | -See this exception on Errbit: <%= app_err_url err.app, err %> | ||
2 | -<% if notice = err.notices.first %> | 1 | +See this exception on Errbit: <%= app_err_url problem.app, problem %> |
2 | +<% if notice = problem.notices.first %> | ||
3 | <% if notice.request['url'].present? %>URL: <%= notice.request['url'] %><% end %> | 3 | <% if notice.request['url'].present? %>URL: <%= notice.request['url'] %><% end %> |
4 | - Where: <%= notice.err.where %> | 4 | + Where: <%= notice.where %> |
5 | Occurred: <%= notice.created_at.to_s :micro %> | 5 | Occurred: <%= notice.created_at.to_s :micro %> |
6 | - Similar: <%= (notice.err.notices.count - 1).to_s %> | 6 | + Similar: <%= (notice.problem.notices.count - 1).to_s %> |
7 | 7 | ||
8 | Params: | 8 | Params: |
9 | <%= pretty_hash notice.params %> | 9 | <%= pretty_hash notice.params %> |
app/views/issue_trackers/textile_body.txt.erb
1 | -<% if notice = err.notices.first %> | 1 | +<% if notice = problem.notices.first %> |
2 | h1. <%= notice.message %> | 2 | h1. <%= notice.message %> |
3 | 3 | ||
4 | -h3. "See this exception on Errbit":<%= app_err_url err.app, err %> | 4 | +h3. "See this exception on Errbit":<%= app_err_url problem.app, problem %> |
5 | 5 | ||
6 | h2. Summary | 6 | h2. Summary |
7 | <% if notice.request['url'].present? %> | 7 | <% if notice.request['url'].present? %> |
@@ -11,7 +11,7 @@ h3. URL | @@ -11,7 +11,7 @@ h3. URL | ||
11 | <% end %> | 11 | <% end %> |
12 | h3. Where | 12 | h3. Where |
13 | 13 | ||
14 | -<%= notice.err.where %> | 14 | +<%= notice.where %> |
15 | 15 | ||
16 | h3. Occurred | 16 | h3. Occurred |
17 | 17 | ||
@@ -19,7 +19,7 @@ h3. Occurred | @@ -19,7 +19,7 @@ h3. Occurred | ||
19 | 19 | ||
20 | h3. Similar | 20 | h3. Similar |
21 | 21 | ||
22 | -<%= (notice.err.notices_count - 1).to_s %> | 22 | +<%= (notice.problem.notices_count - 1).to_s %> |
23 | 23 | ||
24 | h2. Params | 24 | h2. Params |
25 | 25 |
app/views/mailer/err_notification.html.haml
@@ -9,12 +9,12 @@ | @@ -9,12 +9,12 @@ | ||
9 | An err has just occurred in | 9 | An err has just occurred in |
10 | = link_to(@app.name, app_url(@app), :class => "bold") << "," | 10 | = link_to(@app.name, app_url(@app), :class => "bold") << "," |
11 | on the | 11 | on the |
12 | - %span.bold= @notice.err.environment | 12 | + %span.bold= @notice.environment_name |
13 | environment. | 13 | environment. |
14 | %br | 14 | %br |
15 | - This err has occurred #{pluralize @notice.err.notices_count, 'time'}. | 15 | + This err has occurred #{pluralize @notice.problem.notices_count, 'time'}. |
16 | %p | 16 | %p |
17 | - = link_to("Click here to view the error on Errbit", app_err_url(@app, @notice.err), :class => "bold") << "." | 17 | + = link_to("Click here to view the error on Errbit", app_err_url(@app, @notice.problem), :class => "bold") << "." |
18 | %tr | 18 | %tr |
19 | %td.section | 19 | %td.section |
20 | %table(cellpadding="0" cellspacing="0" border="0" align="left") | 20 | %table(cellpadding="0" cellspacing="0" border="0" align="left") |
@@ -23,10 +23,10 @@ | @@ -23,10 +23,10 @@ | ||
23 | %td.content(valign="top") | 23 | %td.content(valign="top") |
24 | %div | 24 | %div |
25 | %p.heading ERROR MESSAGE: | 25 | %p.heading ERROR MESSAGE: |
26 | - %p= @notice.err.message | 26 | + %p= @notice.message |
27 | %p.heading WHERE: | 27 | %p.heading WHERE: |
28 | %p.monospace | 28 | %p.monospace |
29 | - = @notice.err.where | 29 | + = @notice.where |
30 | - if (app_backtrace = @notice.app_backtrace).any? | 30 | - if (app_backtrace = @notice.app_backtrace).any? |
31 | - app_backtrace.map {|l| "#{l['file']}:#{l['number']}" }.each do |line| | 31 | - app_backtrace.map {|l| "#{l['file']}:#{l['number']}" }.each do |line| |
32 | %p.backtrace= line | 32 | %p.backtrace= line |
app/views/mailer/err_notification.text.erb
1 | -An err has just occurred in <%= @app.name %>, on the <%= @notice.err.environment %> environment. | 1 | +An err has just occurred in <%= @notice.environment_name %>: <%= raw(@notice.message) %> |
2 | 2 | ||
3 | -This err has occurred <%= pluralize @notice.err.notices_count, 'time' %>. | 3 | +This err has occurred <%= pluralize @notice.problem.notices_count, 'time' %>. You should really look into it here: |
4 | 4 | ||
5 | -You can view it on Errbit here: <%= app_err_url(@app, @notice.err) %> | 5 | + <%= app_err_url(@app, @notice.problem) %> |
6 | 6 | ||
7 | 7 | ||
8 | ERROR MESSAGE: | 8 | ERROR MESSAGE: |
9 | 9 | ||
10 | -<%= raw(@notice.err.message) %> | 10 | +<%= raw(@notice.message) %> |
11 | 11 | ||
12 | 12 | ||
13 | WHERE: | 13 | WHERE: |
14 | 14 | ||
15 | -<%= @notice.err.where %> | 15 | +<%= @notice.where %> |
16 | 16 | ||
17 | <% @notice.app_backtrace.map {|l| "#{l['file']}:#{l['number']}" }.each do |line| %> | 17 | <% @notice.app_backtrace.map {|l| "#{l['file']}:#{l['number']}" }.each do |line| %> |
18 | <%= line %> | 18 | <%= line %> |
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.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.problem.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/_summary.html.haml
@@ -9,19 +9,20 @@ | @@ -9,19 +9,20 @@ | ||
9 | %td.nowrap= link_to notice.request['url'], notice.request['url'] | 9 | %td.nowrap= link_to notice.request['url'], notice.request['url'] |
10 | %tr | 10 | %tr |
11 | %th Where | 11 | %th Where |
12 | - %td= notice.err.where | 12 | + %td= notice.where |
13 | %tr | 13 | %tr |
14 | %th Occurred | 14 | %th Occurred |
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 | 18 | + %td= notice.problem.notices.count - 1 |
19 | %tr | 19 | %tr |
20 | %th Browser | 20 | %th Browser |
21 | - %td= user_agent_graph(notice.err) | 21 | + %td= user_agent_graph(notice.problem) |
22 | %tr | 22 | %tr |
23 | %th App Server | 23 | %th App Server |
24 | %td= notice.server_environment && notice.server_environment["hostname"] | 24 | %td= notice.server_environment && notice.server_environment["hostname"] |
25 | %tr | 25 | %tr |
26 | %th Rel. Directory | 26 | %th Rel. Directory |
27 | %td= notice.server_environment && notice.server_environment["project-root"] | 27 | %td= notice.server_environment && notice.server_environment["project-root"] |
28 | + |
autotest/discover.rb
config/application.rb
@@ -39,7 +39,7 @@ module Errbit | @@ -39,7 +39,7 @@ module Errbit | ||
39 | # config.i18n.default_locale = :de | 39 | # config.i18n.default_locale = :de |
40 | 40 | ||
41 | # JavaScript files you want as :defaults (application.js is always included). | 41 | # JavaScript files you want as :defaults (application.js is always included). |
42 | - config.action_view.javascript_expansions[:defaults] = %w(jquery rails form) | 42 | + config.action_view.javascript_expansions[:defaults] = %w(jquery underscore-1.1.6 rails form) |
43 | 43 | ||
44 | # > rails generate - config | 44 | # > rails generate - config |
45 | config.generators do |g| | 45 | config.generators do |g| |
config/boot.rb
config/environment.rb
@@ -3,3 +3,4 @@ require File.expand_path('../application', __FILE__) | @@ -3,3 +3,4 @@ require File.expand_path('../application', __FILE__) | ||
3 | 3 | ||
4 | # Initialize the rails application | 4 | # Initialize the rails application |
5 | Errbit::Application.initialize! | 5 | Errbit::Application.initialize! |
6 | + |
config/environments/production.rb
@@ -50,3 +50,4 @@ Errbit::Application.configure do | @@ -50,3 +50,4 @@ Errbit::Application.configure do | ||
50 | # Send deprecation notices to registered listeners | 50 | # Send deprecation notices to registered listeners |
51 | config.active_support.deprecation = :notify | 51 | config.active_support.deprecation = :notify |
52 | end | 52 | end |
53 | + |
config/environments/test.rb
@@ -34,3 +34,4 @@ Errbit::Application.configure do | @@ -34,3 +34,4 @@ Errbit::Application.configure do | ||
34 | # Print deprecation notices to the stderr | 34 | # Print deprecation notices to the stderr |
35 | config.active_support.deprecation = :stderr | 35 | config.active_support.deprecation = :stderr |
36 | end | 36 | end |
37 | + |
config/initializers/backtrace_silencers.rb
@@ -5,3 +5,4 @@ | @@ -5,3 +5,4 @@ | ||
5 | 5 | ||
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. |
7 | # Rails.backtrace_cleaner.remove_silencers! | 7 | # Rails.backtrace_cleaner.remove_silencers! |
8 | + |
config/initializers/inflections.rb
config/initializers/inherited_resources.rb
config/initializers/mime_types.rb
@@ -3,3 +3,4 @@ | @@ -3,3 +3,4 @@ | ||
3 | # Add new mime types for use in respond_to blocks: | 3 | # Add new mime types for use in respond_to blocks: |
4 | # Mime::Type.register "text/richtext", :rtf | 4 | # Mime::Type.register "text/richtext", :rtf |
5 | # Mime::Type.register_alias "text/html", :iphone | 5 | # Mime::Type.register_alias "text/html", :iphone |
6 | + |
config/initializers/mongo.rb
config/initializers/session_store.rb
@@ -6,3 +6,4 @@ Errbit::Application.config.session_store :cookie_store, :key => '_errbit_session | @@ -6,3 +6,4 @@ Errbit::Application.config.session_store :cookie_store, :key => '_errbit_session | ||
6 | # which shouldn't be used to store highly confidential information | 6 | # which shouldn't be used to store highly confidential information |
7 | # (create the session table with "rake db:sessions:create") | 7 | # (create the session table with "rake db:sessions:create") |
8 | # Errbit::Application.config.session_store :active_record_store | 8 | # Errbit::Application.config.session_store :active_record_store |
9 | + |
config/initializers/time_formats.rb
config/initializers/xml_backend.rb
config/routes.rb
@@ -6,11 +6,16 @@ Errbit::Application.routes.draw do | @@ -6,11 +6,16 @@ Errbit::Application.routes.draw do | ||
6 | match '/notifier_api/v2/notices' => 'notices#create' | 6 | match '/notifier_api/v2/notices' => 'notices#create' |
7 | match '/deploys.txt' => 'deploys#create' | 7 | match '/deploys.txt' => 'deploys#create' |
8 | 8 | ||
9 | - resources :notices, :only => [:show] | ||
10 | - resources :deploys, :only => [:show] | 9 | + resources :notices, :only => [:show] |
10 | + resources :deploys, :only => [:show] | ||
11 | resources :users | 11 | resources :users |
12 | - resources :errs, :only => [:index] do | 12 | + resources :errs, :only => [:index] do |
13 | collection do | 13 | collection do |
14 | + post :destroy_several | ||
15 | + post :resolve_several | ||
16 | + post :unresolve_several | ||
17 | + post :merge_several | ||
18 | + post :unmerge_several | ||
14 | get :all | 19 | get :all |
15 | end | 20 | end |
16 | end | 21 | end |
@@ -20,6 +25,7 @@ Errbit::Application.routes.draw do | @@ -20,6 +25,7 @@ Errbit::Application.routes.draw do | ||
20 | resources :notices | 25 | resources :notices |
21 | member do | 26 | member do |
22 | put :resolve | 27 | put :resolve |
28 | + put :unresolve | ||
23 | post :create_issue | 29 | post :create_issue |
24 | delete :unlink_issue | 30 | delete :unlink_issue |
25 | post :create_comment | 31 | post :create_comment |
db/migrate/20110422152027_move_notices_to_separate_collection.rb
@@ -17,10 +17,10 @@ class MoveNoticesToSeparateCollection < Mongoid::Migration | @@ -17,10 +17,10 @@ class MoveNoticesToSeparateCollection < Mongoid::Migration | ||
17 | mongo_db.collection("errs").update({ "_id" => err['_id']}, { "$unset" => { "notices" => 1}}) | 17 | mongo_db.collection("errs").update({ "_id" => err['_id']}, { "$unset" => { "notices" => 1}}) |
18 | end | 18 | end |
19 | Rake::Task["errbit:db:update_notices_count"].invoke | 19 | Rake::Task["errbit:db:update_notices_count"].invoke |
20 | - Rake::Task["errbit:db:update_err_message"].invoke | 20 | + Rake::Task["errbit:db:update_problem_attrs"].invoke |
21 | end | 21 | end |
22 | 22 | ||
23 | def self.down | 23 | def self.down |
24 | end | 24 | end |
25 | - | ||
26 | end | 25 | end |
26 | + |
@@ -0,0 +1,27 @@ | @@ -0,0 +1,27 @@ | ||
1 | +class LinkErrsToProblems < Mongoid::Migration | ||
2 | + def self.up | ||
3 | + | ||
4 | + # Copy err.klass to notice.klass | ||
5 | + Notice.all.each do |notice| | ||
6 | + if notice.err && (klass = notice.err['klass']) | ||
7 | + notice.update_attribute(:klass, klass) | ||
8 | + end | ||
9 | + end | ||
10 | + | ||
11 | + # Create a Problem for each Err | ||
12 | + Err.all.each do |err| | ||
13 | + app_id = err['app_id'] | ||
14 | + app = app_id && App.where(:_id => app_id).first | ||
15 | + if app | ||
16 | + err.problem = app.problems.create | ||
17 | + err.save | ||
18 | + end | ||
19 | + end | ||
20 | + | ||
21 | + Rake::Task["errbit:db:update_notices_count"].invoke | ||
22 | + Rake::Task["errbit:db:update_problem_attrs"].invoke | ||
23 | + end | ||
24 | + | ||
25 | + def self.down | ||
26 | + end | ||
27 | +end |
@@ -0,0 +1,61 @@ | @@ -0,0 +1,61 @@ | ||
1 | +module CoreExt | ||
2 | + module Hash | ||
3 | + | ||
4 | + | ||
5 | + | ||
6 | + def pick(*picks) | ||
7 | + picks = picks.flatten | ||
8 | + picks.inject({}) {|result, key| self.key?(key) ? result.merge(key => self[key]) : result} | ||
9 | + end | ||
10 | + | ||
11 | + | ||
12 | + | ||
13 | + def pick!(*picks) | ||
14 | + picks = picks.flatten | ||
15 | + keys.each {|key| self.delete(key) unless picks.member?(key) } | ||
16 | + end | ||
17 | + | ||
18 | + | ||
19 | + | ||
20 | + def except(*picks) | ||
21 | + result = self.dup | ||
22 | + result.except!(*picks) | ||
23 | + result | ||
24 | + end | ||
25 | + | ||
26 | + | ||
27 | + | ||
28 | + def except!(*picks) | ||
29 | + picks = picks.flatten | ||
30 | + keys.each {|key| self.delete(key) if picks.member?(key) } | ||
31 | + end | ||
32 | + | ||
33 | + | ||
34 | + | ||
35 | + def inspect!(depth=0) | ||
36 | + s = "" | ||
37 | + self.each do |k,v| | ||
38 | + s << (" " * depth) | ||
39 | + s << k | ||
40 | + s << ": " | ||
41 | + if v.is_a?(Hash) | ||
42 | + s << "{\n" | ||
43 | + s << v.inspect!(depth + 2) | ||
44 | + s << (" " * depth) | ||
45 | + s << "}" | ||
46 | + elsif v.is_a?(Array) | ||
47 | + s << v.inspect | ||
48 | + else | ||
49 | + s << v.to_s | ||
50 | + end | ||
51 | + s << "\n" | ||
52 | + end | ||
53 | + s | ||
54 | + end | ||
55 | + | ||
56 | + | ||
57 | + | ||
58 | + end | ||
59 | +end | ||
60 | + | ||
61 | +Hash.send :include, CoreExt::Hash |
lib/hoptoad.rb
1 | -module Hoptoad | ||
2 | - module V2 | ||
3 | - require 'digest/md5' | 1 | +require 'hoptoad/v2' |
4 | 2 | ||
5 | - class ApiVersionError < StandardError | ||
6 | - def initialize(version) | ||
7 | - super "Wrong API Version: Expecting v2.0, got version: #{version}" | ||
8 | - end | ||
9 | - end | ||
10 | - | ||
11 | - def self.parse_xml(xml) | ||
12 | - xml = xml.unpack('C*').pack('U*') # Repack string into Unicode to fix invalid UTF-8 chars | ||
13 | - parsed = ActiveSupport::XmlMini.backend.parse(xml)['notice'] | ||
14 | - raise ApiVersionError.new(parsed['version']) unless parsed && parsed['version'].to_s == '2.0' | ||
15 | - rekeyed = rekey(parsed) | ||
16 | - rekeyed['fingerprint'] = Digest::MD5.hexdigest(rekeyed['error']['backtrace'].to_s) | ||
17 | - rekeyed | 3 | +module Hoptoad |
4 | + class ApiVersionError < StandardError | ||
5 | + def initialize | ||
6 | + super "Wrong API Version: Expecting v2.0" | ||
18 | end | 7 | end |
8 | + end | ||
19 | 9 | ||
20 | - private | 10 | + def self.parse_xml!(xml) |
11 | + xml = xml.unpack('C*').pack('U*') # Repack string into Unicode to fix invalid UTF-8 chars | ||
12 | + parsed = ActiveSupport::XmlMini.backend.parse(xml)['notice'] || raise(ApiVersionError) | ||
13 | + processor = get_version_processor(parsed['version']) | ||
14 | + processor.process_notice(parsed) | ||
15 | + end | ||
21 | 16 | ||
22 | - def self.rekey(node) | ||
23 | - if node.is_a?(Hash) && node.has_key?('var') && node.has_key?('key') | ||
24 | - {node['key'] => rekey(node['var'])} | ||
25 | - elsif node.is_a?(Hash) && node.has_key?('var') | ||
26 | - rekey(node['var']) | ||
27 | - elsif node.is_a?(Hash) && node.has_key?('__content__') && node.has_key?('key') | ||
28 | - {node['key'] => node['__content__']} | ||
29 | - elsif node.is_a?(Hash) && node.has_key?('__content__') | ||
30 | - node['__content__'] | ||
31 | - elsif node.is_a?(Hash) | ||
32 | - node.inject({}) {|rekeyed, (key,val)| | ||
33 | - rekeyed.merge(key => rekey(val)) | ||
34 | - } | ||
35 | - elsif node.is_a?(Array) && node.first.has_key?('key') | ||
36 | - node.inject({}) {|rekeyed,keypair| | ||
37 | - rekeyed.merge(rekey(keypair)) | ||
38 | - } | ||
39 | - elsif node.is_a?(Array) | ||
40 | - node.map {|n| rekey(n)} | ||
41 | - else | ||
42 | - node | ||
43 | - end | 17 | + private |
18 | + def self.get_version_processor(version) | ||
19 | + case version | ||
20 | + when '2.0'; Hoptoad::V2 | ||
21 | + else; raise ApiVersionError | ||
44 | end | 22 | end |
45 | - end | 23 | + end |
46 | end | 24 | end |
47 | 25 |
@@ -0,0 +1,66 @@ | @@ -0,0 +1,66 @@ | ||
1 | +module Hoptoad | ||
2 | + module V2 | ||
3 | + def self.process_notice(parsed) | ||
4 | + for_errbit_api( | ||
5 | + normalize( | ||
6 | + rekey(parsed))) | ||
7 | + end | ||
8 | + | ||
9 | + private | ||
10 | + def self.rekey(node) | ||
11 | + case node | ||
12 | + when Hash | ||
13 | + if node.has_key?('var') && node.has_key?('key') | ||
14 | + {normalize_key(node['key']) => rekey(node['var'])} | ||
15 | + elsif node.has_key?('var') | ||
16 | + rekey(node['var']) | ||
17 | + elsif node.has_key?('__content__') && node.has_key?('key') | ||
18 | + {normalize_key(node['key']) => rekey(node['__content__'])} | ||
19 | + elsif node.has_key?('__content__') | ||
20 | + rekey(node['__content__']) | ||
21 | + else | ||
22 | + node.inject({}) {|rekeyed, (key, val)| rekeyed.merge(normalize_key(key) => rekey(val))} | ||
23 | + end | ||
24 | + when Array | ||
25 | + if node.first.has_key?('key') | ||
26 | + node.inject({}) {|rekeyed, keypair| rekeyed.merge(rekey(keypair))} | ||
27 | + else | ||
28 | + node.map {|n| rekey(n)} | ||
29 | + end | ||
30 | + else | ||
31 | + node | ||
32 | + end | ||
33 | + end | ||
34 | + | ||
35 | + def self.normalize_key(key) | ||
36 | + key.gsub('.', '_') | ||
37 | + end | ||
38 | + | ||
39 | + def self.normalize(notice) | ||
40 | + error = notice['error'] | ||
41 | + backtrace = error['backtrace'] | ||
42 | + backtrace['line'] = [backtrace['line']] unless backtrace['line'].is_a?(Array) | ||
43 | + | ||
44 | + notice['request'] ||= {} | ||
45 | + notice['request']['component'] = 'unknown' if notice['request']['component'].blank? | ||
46 | + notice['request']['action'] = nil if notice['request']['action'].blank? | ||
47 | + | ||
48 | + notice | ||
49 | + end | ||
50 | + | ||
51 | + def self.for_errbit_api(notice) | ||
52 | + { | ||
53 | + :klass => notice['error']['class'], | ||
54 | + :message => notice['error']['message'], | ||
55 | + :backtrace => notice['error']['backtrace']['line'], | ||
56 | + | ||
57 | + :request => notice['request'], | ||
58 | + :server_environment => notice['server-environment'], | ||
59 | + | ||
60 | + :api_key => notice['api-key'], | ||
61 | + :notifier => notice['notifier'] | ||
62 | + } | ||
63 | + end | ||
64 | + end | ||
65 | +end | ||
66 | + |
lib/overrides/mongoid/relations/builder.rb
lib/recurse.rb
lib/tasks/errbit/database.rake
1 | namespace :errbit do | 1 | namespace :errbit do |
2 | namespace :db do | 2 | namespace :db do |
3 | - desc "Updates Err#notices_count" | ||
4 | - task :update_err_message => :environment do | ||
5 | - puts "Updating err.message" | ||
6 | - Err.all.each do |e| | ||
7 | - e.update_attributes(:message => e.notices.first.message) if e.notices.first | ||
8 | - end | 3 | + |
4 | + desc "Updates cached attributes on Problem" | ||
5 | + task :update_problem_attrs => :environment do | ||
6 | + puts "Updating problems" | ||
7 | + Problem.all.each(&:cache_notice_attributes) | ||
9 | end | 8 | end |
10 | - | ||
11 | - desc "Updates Err#notices_count" | 9 | + |
10 | + desc "Updates Problem#notices_count" | ||
12 | task :update_notices_count => :environment do | 11 | task :update_notices_count => :environment do |
13 | - puts "Updating err.notices_count" | ||
14 | - Err.all.each do |e| | ||
15 | - e.update_attributes(:notices_count => e.notices.count) | 12 | + puts "Updating problem.notices_count" |
13 | + Problem.all.each do |p| | ||
14 | + p.update_attributes(:notices_count => p.notices.count) | ||
16 | end | 15 | end |
17 | end | 16 | end |
18 | - | 17 | + |
19 | desc "Delete resolved errors from the database. (Useful for limited heroku databases)" | 18 | desc "Delete resolved errors from the database. (Useful for limited heroku databases)" |
20 | task :clear_resolved => :environment do | 19 | task :clear_resolved => :environment do |
21 | - count = Err.resolved.count | ||
22 | - Err.resolved.each {|err| err.destroy } | 20 | + count = Problem.resolved.count |
21 | + Problem.resolved.each {|problem| problem.destroy } | ||
23 | puts "=== Cleared #{count} resolved errors from the database." if count > 0 | 22 | puts "=== Cleared #{count} resolved errors from the database." if count > 0 |
24 | end | 23 | end |
25 | end | 24 | end |
26 | end | 25 | end |
27 | - |
lib/tasks/errbit/demo.rake
1 | namespace :errbit do | 1 | namespace :errbit do |
2 | - desc "Add a demo app & error to your database (for testing)" | 2 | + |
3 | + desc "Add a demo app & errors to your database (for testing)" | ||
3 | task :demo => :environment do | 4 | task :demo => :environment do |
4 | require 'factory_girl_rails' | 5 | require 'factory_girl_rails' |
5 | - Dir.glob(File.join(Rails.root,'spec/factories/*.rb')).each {|f| require f } | 6 | + |
7 | + Dir.glob(File.join(Rails.root, 'spec/factories/*.rb')).each {|f| require f } | ||
6 | app = Factory(:app, :name => "Demo App #{Time.now.strftime("%N")}") | 8 | app = Factory(:app, :name => "Demo App #{Time.now.strftime("%N")}") |
7 | - Factory(:notice, :err => Factory(:err, :app => app)) | ||
8 | - puts "=== Created demo app: '#{app.name}', with an example error." | 9 | + |
10 | + # Report a number of errors for the application | ||
11 | + app.problems.delete_all | ||
12 | + | ||
13 | + errors = [{ | ||
14 | + :klass => "ArgumentError", | ||
15 | + :message => "wrong number of arguments (3 for 0)" | ||
16 | + }, { | ||
17 | + :klass => "RuntimeError", | ||
18 | + :message => "Could not find Red October" | ||
19 | + }, { | ||
20 | + :klass => "TypeError", | ||
21 | + :message => "can't convert Symbol into Integer" | ||
22 | + }, { | ||
23 | + :klass => "ActiveRecord::RecordNotFound", | ||
24 | + :message => "could not find a record with the id 5" | ||
25 | + }, { | ||
26 | + :klass => "NameError", | ||
27 | + :message => "uninitialized constant Tag" | ||
28 | + }, { | ||
29 | + :klass => "SyntaxError", | ||
30 | + :message => "unexpected tSTRING_BEG, expecting keyword_do or '{' or '('" | ||
31 | + }] | ||
32 | + | ||
33 | + RANDOM_METHODS = ActiveSupport.methods.shuffle[1..8] | ||
34 | + | ||
35 | + def random_backtrace | ||
36 | + backtrace = [] | ||
37 | + 99.times {|t| backtrace << { | ||
38 | + 'number' => t.hash % 1000, | ||
39 | + 'file' => "/path/to/file.rb", | ||
40 | + 'method' => RANDOM_METHODS.shuffle.first | ||
41 | + }} | ||
42 | + backtrace | ||
43 | + end | ||
44 | + | ||
45 | + errors.each do |error_template| | ||
46 | + rand(34).times do | ||
47 | + | ||
48 | + error_report = error_template.reverse_merge({ | ||
49 | + :klass => "StandardError", | ||
50 | + :message => "Oops. Something went wrong!", | ||
51 | + :backtrace => random_backtrace, | ||
52 | + :request => { | ||
53 | + 'component' => 'main', | ||
54 | + 'action' => 'error' | ||
55 | + }, | ||
56 | + :server_environment => {'environment-name' => Rails.env.to_s}, | ||
57 | + :notifier => {:name => "seeds.rb"} | ||
58 | + }) | ||
59 | + | ||
60 | + app.report_error!(error_report) | ||
61 | + end | ||
62 | + end | ||
63 | + | ||
64 | + | ||
65 | + Factory(:notice, :err => Factory(:err, :problem => Factory(:problem, :app => app))) | ||
66 | + puts "=== Created demo app: '#{app.name}', with example errors." | ||
9 | end | 67 | end |
68 | + | ||
10 | end | 69 | end |
11 | - |
public/javascripts/application.js
1 | // App JS | 1 | // App JS |
2 | 2 | ||
3 | -$(function(){ | ||
4 | - activateTabbedPanels(); | ||
5 | - | ||
6 | - $('#watcher_name').live("click", function() { | ||
7 | - $(this).closest('form').find('.show').removeClass('show'); | ||
8 | - $('#app_watchers_attributes_0_user_id').addClass('show'); | ||
9 | - }); | ||
10 | - | ||
11 | - $('#watcher_email').live("click", function() { | ||
12 | - $(this).closest('form').find('.show').removeClass('show'); | ||
13 | - $('#app_watchers_attributes_0_email').addClass('show'); | ||
14 | - }); | ||
15 | - | ||
16 | - $('a.copy_config').live("click", function() { | ||
17 | - $('select.choose_other_app').show().focus(); | ||
18 | - }); | ||
19 | - $('select.choose_other_app').live("change", function() { | ||
20 | - var loc = window.location; | ||
21 | - window.location.href = loc.protocol + "//" + loc.host + loc.pathname + | ||
22 | - "?copy_attributes_from=" + $(this).val(); | ||
23 | - }); | ||
24 | -}); | ||
25 | - | ||
26 | -function activateTabbedPanels() { | ||
27 | - $('.tab-bar a').each(function(){ | ||
28 | - var tab = $(this); | 3 | +$(function() { |
4 | + | ||
5 | + function init() { | ||
6 | + | ||
7 | + activateTabbedPanels(); | ||
8 | + | ||
9 | + activateSelectableRows(); | ||
10 | + | ||
11 | + $('#watcher_name').live("click", function() { | ||
12 | + $(this).closest('form').find('.show').removeClass('show'); | ||
13 | + $('#app_watchers_attributes_0_user_id').addClass('show'); | ||
14 | + }); | ||
15 | + | ||
16 | + $('#watcher_email').live("click", function() { | ||
17 | + $(this).closest('form').find('.show').removeClass('show'); | ||
18 | + $('#app_watchers_attributes_0_email').addClass('show'); | ||
19 | + }); | ||
20 | + | ||
21 | + $('a.copy_config').live("click", function() { | ||
22 | + $('select.choose_other_app').show().focus(); | ||
23 | + }); | ||
24 | + | ||
25 | + $('select.choose_other_app').live("change", function() { | ||
26 | + var loc = window.location; | ||
27 | + window.location.href = loc.protocol + "//" + loc.host + loc.pathname + | ||
28 | + "?copy_attributes_from=" + $(this).val(); | ||
29 | + }); | ||
30 | + | ||
31 | + $('input[type=submit][data-action]').click(function() { | ||
32 | + $(this).closest('form').attr('action', $(this).attr('data-action')); | ||
33 | + }); | ||
34 | + } | ||
35 | + | ||
36 | + function activateTabbedPanels() { | ||
37 | + $('.tab-bar a').each(function(){ | ||
38 | + var tab = $(this); | ||
39 | + var panel = $('#'+tab.attr('rel')); | ||
40 | + panel.addClass('panel'); | ||
41 | + panel.find('h3').hide(); | ||
42 | + }) | ||
43 | + | ||
44 | + $('.tab-bar a').click(function(){ | ||
45 | + activateTab($(this)); | ||
46 | + return(false); | ||
47 | + }); | ||
48 | + activateTab($('.tab-bar a').first()); | ||
49 | + } | ||
50 | + | ||
51 | + function activateTab(tab) { | ||
52 | + tab = $(tab); | ||
29 | var panel = $('#'+tab.attr('rel')); | 53 | var panel = $('#'+tab.attr('rel')); |
30 | - panel.addClass('panel'); | ||
31 | - panel.find('h3').hide(); | ||
32 | - }) | ||
33 | - | ||
34 | - $('.tab-bar a').click(function(){ | ||
35 | - activateTab($(this)); | ||
36 | - return(false); | ||
37 | - }); | ||
38 | - activateTab($('.tab-bar a').first()); | ||
39 | -} | ||
40 | - | ||
41 | -function activateTab(tab) { | ||
42 | - tab = $(tab); | ||
43 | - var panel = $('#'+tab.attr('rel')); | ||
44 | - | ||
45 | - tab.closest('.tab-bar').find('a.active').removeClass('active'); | ||
46 | - tab.addClass('active'); | ||
47 | - | ||
48 | - $('.panel').hide(); | ||
49 | - panel.show(); | ||
50 | -} | ||
51 | - | 54 | + |
55 | + tab.closest('.tab-bar').find('a.active').removeClass('active'); | ||
56 | + tab.addClass('active'); | ||
57 | + | ||
58 | + $('.panel').hide(); | ||
59 | + panel.show(); | ||
60 | + } | ||
61 | + | ||
62 | + function activateSelectableRows() { | ||
63 | + $('.selectable tr').click(function(event) { | ||
64 | + if(!_.include(['A', 'INPUT', 'BUTTON', 'TEXTAREA'], event.target.nodeName)) { | ||
65 | + var checkbox = $(this).find('input[name="problems[]"]'); | ||
66 | + checkbox.attr('checked', !checkbox.is(':checked')); | ||
67 | + } | ||
68 | + }) | ||
69 | + } | ||
70 | + | ||
71 | + init(); | ||
72 | +}); |
@@ -0,0 +1,26 @@ | @@ -0,0 +1,26 @@ | ||
1 | +// Underscore.js 1.1.6 | ||
2 | +// (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. | ||
3 | +// Underscore is freely distributable under the MIT license. | ||
4 | +// Portions of Underscore are inspired or borrowed from Prototype, | ||
5 | +// Oliver Steele's Functional, and John Resig's Micro-Templating. | ||
6 | +// For all details and documentation: | ||
7 | +// http://documentcloud.github.com/underscore | ||
8 | +(function(){var p=this,C=p._,m={},i=Array.prototype,n=Object.prototype,f=i.slice,D=i.unshift,E=n.toString,l=n.hasOwnProperty,s=i.forEach,t=i.map,u=i.reduce,v=i.reduceRight,w=i.filter,x=i.every,y=i.some,o=i.indexOf,z=i.lastIndexOf;n=Array.isArray;var F=Object.keys,q=Function.prototype.bind,b=function(a){return new j(a)};typeof module!=="undefined"&&module.exports?(module.exports=b,b._=b):p._=b;b.VERSION="1.1.6";var h=b.each=b.forEach=function(a,c,d){if(a!=null)if(s&&a.forEach===s)a.forEach(c,d);else if(b.isNumber(a.length))for(var e= | ||
9 | +0,k=a.length;e<k;e++){if(c.call(d,a[e],e,a)===m)break}else for(e in a)if(l.call(a,e)&&c.call(d,a[e],e,a)===m)break};b.map=function(a,c,b){var e=[];if(a==null)return e;if(t&&a.map===t)return a.map(c,b);h(a,function(a,g,G){e[e.length]=c.call(b,a,g,G)});return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var k=d!==void 0;a==null&&(a=[]);if(u&&a.reduce===u)return e&&(c=b.bind(c,e)),k?a.reduce(c,d):a.reduce(c);h(a,function(a,b,f){!k&&b===0?(d=a,k=!0):d=c.call(e,d,a,b,f)});if(!k)throw new TypeError("Reduce of empty array with no initial value"); | ||
10 | +return d};b.reduceRight=b.foldr=function(a,c,d,e){a==null&&(a=[]);if(v&&a.reduceRight===v)return e&&(c=b.bind(c,e)),d!==void 0?a.reduceRight(c,d):a.reduceRight(c);a=(b.isArray(a)?a.slice():b.toArray(a)).reverse();return b.reduce(a,c,d,e)};b.find=b.detect=function(a,c,b){var e;A(a,function(a,g,f){if(c.call(b,a,g,f))return e=a,!0});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(w&&a.filter===w)return a.filter(c,b);h(a,function(a,g,f){c.call(b,a,g,f)&&(e[e.length]=a)});return e}; | ||
11 | +b.reject=function(a,c,b){var e=[];if(a==null)return e;h(a,function(a,g,f){c.call(b,a,g,f)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=!0;if(a==null)return e;if(x&&a.every===x)return a.every(c,b);h(a,function(a,g,f){if(!(e=e&&c.call(b,a,g,f)))return m});return e};var A=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=!1;if(a==null)return e;if(y&&a.some===y)return a.some(c,d);h(a,function(a,b,f){if(e=c.call(d,a,b,f))return m});return e};b.include=b.contains=function(a,c){var b= | ||
12 | +!1;if(a==null)return b;if(o&&a.indexOf===o)return a.indexOf(c)!=-1;A(a,function(a){if(b=a===c)return!0});return b};b.invoke=function(a,c){var d=f.call(arguments,2);return b.map(a,function(a){return(c.call?c||a:a[c]).apply(a,d)})};b.pluck=function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);var e={computed:-Infinity};h(a,function(a,b,f){b=c?c.call(d,a,b,f):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a, | ||
13 | +c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};h(a,function(a,b,f){b=c?c.call(d,a,b,f):a;b<e.computed&&(e={value:a,computed:b})});return e.value};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,f){return{value:a,criteria:c.call(d,a,b,f)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c<d?-1:c>d?1:0}),"value")};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray= | ||
14 | +function(a){if(!a)return[];if(a.toArray)return a.toArray();if(b.isArray(a))return a;if(b.isArguments(a))return f.call(a);return b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?f.call(a,0,b):a[0]};b.rest=b.tail=function(a,b,d){return f.call(a,b==null||d?1:b)};b.last=function(a){return a[a.length-1]};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a){return b.reduce(a,function(a,d){if(b.isArray(d))return a.concat(b.flatten(d)); | ||
15 | +a[a.length]=d;return a},[])};b.without=function(a){var c=f.call(arguments,1);return b.filter(a,function(a){return!b.include(c,a)})};b.uniq=b.unique=function(a,c){return b.reduce(a,function(a,e,f){if(0==f||(c===!0?b.last(a)!=e:!b.include(a,e)))a[a.length]=e;return a},[])};b.intersect=function(a){var c=f.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.zip=function(){for(var a=f.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c), | ||
16 | +e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(o&&a.indexOf===o)return a.indexOf(c);d=0;for(e=a.length;d<e;d++)if(a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(z&&a.lastIndexOf===z)return a.lastIndexOf(b);for(var d=a.length;d--;)if(a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);d=arguments[2]||1;for(var e=Math.max(Math.ceil((b-a)/ | ||
17 | +d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g};b.bind=function(a,b){if(a.bind===q&&q)return q.apply(a,f.call(arguments,1));var d=f.call(arguments,2);return function(){return a.apply(b,d.concat(f.call(arguments)))}};b.bindAll=function(a){var c=f.call(arguments,1);c.length==0&&(c=b.functions(a));h(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var b=c.apply(this,arguments);return l.call(d,b)?d[b]:d[b]=a.apply(this,arguments)}};b.delay= | ||
18 | +function(a,b){var d=f.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(f.call(arguments,1)))};var B=function(a,b,d){var e;return function(){var f=this,g=arguments,h=function(){e=null;a.apply(f,g)};d&&clearTimeout(e);if(d||!e)e=setTimeout(h,b)}};b.throttle=function(a,b){return B(a,b,!1)};b.debounce=function(a,b){return B(a,b,!0)};b.once=function(a){var b=!1,d;return function(){if(b)return d;b=!0;return d=a.apply(this,arguments)}}; | ||
19 | +b.wrap=function(a,b){return function(){var d=[a].concat(f.call(arguments));return b.apply(this,d)}};b.compose=function(){var a=f.call(arguments);return function(){for(var b=f.call(arguments),d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return function(){if(--a<1)return b.apply(this,arguments)}};b.keys=F||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var b=[],d;for(d in a)l.call(a,d)&&(b[b.length]=d);return b};b.values=function(a){return b.map(a, | ||
20 | +b.identity)};b.functions=b.methods=function(a){return b.filter(b.keys(a),function(c){return b.isFunction(a[c])}).sort()};b.extend=function(a){h(f.call(arguments,1),function(b){for(var d in b)b[d]!==void 0&&(a[d]=b[d])});return a};b.defaults=function(a){h(f.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,c){if(a===c)return!0;var d=typeof a;if(d!= | ||
21 | +typeof c)return!1;if(a==c)return!0;if(!a&&c||a&&!c)return!1;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual)return a.isEqual(c);if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(b.isNaN(a)&&b.isNaN(c))return!1;if(b.isRegExp(a)&&b.isRegExp(c))return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;if(d!=="object")return!1;if(a.length&&a.length!==c.length)return!1;d=b.keys(a);var e=b.keys(c);if(d.length!=e.length)return!1; | ||
22 | +for(var f in a)if(!(f in c)||!b.isEqual(a[f],c[f]))return!1;return!0};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(l.call(a,c))return!1;return!0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=n||function(a){return E.call(a)==="[object Array]"};b.isArguments=function(a){return!(!a||!l.call(a,"callee"))};b.isFunction=function(a){return!(!a||!a.constructor||!a.call||!a.apply)};b.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)}; | ||
23 | +b.isNumber=function(a){return!!(a===0||a&&a.toExponential&&a.toFixed)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===!0||a===!1};b.isDate=function(a){return!(!a||!a.getTimezoneOffset||!a.setUTCFullYear)};b.isRegExp=function(a){return!(!a||!a.test||!a.exec||!(a.ignoreCase||a.ignoreCase===!1))};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.noConflict=function(){p._=C;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e= | ||
24 | +0;e<a;e++)b.call(d,e)};b.mixin=function(a){h(b.functions(a),function(c){H(c,b[c]=a[c])})};var I=0;b.uniqueId=function(a){var b=I++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g};b.template=function(a,c){var d=b.templateSettings;d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.interpolate,function(a,b){return"',"+b.replace(/\\'/g,"'")+",'"}).replace(d.evaluate|| | ||
25 | +null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+"__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');";d=new Function("obj",d);return c?d(c):d};var j=function(a){this._wrapped=a};b.prototype=j.prototype;var r=function(a,c){return c?b(a).chain():a},H=function(a,c){j.prototype[a]=function(){var a=f.call(arguments);D.call(a,this._wrapped);return r(c.apply(b,a),this._chain)}};b.mixin(b);h(["pop","push","reverse","shift","sort", | ||
26 | +"splice","unshift"],function(a){var b=i[a];j.prototype[a]=function(){b.apply(this._wrapped,arguments);return r(this._wrapped,this._chain)}});h(["concat","join","slice"],function(a){var b=i[a];j.prototype[a]=function(){return r(b.apply(this._wrapped,arguments),this._chain)}});j.prototype.chain=function(){this._chain=!0;return this};j.prototype.value=function(){return this._wrapped}})(); | ||
0 | \ No newline at end of file | 27 | \ No newline at end of file |
public/stylesheets/application.css
@@ -244,7 +244,10 @@ a.action { float: right; font-size: 0.9em;} | @@ -244,7 +244,10 @@ a.action { float: right; font-size: 0.9em;} | ||
244 | } | 244 | } |
245 | 245 | ||
246 | /* Forms */ | 246 | /* Forms */ |
247 | -form { | 247 | +form#new_user, |
248 | +form.edit_user, | ||
249 | +form#new_app, | ||
250 | +form.edit_app { | ||
248 | width: 620px; | 251 | width: 620px; |
249 | } | 252 | } |
250 | form > div, form fieldset > div { margin: 1em 0;} | 253 | form > div, form fieldset > div { margin: 1em 0;} |
@@ -289,7 +292,11 @@ form input[type=submit] { | @@ -289,7 +292,11 @@ form input[type=submit] { | ||
289 | font-size: 1.2em; line-height: 1em; text-transform: uppercase; | 292 | font-size: 1.2em; line-height: 1em; text-transform: uppercase; |
290 | border: none; color: #FFF; background-color: #387fc1; | 293 | border: none; color: #FFF; background-color: #387fc1; |
291 | } | 294 | } |
292 | -form div.buttons { | 295 | +form input[type=submit].button { |
296 | + font-size: 1em; | ||
297 | + text-transform: none; | ||
298 | +} | ||
299 | +form div.buttons { | ||
293 | color: #666; | 300 | color: #666; |
294 | background: #FFF url(images/button-bg.png) 0 bottom repeat-x; | 301 | background: #FFF url(images/button-bg.png) 0 bottom repeat-x; |
295 | border-radius: 50px; | 302 | border-radius: 50px; |
@@ -477,6 +484,7 @@ pre { | @@ -477,6 +484,7 @@ pre { | ||
477 | } | 484 | } |
478 | 485 | ||
479 | /* Buttons */ | 486 | /* Buttons */ |
487 | +input[type="submit"].button, | ||
480 | a.button { | 488 | a.button { |
481 | display: inline-block; | 489 | display: inline-block; |
482 | padding: 0 0.8em; | 490 | padding: 0 0.8em; |
@@ -492,6 +500,7 @@ a.button { | @@ -492,6 +500,7 @@ a.button { | ||
492 | -webkit-box-shadow: inset 0px 0px 4px #FFF; | 500 | -webkit-box-shadow: inset 0px 0px 4px #FFF; |
493 | line-height: 30px; | 501 | line-height: 30px; |
494 | } | 502 | } |
503 | +input[type="submit"]:hover.button, | ||
495 | a:hover.button { | 504 | a:hover.button { |
496 | box-shadow: 0px 0px 4px #ccc; | 505 | box-shadow: 0px 0px 4px #ccc; |
497 | -moz-box-shadow: 0px 0px 4px #ccc; | 506 | -moz-box-shadow: 0px 0px 4px #ccc; |
@@ -508,6 +517,7 @@ a.button.active { | @@ -508,6 +517,7 @@ a.button.active { | ||
508 | -webkit-box-shadow: inset 0 0 5px #999; | 517 | -webkit-box-shadow: inset 0 0 5px #999; |
509 | } | 518 | } |
510 | 519 | ||
520 | + | ||
511 | /* Tab Bar */ | 521 | /* Tab Bar */ |
512 | .tab-bar { | 522 | .tab-bar { |
513 | margin-bottom: 24px; | 523 | margin-bottom: 24px; |
spec/controllers/apps_controller_spec.rb
@@ -6,6 +6,7 @@ describe AppsController do | @@ -6,6 +6,7 @@ describe AppsController do | ||
6 | it_requires_authentication | 6 | it_requires_authentication |
7 | it_requires_admin_privileges :for => {:new => :get, :edit => :get, :create => :post, :update => :put, :destroy => :delete} | 7 | it_requires_admin_privileges :for => {:new => :get, :edit => :get, :create => :post, :update => :put, :destroy => :delete} |
8 | 8 | ||
9 | + | ||
9 | describe "GET /apps" do | 10 | describe "GET /apps" do |
10 | context 'when logged in as an admin' do | 11 | context 'when logged in as an admin' do |
11 | it 'finds all apps' do | 12 | it 'finds all apps' do |
@@ -38,8 +39,7 @@ describe AppsController do | @@ -38,8 +39,7 @@ describe AppsController do | ||
38 | @user = Factory(:admin) | 39 | @user = Factory(:admin) |
39 | sign_in @user | 40 | sign_in @user |
40 | @app = Factory(:app) | 41 | @app = Factory(:app) |
41 | - @err = Factory :err, :app => @app | ||
42 | - @notice = Factory :notice, :err => @err | 42 | + @problem = Factory(:notice, :err => Factory(:err, :problem => Factory(:problem, :app => @app))).problem |
43 | end | 43 | end |
44 | 44 | ||
45 | it 'finds the app' do | 45 | it 'finds the app' do |
@@ -48,50 +48,51 @@ describe AppsController do | @@ -48,50 +48,51 @@ describe AppsController do | ||
48 | end | 48 | end |
49 | 49 | ||
50 | it "should not raise errors for app with err without notices" do | 50 | it "should not raise errors for app with err without notices" do |
51 | - Factory :err, :app => @app | 51 | + Factory(:err, :problem => Factory(:problem, :app => @app)) |
52 | lambda { get :show, :id => @app.id }.should_not raise_error | 52 | lambda { get :show, :id => @app.id }.should_not raise_error |
53 | end | 53 | end |
54 | 54 | ||
55 | it "should list atom feed successfully" do | 55 | it "should list atom feed successfully" do |
56 | get :show, :id => @app.id, :format => "atom" | 56 | get :show, :id => @app.id, :format => "atom" |
57 | response.should be_success | 57 | response.should be_success |
58 | - response.body.should match(@err.message) | 58 | + response.body.should match(@problem.message) |
59 | end | 59 | end |
60 | 60 | ||
61 | context "pagination" do | 61 | context "pagination" do |
62 | before(:each) do | 62 | before(:each) do |
63 | - 35.times { Factory :err, :app => @app } | 63 | + 35.times { Factory(:err, :problem => Factory(:problem, :app => @app)) } |
64 | end | 64 | end |
65 | 65 | ||
66 | it "should have default per_page value for user" do | 66 | it "should have default per_page value for user" do |
67 | get :show, :id => @app.id | 67 | get :show, :id => @app.id |
68 | - assigns(:errs).size.should == User::PER_PAGE | 68 | + assigns(:problems).size.should == User::PER_PAGE |
69 | end | 69 | end |
70 | 70 | ||
71 | it "should be able to override default per_page value" do | 71 | it "should be able to override default per_page value" do |
72 | @user.update_attribute :per_page, 10 | 72 | @user.update_attribute :per_page, 10 |
73 | get :show, :id => @app.id | 73 | get :show, :id => @app.id |
74 | - assigns(:errs).size.should == 10 | 74 | + assigns(:problems).size.should == 10 |
75 | end | 75 | end |
76 | end | 76 | end |
77 | 77 | ||
78 | context 'with resolved errors' do | 78 | context 'with resolved errors' do |
79 | before(:each) do | 79 | before(:each) do |
80 | - resolved_err = Factory.create(:err, :app => @app, :resolved => true) | ||
81 | - Factory.create(:notice, :err => resolved_err) | 80 | + resolved_problem = Factory(:problem, :app => @app) |
81 | + Factory(:notice, :err => Factory(:err, :problem => resolved_problem)) | ||
82 | + resolved_problem.resolve! | ||
82 | end | 83 | end |
83 | 84 | ||
84 | context 'and no params' do | 85 | context 'and no params' do |
85 | - it 'shows only unresolved errs' do | 86 | + it 'shows only unresolved problems' do |
86 | get :show, :id => @app.id | 87 | get :show, :id => @app.id |
87 | - assigns(:errs).size.should == 1 | 88 | + assigns(:problems).size.should == 1 |
88 | end | 89 | end |
89 | end | 90 | end |
90 | 91 | ||
91 | - context 'and all_errs=true params' do | 92 | + context 'and all_problems=true params' do |
92 | it 'shows all errors' do | 93 | it 'shows all errors' do |
93 | get :show, :id => @app.id, :all_errs => true | 94 | get :show, :id => @app.id, :all_errs => true |
94 | - assigns(:errs).size.should == 2 | 95 | + assigns(:problems).size.should == 2 |
95 | end | 96 | end |
96 | end | 97 | end |
97 | end | 98 | end |
@@ -100,42 +101,42 @@ describe AppsController do | @@ -100,42 +101,42 @@ describe AppsController do | ||
100 | before(:each) do | 101 | before(:each) do |
101 | environments = ['production', 'test', 'development', 'staging'] | 102 | environments = ['production', 'test', 'development', 'staging'] |
102 | 20.times do |i| | 103 | 20.times do |i| |
103 | - Factory.create(:err, :app => @app, :environment => environments[i % environments.length]) | 104 | + Factory.create(:problem, :app => @app, :environment => environments[i % environments.length]) |
104 | end | 105 | end |
105 | end | 106 | end |
106 | 107 | ||
107 | context 'no params' do | 108 | context 'no params' do |
108 | it 'shows errs for all environments' do | 109 | it 'shows errs for all environments' do |
109 | get :show, :id => @app.id | 110 | get :show, :id => @app.id |
110 | - assigns(:errs).size.should == 21 | 111 | + assigns(:problems).size.should == 21 |
111 | end | 112 | end |
112 | end | 113 | end |
113 | 114 | ||
114 | context 'environment production' do | 115 | context 'environment production' do |
115 | it 'shows errs for just production' do | 116 | it 'shows errs for just production' do |
116 | - get :show, :id => @app.id, :environment => :production | ||
117 | - assigns(:errs).size.should == 6 | 117 | + get :show, :id => @app.id, :environment => 'production' |
118 | + assigns(:problems).size.should == 6 | ||
118 | end | 119 | end |
119 | end | 120 | end |
120 | 121 | ||
121 | context 'environment staging' do | 122 | context 'environment staging' do |
122 | it 'shows errs for just staging' do | 123 | it 'shows errs for just staging' do |
123 | - get :show, :id => @app.id, :environment => :staging | ||
124 | - assigns(:errs).size.should == 5 | 124 | + get :show, :id => @app.id, :environment => 'staging' |
125 | + assigns(:problems).size.should == 5 | ||
125 | end | 126 | end |
126 | end | 127 | end |
127 | 128 | ||
128 | context 'environment development' do | 129 | context 'environment development' do |
129 | it 'shows errs for just development' do | 130 | it 'shows errs for just development' do |
130 | - get :show, :id => @app.id, :environment => :development | ||
131 | - assigns(:errs).size.should == 5 | 131 | + get :show, :id => @app.id, :environment => 'development' |
132 | + assigns(:problems).size.should == 5 | ||
132 | end | 133 | end |
133 | end | 134 | end |
134 | 135 | ||
135 | context 'environment test' do | 136 | context 'environment test' do |
136 | it 'shows errs for just test' do | 137 | it 'shows errs for just test' do |
137 | - get :show, :id => @app.id, :environment => :test | ||
138 | - assigns(:errs).size.should == 5 | 138 | + get :show, :id => @app.id, :environment => 'test' |
139 | + assigns(:problems).size.should == 5 | ||
139 | end | 140 | end |
140 | end | 141 | end |
141 | end | 142 | end |
@@ -345,5 +346,6 @@ describe AppsController do | @@ -345,5 +346,6 @@ describe AppsController do | ||
345 | end | 346 | end |
346 | end | 347 | end |
347 | 348 | ||
349 | + | ||
348 | end | 350 | end |
349 | 351 |
spec/controllers/errs_controller_spec.rb
@@ -8,7 +8,8 @@ describe ErrsController do | @@ -8,7 +8,8 @@ describe ErrsController do | ||
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, :problem => Factory(:problem, :app => app, :environment => "production")) } |
12 | + | ||
12 | 13 | ||
13 | describe "GET /errs" do | 14 | describe "GET /errs" do |
14 | render_views | 15 | render_views |
@@ -16,20 +17,19 @@ describe ErrsController do | @@ -16,20 +17,19 @@ describe ErrsController do | ||
16 | before(:each) do | 17 | before(:each) do |
17 | @user = Factory(:admin) | 18 | @user = Factory(:admin) |
18 | sign_in @user | 19 | sign_in @user |
19 | - @notice = Factory :notice | ||
20 | - @err = @notice.err | 20 | + @problem = Factory(:notice, :err => Factory(:err, :problem => Factory(:problem, :app => app, :environment => "production"))).problem |
21 | end | 21 | end |
22 | 22 | ||
23 | it "should successfully list errs" do | 23 | it "should successfully list errs" do |
24 | get :index | 24 | get :index |
25 | response.should be_success | 25 | response.should be_success |
26 | - response.body.should match(@err.message) | 26 | + response.body.should match(@problem.message) |
27 | end | 27 | end |
28 | 28 | ||
29 | it "should list atom feed successfully" do | 29 | it "should list atom feed successfully" do |
30 | get :index, :format => "atom" | 30 | get :index, :format => "atom" |
31 | response.should be_success | 31 | response.should be_success |
32 | - response.body.should match(@err.message) | 32 | + response.body.should match(@problem.message) |
33 | end | 33 | end |
34 | 34 | ||
35 | context "pagination" do | 35 | context "pagination" do |
@@ -39,13 +39,13 @@ describe ErrsController do | @@ -39,13 +39,13 @@ describe ErrsController do | ||
39 | 39 | ||
40 | it "should have default per_page value for user" do | 40 | it "should have default per_page value for user" do |
41 | get :index | 41 | get :index |
42 | - assigns(:errs).size.should == User::PER_PAGE | 42 | + assigns(:problems).size.should == User::PER_PAGE |
43 | end | 43 | end |
44 | 44 | ||
45 | it "should be able to override default per_page value" do | 45 | it "should be able to override default per_page value" do |
46 | @user.update_attribute :per_page, 10 | 46 | @user.update_attribute :per_page, 10 |
47 | get :index | 47 | get :index |
48 | - assigns(:errs).size.should == 10 | 48 | + assigns(:problems).size.should == 10 |
49 | end | 49 | end |
50 | end | 50 | end |
51 | 51 | ||
@@ -53,42 +53,42 @@ describe ErrsController do | @@ -53,42 +53,42 @@ describe ErrsController do | ||
53 | before(:each) do | 53 | before(:each) do |
54 | environments = ['production', 'test', 'development', 'staging'] | 54 | environments = ['production', 'test', 'development', 'staging'] |
55 | 20.times do |i| | 55 | 20.times do |i| |
56 | - Factory.create(:err, :environment => environments[i % environments.length]) | 56 | + Factory(:problem, :environment => environments[i % environments.length]) |
57 | end | 57 | end |
58 | end | 58 | end |
59 | 59 | ||
60 | context 'no params' do | 60 | context 'no params' do |
61 | it 'shows errs for all environments' do | 61 | it 'shows errs for all environments' do |
62 | get :index | 62 | get :index |
63 | - assigns(:errs).size.should == 21 | 63 | + assigns(:problems).size.should == 21 |
64 | end | 64 | end |
65 | end | 65 | end |
66 | 66 | ||
67 | context 'environment production' do | 67 | context 'environment production' do |
68 | it 'shows errs for just production' do | 68 | it 'shows errs for just production' do |
69 | - get :index, :environment => :production | ||
70 | - assigns(:errs).size.should == 6 | 69 | + get :index, :environment => 'production' |
70 | + assigns(:problems).size.should == 6 | ||
71 | end | 71 | end |
72 | end | 72 | end |
73 | 73 | ||
74 | context 'environment staging' do | 74 | context 'environment staging' do |
75 | it 'shows errs for just staging' do | 75 | it 'shows errs for just staging' do |
76 | - get :index, :environment => :staging | ||
77 | - assigns(:errs).size.should == 5 | 76 | + get :index, :environment => 'staging' |
77 | + assigns(:problems).size.should == 5 | ||
78 | end | 78 | end |
79 | end | 79 | end |
80 | 80 | ||
81 | context 'environment development' do | 81 | context 'environment development' do |
82 | it 'shows errs for just development' do | 82 | it 'shows errs for just development' do |
83 | - get :index, :environment => :development | ||
84 | - assigns(:errs).size.should == 5 | 83 | + get :index, :environment => 'development' |
84 | + assigns(:problems).size.should == 5 | ||
85 | end | 85 | end |
86 | end | 86 | end |
87 | 87 | ||
88 | context 'environment test' do | 88 | context 'environment test' do |
89 | it 'shows errs for just test' do | 89 | it 'shows errs for just test' do |
90 | - get :index, :environment => :test | ||
91 | - assigns(:errs).size.should == 5 | 90 | + get :index, :environment => 'test' |
91 | + assigns(:problems).size.should == 5 | ||
92 | end | 92 | end |
93 | end | 93 | end |
94 | end | 94 | end |
@@ -98,11 +98,11 @@ describe ErrsController do | @@ -98,11 +98,11 @@ describe ErrsController do | ||
98 | it 'gets a paginated list of unresolved errs for the users apps' do | 98 | it 'gets a paginated list of unresolved errs for the users apps' do |
99 | sign_in(user = Factory(:user)) | 99 | sign_in(user = Factory(:user)) |
100 | unwatched_err = Factory(:err) | 100 | unwatched_err = Factory(:err) |
101 | - watched_unresolved_err = Factory(:err, :app => Factory(:user_watcher, :user => user).app, :resolved => false) | ||
102 | - watched_resolved_err = Factory(:err, :app => Factory(:user_watcher, :user => user).app, :resolved => true) | 101 | + watched_unresolved_err = Factory(:err, :problem => Factory(:problem, :app => Factory(:user_watcher, :user => user).app, :resolved => false)) |
102 | + watched_resolved_err = Factory(:err, :problem => Factory(:problem, :app => Factory(:user_watcher, :user => user).app, :resolved => true)) | ||
103 | get :index | 103 | get :index |
104 | - assigns(:errs).should include(watched_unresolved_err) | ||
105 | - assigns(:errs).should_not include(unwatched_err, watched_resolved_err) | 104 | + assigns(:problems).should include(watched_unresolved_err.problem) |
105 | + assigns(:problems).should_not include(unwatched_err.problem, watched_resolved_err.problem) | ||
106 | end | 106 | end |
107 | end | 107 | end |
108 | end | 108 | end |
@@ -112,25 +112,25 @@ describe ErrsController do | @@ -112,25 +112,25 @@ describe ErrsController do | ||
112 | it "gets a paginated list of all errs" do | 112 | it "gets a paginated list of all errs" do |
113 | sign_in Factory(:admin) | 113 | sign_in Factory(:admin) |
114 | errs = WillPaginate::Collection.new(1,30) | 114 | errs = WillPaginate::Collection.new(1,30) |
115 | - 3.times { errs << Factory(:err) } | ||
116 | - 3.times { errs << Factory(:err, :resolved => true)} | ||
117 | - Err.should_receive(:ordered).and_return( | 115 | + 3.times { errs << Factory(:err).problem } |
116 | + 3.times { errs << Factory(:err, :problem => Factory(:problem, :resolved => true)).problem } | ||
117 | + Problem.should_receive(:ordered).and_return( | ||
118 | mock('proxy', :paginate => errs) | 118 | mock('proxy', :paginate => errs) |
119 | ) | 119 | ) |
120 | get :all | 120 | get :all |
121 | - assigns(:errs).should == errs | 121 | + assigns(:problems).should == errs |
122 | end | 122 | end |
123 | end | 123 | end |
124 | 124 | ||
125 | context 'when logged in as a user' do | 125 | context 'when logged in as a user' do |
126 | it 'gets a paginated list of all errs for the users apps' do | 126 | it 'gets a paginated list of all errs for the users apps' do |
127 | sign_in(user = Factory(:user)) | 127 | sign_in(user = Factory(:user)) |
128 | - unwatched_err = Factory(:err) | ||
129 | - watched_unresolved_err = Factory(:err, :app => Factory(:user_watcher, :user => user).app, :resolved => false) | ||
130 | - watched_resolved_err = Factory(:err, :app => Factory(:user_watcher, :user => user).app, :resolved => true) | 128 | + unwatched_err = Factory(:problem) |
129 | + watched_unresolved_err = Factory(:problem, :app => Factory(:user_watcher, :user => user).app, :resolved => false) | ||
130 | + watched_resolved_err = Factory(:problem, :app => Factory(:user_watcher, :user => user).app, :resolved => true) | ||
131 | get :all | 131 | get :all |
132 | - assigns(:errs).should include(watched_resolved_err, watched_unresolved_err) | ||
133 | - assigns(:errs).should_not include(unwatched_err) | 132 | + assigns(:problems).should include(watched_resolved_err, watched_unresolved_err) |
133 | + assigns(:problems).should_not include(unwatched_err) | ||
134 | end | 134 | end |
135 | end | 135 | end |
136 | end | 136 | end |
@@ -148,17 +148,17 @@ describe ErrsController do | @@ -148,17 +148,17 @@ describe ErrsController do | ||
148 | end | 148 | end |
149 | 149 | ||
150 | it "finds the app" do | 150 | it "finds the app" do |
151 | - get :show, :app_id => app.id, :id => err.id | 151 | + get :show, :app_id => app.id, :id => err.problem.id |
152 | assigns(:app).should == app | 152 | assigns(:app).should == app |
153 | end | 153 | end |
154 | 154 | ||
155 | it "finds the err" do | 155 | it "finds the err" do |
156 | - get :show, :app_id => app.id, :id => err.id | ||
157 | - assigns(:err).should == err | 156 | + get :show, :app_id => app.id, :id => err.problem.id |
157 | + assigns(:problem).should == err.problem | ||
158 | end | 158 | end |
159 | 159 | ||
160 | it "successfully render page" do | 160 | it "successfully render page" do |
161 | - get :show, :app_id => app.id, :id => err.id | 161 | + get :show, :app_id => app.id, :id => err.problem.id |
162 | response.should be_success | 162 | response.should be_success |
163 | end | 163 | end |
164 | 164 | ||
@@ -167,23 +167,23 @@ describe ErrsController do | @@ -167,23 +167,23 @@ describe ErrsController do | ||
167 | 167 | ||
168 | it "should not exist for err's app without issue tracker" do | 168 | it "should not exist for err's app without issue tracker" do |
169 | err = Factory :err | 169 | err = Factory :err |
170 | - get :show, :app_id => err.app.id, :id => err.id | 170 | + get :show, :app_id => err.app.id, :id => err.problem.id |
171 | 171 | ||
172 | response.body.should_not button_matcher | 172 | response.body.should_not button_matcher |
173 | end | 173 | end |
174 | 174 | ||
175 | it "should exist for err's app with issue tracker" do | 175 | it "should exist for err's app with issue tracker" do |
176 | tracker = Factory(:lighthouse_tracker) | 176 | tracker = Factory(:lighthouse_tracker) |
177 | - err = Factory(:err, :app => tracker.app) | ||
178 | - get :show, :app_id => err.app.id, :id => err.id | 177 | + err = Factory(:err, :problem => Factory(:problem, :app => tracker.app)) |
178 | + get :show, :app_id => err.app.id, :id => err.problem.id | ||
179 | 179 | ||
180 | response.body.should button_matcher | 180 | response.body.should button_matcher |
181 | end | 181 | end |
182 | 182 | ||
183 | it "should not exist for err with issue_link" do | 183 | it "should not exist for err with issue_link" do |
184 | tracker = Factory(:lighthouse_tracker) | 184 | tracker = Factory(:lighthouse_tracker) |
185 | - err = Factory(:err, :app => tracker.app, :issue_link => "http://some.host") | ||
186 | - get :show, :app_id => err.app.id, :id => err.id | 185 | + err = Factory(:err, :problem => Factory(:problem, :app => tracker.app, :issue_link => "http://some.host")) |
186 | + get :show, :app_id => err.app.id, :id => err.problem.id | ||
187 | 187 | ||
188 | response.body.should_not button_matcher | 188 | response.body.should_not button_matcher |
189 | end | 189 | end |
@@ -196,17 +196,17 @@ describe ErrsController do | @@ -196,17 +196,17 @@ describe ErrsController do | ||
196 | @unwatched_err = Factory(:err) | 196 | @unwatched_err = Factory(:err) |
197 | @watched_app = Factory(:app) | 197 | @watched_app = Factory(:app) |
198 | @watcher = Factory(:user_watcher, :user => @user, :app => @watched_app) | 198 | @watcher = Factory(:user_watcher, :user => @user, :app => @watched_app) |
199 | - @watched_err = Factory(:err, :app => @watched_app) | 199 | + @watched_err = Factory(:err, :problem => Factory(:problem, :app => @watched_app)) |
200 | end | 200 | end |
201 | 201 | ||
202 | it 'finds the err if the user is watching the app' do | 202 | it 'finds the err if the user is watching the app' do |
203 | - get :show, :app_id => @watched_app.to_param, :id => @watched_err.id | ||
204 | - assigns(:err).should == @watched_err | 203 | + get :show, :app_id => @watched_app.to_param, :id => @watched_err.problem.id |
204 | + assigns(:problem).should == @watched_err.problem | ||
205 | end | 205 | end |
206 | 206 | ||
207 | it 'raises a DocumentNotFound error if the user is not watching the app' do | 207 | it 'raises a DocumentNotFound error if the user is not watching the app' do |
208 | lambda { | 208 | lambda { |
209 | - get :show, :app_id => @unwatched_err.app_id, :id => @unwatched_err.id | 209 | + get :show, :app_id => @unwatched_err.problem.app_id, :id => @unwatched_err.problem.id |
210 | }.should raise_error(Mongoid::Errors::DocumentNotFound) | 210 | }.should raise_error(Mongoid::Errors::DocumentNotFound) |
211 | end | 211 | end |
212 | end | 212 | end |
@@ -216,38 +216,38 @@ describe ErrsController do | @@ -216,38 +216,38 @@ describe ErrsController do | ||
216 | before do | 216 | before do |
217 | sign_in Factory(:admin) | 217 | sign_in Factory(:admin) |
218 | 218 | ||
219 | - @err = Factory(:err) | ||
220 | - App.stub(:find).with(@err.app.id).and_return(@err.app) | ||
221 | - @err.app.errs.stub(:find).and_return(@err) | ||
222 | - @err.stub(:resolve!) | 219 | + @problem = Factory(:err) |
220 | + App.stub(:find).with(@problem.app.id).and_return(@problem.app) | ||
221 | + @problem.app.problems.stub(:find).and_return(@problem.problem) | ||
222 | + @problem.problem.stub(:resolve!) | ||
223 | end | 223 | end |
224 | 224 | ||
225 | it 'finds the app and the err' do | 225 | it 'finds the app and the err' do |
226 | - App.should_receive(:find).with(@err.app.id).and_return(@err.app) | ||
227 | - @err.app.errs.should_receive(:find).and_return(@err) | ||
228 | - put :resolve, :app_id => @err.app.id, :id => @err.id | ||
229 | - assigns(:app).should == @err.app | ||
230 | - assigns(:err).should == @err | 226 | + App.should_receive(:find).with(@problem.app.id).and_return(@problem.app) |
227 | + @problem.app.problems.should_receive(:find).and_return(@problem.problem) | ||
228 | + put :resolve, :app_id => @problem.app.id, :id => @problem.problem.id | ||
229 | + assigns(:app).should == @problem.app | ||
230 | + assigns(:problem).should == @problem.problem | ||
231 | end | 231 | end |
232 | 232 | ||
233 | it "should resolve the issue" do | 233 | it "should resolve the issue" do |
234 | - @err.should_receive(:resolve!).and_return(true) | ||
235 | - put :resolve, :app_id => @err.app.id, :id => @err.id | 234 | + @problem.problem.should_receive(:resolve!).and_return(true) |
235 | + put :resolve, :app_id => @problem.app.id, :id => @problem.problem.id | ||
236 | end | 236 | end |
237 | 237 | ||
238 | it "should display a message" do | 238 | it "should display a message" do |
239 | - put :resolve, :app_id => @err.app.id, :id => @err.id | 239 | + put :resolve, :app_id => @problem.app.id, :id => @problem.problem.id |
240 | request.flash[:success].should match(/Great news/) | 240 | request.flash[:success].should match(/Great news/) |
241 | end | 241 | end |
242 | 242 | ||
243 | it "should redirect to the app page" do | 243 | it "should redirect to the app page" do |
244 | - put :resolve, :app_id => @err.app.id, :id => @err.id | ||
245 | - response.should redirect_to(app_path(@err.app)) | 244 | + put :resolve, :app_id => @problem.app.id, :id => @problem.problem.id |
245 | + response.should redirect_to(app_path(@problem.app)) | ||
246 | end | 246 | end |
247 | 247 | ||
248 | it "should redirect back to errs page" do | 248 | it "should redirect back to errs page" do |
249 | request.env["Referer"] = errs_path | 249 | request.env["Referer"] = errs_path |
250 | - put :resolve, :app_id => @err.app.id, :id => @err.id | 250 | + put :resolve, :app_id => @problem.app.id, :id => @problem.problem.id |
251 | response.should redirect_to(errs_path) | 251 | response.should redirect_to(errs_path) |
252 | end | 252 | end |
253 | end | 253 | end |
@@ -262,8 +262,8 @@ describe ErrsController do | @@ -262,8 +262,8 @@ describe ErrsController do | ||
262 | context "successful issue creation" do | 262 | context "successful issue creation" do |
263 | context "lighthouseapp tracker" do | 263 | context "lighthouseapp tracker" do |
264 | let(:notice) { Factory :notice } | 264 | let(:notice) { Factory :notice } |
265 | - let(:tracker) { Factory :lighthouse_tracker, :app => notice.err.app } | ||
266 | - let(:err) { notice.err } | 265 | + let(:tracker) { Factory :lighthouse_tracker, :app => notice.app } |
266 | + let(:problem) { notice.problem } | ||
267 | 267 | ||
268 | before(:each) do | 268 | before(:each) do |
269 | number = 5 | 269 | number = 5 |
@@ -272,25 +272,25 @@ describe ErrsController do | @@ -272,25 +272,25 @@ describe ErrsController do | ||
272 | stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml"). | 272 | stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml"). |
273 | to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body ) | 273 | to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body ) |
274 | 274 | ||
275 | - post :create_issue, :app_id => err.app.id, :id => err.id | ||
276 | - err.reload | 275 | + post :create_issue, :app_id => problem.app.id, :id => problem.id |
276 | + problem.reload | ||
277 | end | 277 | end |
278 | 278 | ||
279 | - it "should redirect to err page" do | ||
280 | - response.should redirect_to( app_err_path(err.app, err) ) | 279 | + it "should redirect to problem page" do |
280 | + response.should redirect_to( app_err_path(problem.app, problem) ) | ||
281 | end | 281 | end |
282 | end | 282 | end |
283 | end | 283 | end |
284 | 284 | ||
285 | context "absent issue tracker" do | 285 | context "absent issue tracker" do |
286 | - let(:err) { Factory :err } | 286 | + let(:problem) { Factory :problem } |
287 | 287 | ||
288 | before(:each) do | 288 | before(:each) do |
289 | - post :create_issue, :app_id => err.app.id, :id => err.id | 289 | + post :create_issue, :app_id => problem.app.id, :id => problem.id |
290 | end | 290 | end |
291 | 291 | ||
292 | - it "should redirect to err page" do | ||
293 | - response.should redirect_to( app_err_path(err.app, err) ) | 292 | + it "should redirect to problem page" do |
293 | + response.should redirect_to( app_err_path(problem.app, problem) ) | ||
294 | end | 294 | end |
295 | 295 | ||
296 | it "should set flash error message telling issue tracker of the app doesn't exist" do | 296 | it "should set flash error message telling issue tracker of the app doesn't exist" do |
@@ -301,16 +301,16 @@ describe ErrsController do | @@ -301,16 +301,16 @@ describe ErrsController do | ||
301 | context "error during request to a tracker" do | 301 | context "error during request to a tracker" do |
302 | context "lighthouseapp tracker" do | 302 | context "lighthouseapp tracker" do |
303 | let(:tracker) { Factory :lighthouse_tracker } | 303 | let(:tracker) { Factory :lighthouse_tracker } |
304 | - let(:err) { Factory :err, :app => tracker.app } | 304 | + let(:err) { Factory(:err, :problem => Factory(:problem, :app => tracker.app)) } |
305 | 305 | ||
306 | before(:each) do | 306 | before(:each) do |
307 | stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml").to_return(:status => 500) | 307 | stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml").to_return(:status => 500) |
308 | 308 | ||
309 | - post :create_issue, :app_id => err.app.id, :id => err.id | 309 | + post :create_issue, :app_id => err.app.id, :id => err.problem.id |
310 | end | 310 | end |
311 | 311 | ||
312 | it "should redirect to err page" do | 312 | it "should redirect to err page" do |
313 | - response.should redirect_to( app_err_path(err.app, err) ) | 313 | + response.should redirect_to( app_err_path(err.app, err.problem) ) |
314 | end | 314 | end |
315 | 315 | ||
316 | it "should notify of connection error" do | 316 | it "should notify of connection error" do |
@@ -326,19 +326,19 @@ describe ErrsController do | @@ -326,19 +326,19 @@ describe ErrsController do | ||
326 | end | 326 | end |
327 | 327 | ||
328 | context "err with issue" do | 328 | context "err with issue" do |
329 | - let(:err) { Factory :err, :issue_link => "http://some.host" } | 329 | + let(:err) { Factory(:err, :problem => Factory(:problem, :issue_link => "http://some.host")) } |
330 | 330 | ||
331 | before(:each) do | 331 | before(:each) do |
332 | - delete :unlink_issue, :app_id => err.app.id, :id => err.id | ||
333 | - err.reload | 332 | + delete :unlink_issue, :app_id => err.app.id, :id => err.problem.id |
333 | + err.problem.reload | ||
334 | end | 334 | end |
335 | 335 | ||
336 | it "should redirect to err page" do | 336 | it "should redirect to err page" do |
337 | - response.should redirect_to( app_err_path(err.app, err) ) | 337 | + response.should redirect_to( app_err_path(err.app, err.problem) ) |
338 | end | 338 | end |
339 | 339 | ||
340 | it "should clear issue link" do | 340 | it "should clear issue link" do |
341 | - err.issue_link.should be_nil | 341 | + err.problem.issue_link.should be_nil |
342 | end | 342 | end |
343 | end | 343 | end |
344 | 344 | ||
@@ -346,12 +346,12 @@ describe ErrsController do | @@ -346,12 +346,12 @@ describe ErrsController do | ||
346 | let(:err) { Factory :err } | 346 | let(:err) { Factory :err } |
347 | 347 | ||
348 | before(:each) do | 348 | before(:each) do |
349 | - delete :unlink_issue, :app_id => err.app.id, :id => err.id | ||
350 | - err.reload | 349 | + delete :unlink_issue, :app_id => err.app.id, :id => err.problem.id |
350 | + err.problem.reload | ||
351 | end | 351 | end |
352 | 352 | ||
353 | it "should redirect to err page" do | 353 | it "should redirect to err page" do |
354 | - response.should redirect_to( app_err_path(err.app, err) ) | 354 | + response.should redirect_to( app_err_path(err.app, err.problem) ) |
355 | end | 355 | end |
356 | end | 356 | end |
357 | end | 357 | end |
@@ -365,21 +365,21 @@ describe ErrsController do | @@ -365,21 +365,21 @@ describe ErrsController do | ||
365 | end | 365 | end |
366 | 366 | ||
367 | context "successful comment creation" do | 367 | context "successful comment creation" do |
368 | - let(:err) { Factory(:err) } | 368 | + let(:problem) { Factory(:problem) } |
369 | let(:user) { Factory(:user) } | 369 | let(:user) { Factory(:user) } |
370 | 370 | ||
371 | before(:each) do | 371 | before(:each) do |
372 | - post :create_comment, :app_id => err.app.id, :id => err.id, | 372 | + post :create_comment, :app_id => problem.app.id, :id => problem.id, |
373 | :comment => { :body => "One test comment", :user_id => user.id } | 373 | :comment => { :body => "One test comment", :user_id => user.id } |
374 | - err.reload | 374 | + problem.reload |
375 | end | 375 | end |
376 | 376 | ||
377 | it "should create the comment" do | 377 | it "should create the comment" do |
378 | - err.comments.size.should == 1 | 378 | + problem.comments.size.should == 1 |
379 | end | 379 | end |
380 | 380 | ||
381 | - it "should redirect to err page" do | ||
382 | - response.should redirect_to( app_err_path(err.app, err) ) | 381 | + it "should redirect to problem page" do |
382 | + response.should redirect_to( app_err_path(problem.app, problem) ) | ||
383 | end | 383 | end |
384 | end | 384 | end |
385 | end | 385 | end |
@@ -392,20 +392,85 @@ describe ErrsController do | @@ -392,20 +392,85 @@ describe ErrsController do | ||
392 | end | 392 | end |
393 | 393 | ||
394 | context "successful comment deletion" do | 394 | context "successful comment deletion" do |
395 | - let(:err) { Factory :err_with_comments } | ||
396 | - let(:comment) { err.comments.first } | 395 | + let(:problem) { Factory(:problem_with_comments) } |
396 | + let(:comment) { problem.comments.first } | ||
397 | 397 | ||
398 | before(:each) do | 398 | before(:each) do |
399 | - delete :destroy_comment, :app_id => err.app.id, :id => err.id, :comment_id => comment.id | ||
400 | - err.reload | 399 | + delete :destroy_comment, :app_id => problem.app.id, :id => problem.id, :comment_id => comment.id |
400 | + problem.reload | ||
401 | end | 401 | end |
402 | 402 | ||
403 | it "should delete the comment" do | 403 | it "should delete the comment" do |
404 | - err.comments.detect{|c| c.id.to_s == comment.id }.should == nil | 404 | + problem.comments.detect{|c| c.id.to_s == comment.id }.should == nil |
405 | end | 405 | end |
406 | 406 | ||
407 | - it "should redirect to err page" do | ||
408 | - response.should redirect_to( app_err_path(err.app, err) ) | 407 | + it "should redirect to problem page" do |
408 | + response.should redirect_to( app_err_path(problem.app, problem) ) | ||
409 | + end | ||
410 | + end | ||
411 | + end | ||
412 | + | ||
413 | + describe "Bulk Actions" do | ||
414 | + before(:each) do | ||
415 | + sign_in Factory(:admin) | ||
416 | + @problem1 = Factory(:err, :problem => Factory(:problem, :resolved => true)).problem | ||
417 | + @problem2 = Factory(:err, :problem => Factory(:problem, :resolved => false)).problem | ||
418 | + end | ||
419 | + | ||
420 | + it "should apply to multiple problems" do | ||
421 | + post :resolve_several, :problems => [@problem1.id.to_s, @problem2.id.to_s] | ||
422 | + assigns(:selected_problems).should == [@problem1, @problem2] | ||
423 | + end | ||
424 | + | ||
425 | + it "should require at least one problem" do | ||
426 | + post :resolve_several, :problems => [] | ||
427 | + request.flash[:notice].should match(/You have not selected any/) | ||
428 | + end | ||
429 | + | ||
430 | + context "POST /errs/merge_several" do | ||
431 | + it "should require at least two problems" do | ||
432 | + post :merge_several, :problems => [@problem1.id.to_s] | ||
433 | + request.flash[:notice].should match(/You must select at least two/) | ||
434 | + end | ||
435 | + | ||
436 | + it "should merge the problems" do | ||
437 | + lambda { | ||
438 | + post :merge_several, :problems => [@problem1.id.to_s, @problem2.id.to_s] | ||
439 | + assigns(:merged_problem).reload.errs.length.should == 2 | ||
440 | + }.should change(Problem, :count).by(-1) | ||
441 | + end | ||
442 | + end | ||
443 | + | ||
444 | + context "POST /errs/unmerge_several" do | ||
445 | + it "should unmerge a merged problem" do | ||
446 | + merged_problem = Problem.merge!(@problem1, @problem2) | ||
447 | + merged_problem.errs.length.should == 2 | ||
448 | + lambda { | ||
449 | + post :unmerge_several, :problems => [merged_problem.id.to_s] | ||
450 | + merged_problem.reload.errs.length.should == 1 | ||
451 | + }.should change(Problem, :count).by(1) | ||
452 | + end | ||
453 | + end | ||
454 | + | ||
455 | + context "POST /errs/resolve_several" do | ||
456 | + it "should resolve the issue" do | ||
457 | + post :resolve_several, :problems => [@problem2.id.to_s] | ||
458 | + @problem2.reload.resolved?.should == true | ||
459 | + end | ||
460 | + end | ||
461 | + | ||
462 | + context "POST /errs/unresolve_several" do | ||
463 | + it "should unresolve the issue" do | ||
464 | + post :unresolve_several, :problems => [@problem1.id.to_s] | ||
465 | + @problem1.reload.resolved?.should == false | ||
466 | + end | ||
467 | + end | ||
468 | + | ||
469 | + context "POST /errs/destroy_several" do | ||
470 | + it "should delete the errs" do | ||
471 | + lambda { | ||
472 | + post :destroy_several, :problems => [@problem1.id.to_s] | ||
473 | + }.should change(Problem, :count).by(-1) | ||
409 | end | 474 | end |
410 | end | 475 | end |
411 | end | 476 | end |
spec/controllers/notices_controller_spec.rb
@@ -7,20 +7,20 @@ describe NoticesController do | @@ -7,20 +7,20 @@ describe NoticesController do | ||
7 | @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read | 7 | @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read |
8 | @app = Factory(:app_with_watcher) | 8 | @app = Factory(:app_with_watcher) |
9 | App.stub(:find_by_api_key!).and_return(@app) | 9 | App.stub(:find_by_api_key!).and_return(@app) |
10 | - @notice = Notice.from_xml(@xml) | 10 | + @notice = App.report_error!(@xml) |
11 | 11 | ||
12 | request.env['Content-type'] = 'text/xml' | 12 | request.env['Content-type'] = 'text/xml' |
13 | request.env['Accept'] = 'text/xml, application/xml' | 13 | request.env['Accept'] = 'text/xml, application/xml' |
14 | end | 14 | end |
15 | 15 | ||
16 | it "generates a notice from xml [POST]" do | 16 | it "generates a notice from xml [POST]" do |
17 | - Notice.should_receive(:from_xml).with(@xml).and_return(@notice) | 17 | + App.should_receive(:report_error!).with(@xml).and_return(@notice) |
18 | request.should_receive(:raw_post).and_return(@xml) | 18 | request.should_receive(:raw_post).and_return(@xml) |
19 | post :create | 19 | post :create |
20 | end | 20 | end |
21 | 21 | ||
22 | it "generates a notice from xml [GET]" do | 22 | it "generates a notice from xml [GET]" do |
23 | - Notice.should_receive(:from_xml).with(@xml).and_return(@notice) | 23 | + App.should_receive(:report_error!).with(@xml).and_return(@notice) |
24 | get :create, {:data => @xml} | 24 | get :create, {:data => @xml} |
25 | end | 25 | end |
26 | 26 | ||
@@ -29,10 +29,11 @@ describe NoticesController do | @@ -29,10 +29,11 @@ describe NoticesController do | ||
29 | post :create | 29 | post :create |
30 | email = ActionMailer::Base.deliveries.last | 30 | email = ActionMailer::Base.deliveries.last |
31 | email.to.should include(@app.watchers.first.email) | 31 | email.to.should include(@app.watchers.first.email) |
32 | - email.subject.should include(@notice.err.message) | 32 | + email.subject.should include(@notice.message) |
33 | email.subject.should include("[#{@app.name}]") | 33 | email.subject.should include("[#{@app.name}]") |
34 | - email.subject.should include("[#{@notice.err.environment}]") | 34 | + email.subject.should include("[#{@notice.environment_name}]") |
35 | end | 35 | end |
36 | end | 36 | end |
37 | 37 | ||
38 | end | 38 | end |
39 | + |
spec/controllers/users_controller_spec.rb
spec/factories.rb
@@ -3,3 +3,4 @@ Factory.sequence(:word) {|n| "word#{n}"} | @@ -3,3 +3,4 @@ Factory.sequence(:word) {|n| "word#{n}"} | ||
3 | Factory.sequence(:app_name) {|n| "App ##{n}"} | 3 | Factory.sequence(:app_name) {|n| "App ##{n}"} |
4 | Factory.sequence(:email) {|n| "email#{n}@example.com"} | 4 | Factory.sequence(:email) {|n| "email#{n}@example.com"} |
5 | Factory.sequence(:user_email) {|n| "user.#{n}@example.com"} | 5 | Factory.sequence(:user_email) {|n| "user.#{n}@example.com"} |
6 | + |
spec/factories/app_factories.rb
@@ -20,7 +20,7 @@ Factory.define(:user_watcher, :parent => :watcher) do |w| | @@ -20,7 +20,7 @@ Factory.define(:user_watcher, :parent => :watcher) do |w| | ||
20 | end | 20 | end |
21 | 21 | ||
22 | Factory.define(:deploy) do |d| | 22 | Factory.define(:deploy) do |d| |
23 | - d.app {|p| p.association :app} | 23 | + d.app {|p| p.association :app} |
24 | d.username 'clyde.frog' | 24 | d.username 'clyde.frog' |
25 | d.repository 'git@github.com/errbit/errbit.git' | 25 | d.repository 'git@github.com/errbit/errbit.git' |
26 | d.environment 'production' | 26 | d.environment 'production' |
spec/factories/err_factories.rb
1 | +Factory.define :problem do |p| | ||
2 | + p.app {|a| a.association :app} | ||
3 | + p.comments [] | ||
4 | +end | ||
5 | + | ||
6 | +Factory.define(:problem_with_comments, :parent => :problem) do |ec| | ||
7 | + ec.comments { (1..3).map { Factory(:comment) } } | ||
8 | +end | ||
9 | + | ||
10 | + | ||
11 | + | ||
1 | Factory.define :err do |e| | 12 | Factory.define :err do |e| |
2 | - e.app {|p| p.association :app } | 13 | + e.problem {|p| p.association :problem} |
3 | e.klass 'FooError' | 14 | e.klass 'FooError' |
4 | e.component 'foo' | 15 | e.component 'foo' |
5 | e.action 'bar' | 16 | e.action 'bar' |
6 | e.environment 'production' | 17 | e.environment 'production' |
7 | - e.comments [] | ||
8 | end | 18 | end |
9 | 19 | ||
10 | -Factory.define(:err_with_comments, :parent => :err) do |ec| | ||
11 | - ec.comments { (1..3).map{Factory(:comment)} } | ||
12 | -end | 20 | + |
13 | 21 | ||
14 | Factory.define :notice do |n| | 22 | Factory.define :notice do |n| |
15 | n.err {|e| e.association :err} | 23 | n.err {|e| e.association :err} |
16 | n.message 'FooError: Too Much Bar' | 24 | n.message 'FooError: Too Much Bar' |
17 | n.backtrace { random_backtrace } | 25 | n.backtrace { random_backtrace } |
18 | - n.server_environment 'server-environment' => 'production' | 26 | + n.server_environment 'environment-name' => 'production' |
27 | + n.request {{ 'component' => 'foo', 'action' => 'bar' }} | ||
19 | n.notifier 'name' => 'Notifier', 'version' => '1', 'url' => 'http://toad.com' | 28 | n.notifier 'name' => 'Notifier', 'version' => '1', 'url' => 'http://toad.com' |
20 | end | 29 | end |
21 | 30 |
spec/factories/user_factories.rb
spec/fixtures/hoptoad_test_notice_with_one_line_of_backtrace.xml
0 → 100644
@@ -0,0 +1,75 @@ | @@ -0,0 +1,75 @@ | ||
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<notice version="2.0"> | ||
3 | + <api-key>APIKEY</api-key> | ||
4 | + <notifier> | ||
5 | + <name>Hoptoad Notifier</name> | ||
6 | + <version>2.3.2</version> | ||
7 | + <url>http://hoptoadapp.com</url> | ||
8 | + </notifier> | ||
9 | + <error> | ||
10 | + <class>HoptoadTestingException</class> | ||
11 | + <message>HoptoadTestingException: Testing hoptoad via "rake hoptoad:test". If you can see this, it works.</message> | ||
12 | + <backtrace> | ||
13 | + <line number="425" file="[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/callbacks.rb" method="_run__2115867319__process_action__262109504__callbacks"/> | ||
14 | + </backtrace> | ||
15 | + </error> | ||
16 | + <request> | ||
17 | + <url>http://example.org/verify</url> | ||
18 | + <component>application</component> | ||
19 | + <action>verify</action> | ||
20 | + <params> | ||
21 | + <var key="action">verify</var> | ||
22 | + <var key="controller">application</var> | ||
23 | + </params> | ||
24 | + <cgi-data> | ||
25 | + <var key="rack.session"/> | ||
26 | + <var key="action_dispatch.request.formats">text/html</var> | ||
27 | + <var key="action_dispatch.request.parameters"> | ||
28 | + <var key="action">verify</var> | ||
29 | + <var key="controller">application</var> | ||
30 | + </var> | ||
31 | + <var key="SERVER_NAME">example.org</var> | ||
32 | + <var key="rack.url_scheme">http</var> | ||
33 | + <var key="action_dispatch.remote_ip"/> | ||
34 | + <var key="CONTENT_LENGTH">0</var> | ||
35 | + <var key="rack.errors">#<StringIO:0x103d9dec0></var> | ||
36 | + <var key="action_dispatch.request.unsigned_session_cookie"/> | ||
37 | + <var key="action_dispatch.request.query_parameters"/> | ||
38 | + <var key="HTTPS">off</var> | ||
39 | + <var key="rack.run_once">false</var> | ||
40 | + <var key="PATH_INFO">/verify</var> | ||
41 | + <var key="action_dispatch.secret_token">994f235e3372684bc736dd11842b754d2ddcffc8c2958d33a29527c3217becd6655fa4653a318bc7c34131f9baf2acc0c424ed07e48e0e5e87c6cd34d711e985</var> | ||
42 | + <var key="rack.version">11</var> | ||
43 | + <var key="SCRIPT_NAME"/> | ||
44 | + <var key="action_dispatch.request.path_parameters"> | ||
45 | + <var key="action">verify</var> | ||
46 | + <var key="controller">application</var> | ||
47 | + </var> | ||
48 | + <var key="rack.multithread">false</var> | ||
49 | + <var key="action_dispatch.parameter_filter">password</var> | ||
50 | + <var key="action_dispatch.cookies"/> | ||
51 | + <var key="action_dispatch.request.request_parameters"/> | ||
52 | + <var key="rack.multiprocess">true</var> | ||
53 | + <var key="rack.request.query_hash"/> | ||
54 | + <var key="SERVER_PORT">80</var> | ||
55 | + <var key="REQUEST_METHOD">GET</var> | ||
56 | + <var key="action_controller.instance">#<ApplicationController:0x103d2f560></var> | ||
57 | + <var key="rack.session.options"> | ||
58 | + <var key="secure">false</var> | ||
59 | + <var key="httponly">true</var> | ||
60 | + <var key="path">/</var> | ||
61 | + <var key="expire_after"/> | ||
62 | + <var key="domain"/> | ||
63 | + <var key="id"/> | ||
64 | + </var> | ||
65 | + <var key="rack.input">#<StringIO:0x103d9dc90></var> | ||
66 | + <var key="action_dispatch.request.content_type"/> | ||
67 | + <var key="rack.request.query_string"/> | ||
68 | + <var key="QUERY_STRING"/> | ||
69 | + </cgi-data> | ||
70 | + </request> | ||
71 | + <server-environment> | ||
72 | + <project-root>/path/to/sample/project</project-root> | ||
73 | + <environment-name>development</environment-name> | ||
74 | + </server-environment> | ||
75 | +</notice> | ||
0 | \ No newline at end of file | 76 | \ No newline at end of file |
spec/models/app_spec.rb
1 | require 'spec_helper' | 1 | require 'spec_helper' |
2 | 2 | ||
3 | describe App do | 3 | describe App do |
4 | - | ||
5 | context 'validations' do | 4 | context 'validations' do |
6 | it 'requires a name' do | 5 | it 'requires a name' do |
7 | app = Factory.build(:app, :name => nil) | 6 | app = Factory.build(:app, :name => nil) |
@@ -24,6 +23,7 @@ describe App do | @@ -24,6 +23,7 @@ describe App do | ||
24 | end | 23 | end |
25 | end | 24 | end |
26 | 25 | ||
26 | + | ||
27 | context 'being created' do | 27 | context 'being created' do |
28 | it 'generates a new api-key' do | 28 | it 'generates a new api-key' do |
29 | app = Factory.build(:app) | 29 | app = Factory.build(:app) |
@@ -99,6 +99,7 @@ describe App do | @@ -99,6 +99,7 @@ describe App do | ||
99 | end | 99 | end |
100 | end | 100 | end |
101 | 101 | ||
102 | + | ||
102 | context "copying attributes from existing app" do | 103 | context "copying attributes from existing app" do |
103 | it "should only copy the necessary fields" do | 104 | it "should only copy the necessary fields" do |
104 | @app, @copy_app = Factory(:app, :name => "app", :github_url => "url"), | 105 | @app, @copy_app = Factory(:app, :name => "app", :github_url => "url"), |
@@ -110,5 +111,127 @@ describe App do | @@ -110,5 +111,127 @@ describe App do | ||
110 | @app.watchers.first.email.should == "copywatcher@example.com" | 111 | @app.watchers.first.email.should == "copywatcher@example.com" |
111 | end | 112 | end |
112 | end | 113 | end |
114 | + | ||
115 | + | ||
116 | + context '#find_or_create_err!' do | ||
117 | + before do | ||
118 | + @app = Factory(:app) | ||
119 | + @conditions = { | ||
120 | + :klass => 'Whoops', | ||
121 | + :component => 'Foo', | ||
122 | + :action => 'bar', | ||
123 | + :environment => 'production' | ||
124 | + } | ||
125 | + end | ||
126 | + | ||
127 | + it 'returns the correct err if one already exists' do | ||
128 | + existing = Factory(:err, @conditions.merge(:problem => Factory(:problem, :app => @app))) | ||
129 | + Err.where(@conditions).first.should == existing | ||
130 | + @app.find_or_create_err!(@conditions).should == existing | ||
131 | + end | ||
132 | + | ||
133 | + it 'assigns the returned err to the given app' do | ||
134 | + @app.find_or_create_err!(@conditions).app.should == @app | ||
135 | + end | ||
136 | + | ||
137 | + it 'creates a new problem if a matching one does not already exist' do | ||
138 | + Err.where(@conditions).first.should be_nil | ||
139 | + lambda { | ||
140 | + @app.find_or_create_err!(@conditions) | ||
141 | + }.should change(Problem,:count).by(1) | ||
142 | + end | ||
143 | + end | ||
144 | + | ||
145 | + | ||
146 | + context '#report_error!' do | ||
147 | + before do | ||
148 | + @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read | ||
149 | + @app = Factory(:app, :api_key => 'APIKEY') | ||
150 | + ErrorReport.any_instance.stub(:fingerprint).and_return('fingerprintdigest') | ||
151 | + end | ||
152 | + | ||
153 | + it 'finds the correct app' do | ||
154 | + @notice = App.report_error!(@xml) | ||
155 | + @notice.err.app.should == @app | ||
156 | + end | ||
157 | + | ||
158 | + it 'finds the correct err for the notice' do | ||
159 | + App.should_receive(:find_by_api_key!).and_return(@app) | ||
160 | + @app.should_receive(:find_or_create_err!).with({ | ||
161 | + :klass => 'HoptoadTestingException', | ||
162 | + :component => 'application', | ||
163 | + :action => 'verify', | ||
164 | + :environment => 'development', | ||
165 | + :fingerprint => 'fingerprintdigest' | ||
166 | + }).and_return(err = Factory(:err)) | ||
167 | + err.notices.stub(:create!) | ||
168 | + @notice = App.report_error!(@xml) | ||
169 | + end | ||
170 | + | ||
171 | + it 'marks the err as unresolved if it was previously resolved' do | ||
172 | + App.should_receive(:find_by_api_key!).and_return(@app) | ||
173 | + @app.should_receive(:find_or_create_err!).with({ | ||
174 | + :klass => 'HoptoadTestingException', | ||
175 | + :component => 'application', | ||
176 | + :action => 'verify', | ||
177 | + :environment => 'development', | ||
178 | + :fingerprint => 'fingerprintdigest' | ||
179 | + }).and_return(err = Factory(:err, :problem => Factory(:problem, :resolved => true))) | ||
180 | + err.should be_resolved | ||
181 | + @notice = App.report_error!(@xml) | ||
182 | + @notice.err.should == err | ||
183 | + @notice.err.should_not be_resolved | ||
184 | + end | ||
185 | + | ||
186 | + it 'should create a new notice' do | ||
187 | + @notice = App.report_error!(@xml) | ||
188 | + @notice.should be_persisted | ||
189 | + end | ||
190 | + | ||
191 | + it 'assigns an err to the notice' do | ||
192 | + @notice = App.report_error!(@xml) | ||
193 | + @notice.err.should be_a(Err) | ||
194 | + end | ||
195 | + | ||
196 | + it 'captures the err message' do | ||
197 | + @notice = App.report_error!(@xml) | ||
198 | + @notice.message.should == 'HoptoadTestingException: Testing hoptoad via "rake hoptoad:test". If you can see this, it works.' | ||
199 | + end | ||
200 | + | ||
201 | + it 'captures the backtrace' do | ||
202 | + @notice = App.report_error!(@xml) | ||
203 | + @notice.backtrace.size.should == 73 | ||
204 | + @notice.backtrace.last['file'].should == '[GEM_ROOT]/bin/rake' | ||
205 | + end | ||
206 | + | ||
207 | + it 'captures the server_environment' do | ||
208 | + @notice = App.report_error!(@xml) | ||
209 | + @notice.server_environment['environment-name'].should == 'development' | ||
210 | + end | ||
211 | + | ||
212 | + it 'captures the request' do | ||
213 | + @notice = App.report_error!(@xml) | ||
214 | + @notice.request['url'].should == 'http://example.org/verify' | ||
215 | + @notice.request['params']['controller'].should == 'application' | ||
216 | + end | ||
217 | + | ||
218 | + it 'captures the notifier' do | ||
219 | + @notice = App.report_error!(@xml) | ||
220 | + @notice.notifier['name'].should == 'Hoptoad Notifier' | ||
221 | + end | ||
222 | + | ||
223 | + it "should handle params without 'request' section" do | ||
224 | + xml = Rails.root.join('spec','fixtures','hoptoad_test_notice_without_request_section.xml').read | ||
225 | + lambda { App.report_error!(xml) }.should_not raise_error | ||
226 | + end | ||
227 | + | ||
228 | + it "should handle params with only a single line of backtrace" do | ||
229 | + xml = Rails.root.join('spec','fixtures','hoptoad_test_notice_with_one_line_of_backtrace.xml').read | ||
230 | + lambda { @notice = App.report_error!(xml) }.should_not raise_error | ||
231 | + @notice.backtrace.length.should == 1 | ||
232 | + end | ||
233 | + end | ||
234 | + | ||
235 | + | ||
113 | end | 236 | end |
114 | 237 |
spec/models/deploy_spec.rb
@@ -26,20 +26,20 @@ describe Deploy do | @@ -26,20 +26,20 @@ describe Deploy do | ||
26 | context 'when the app has resolve_errs_on_deploy set to false' do | 26 | context 'when the app has resolve_errs_on_deploy set to false' do |
27 | it 'should not resolve the apps errs' do | 27 | it 'should not resolve the apps errs' do |
28 | app = Factory(:app, :resolve_errs_on_deploy => false) | 28 | app = Factory(:app, :resolve_errs_on_deploy => false) |
29 | - @errs = 3.times.inject([]) {|errs,_| errs << Factory(:err, :resolved => false, :app => app)} | 29 | + @problems = 3.times.map{Factory(:err, :problem => Factory(:problem, :resolved => false, :app => app))} |
30 | Factory(:deploy, :app => app) | 30 | Factory(:deploy, :app => app) |
31 | - app.reload.errs.none?{|err| err.resolved?}.should == true | 31 | + app.reload.problems.none?{|problem| problem.resolved?}.should == true |
32 | end | 32 | end |
33 | end | 33 | end |
34 | 34 | ||
35 | context 'when the app has resolve_errs_on_deploy set to true' do | 35 | context 'when the app has resolve_errs_on_deploy set to true' do |
36 | it 'should resolve the apps errs that were in the same environment' do | 36 | it 'should resolve the apps errs that were in the same environment' do |
37 | app = Factory(:app, :resolve_errs_on_deploy => true) | 37 | app = Factory(:app, :resolve_errs_on_deploy => true) |
38 | - @prod_errs = 3.times.inject([]) {|errs,_| errs << Factory(:err, :resolved => false, :app => app, :environment => 'production')} | ||
39 | - @staging_errs = 3.times.inject([]) {|errs,_| errs << Factory(:err, :resolved => false, :app => app, :environment => 'staging')} | 38 | + @prod_errs = 3.times.map{Factory(:problem, :resolved => false, :app => app, :environment => 'production')} |
39 | + @staging_errs = 3.times.map{Factory(:problem, :resolved => false, :app => app, :environment => 'staging')} | ||
40 | Factory(:deploy, :app => app, :environment => 'production') | 40 | Factory(:deploy, :app => app, :environment => 'production') |
41 | - @prod_errs.all?{|err| err.reload.resolved?}.should == true | ||
42 | - @staging_errs.all?{|err| err.reload.resolved?}.should == false | 41 | + @prod_errs.all?{|problem| problem.reload.resolved?}.should == true |
42 | + @staging_errs.all?{|problem| problem.reload.resolved?}.should == false | ||
43 | end | 43 | end |
44 | end | 44 | end |
45 | 45 |
spec/models/err_spec.rb
@@ -16,157 +16,5 @@ describe Err do | @@ -16,157 +16,5 @@ describe Err do | ||
16 | end | 16 | end |
17 | end | 17 | end |
18 | 18 | ||
19 | - context '#for' do | ||
20 | - before do | ||
21 | - @app = Factory(:app) | ||
22 | - @conditions = { | ||
23 | - :app => @app, | ||
24 | - :klass => 'Whoops', | ||
25 | - :component => 'Foo', | ||
26 | - :action => 'bar', | ||
27 | - :environment => 'production' | ||
28 | - } | ||
29 | - end | ||
30 | - | ||
31 | - it 'returns the correct err if one already exists' do | ||
32 | - existing = Err.create(@conditions) | ||
33 | - Err.for(@conditions).should == existing | ||
34 | - end | ||
35 | - | ||
36 | - it 'assigns the returned err to the given app' do | ||
37 | - Err.for(@conditions).app.should == @app | ||
38 | - end | ||
39 | - | ||
40 | - it 'creates a new err if a matching one does not already exist' do | ||
41 | - Err.where(@conditions.except(:app)).exists?.should == false | ||
42 | - lambda { | ||
43 | - Err.for(@conditions) | ||
44 | - }.should change(Err,:count).by(1) | ||
45 | - end | ||
46 | - end | ||
47 | - | ||
48 | - context '#last_notice_at' do | ||
49 | - it "returns the created_at timestamp of the latest notice" do | ||
50 | - err = Factory(:err) | ||
51 | - err.last_notice_at.should be_nil | ||
52 | - | ||
53 | - notice1 = Factory(:notice, :err => err) | ||
54 | - err.last_notice_at.should == notice1.created_at | ||
55 | - | ||
56 | - notice2 = Factory(:notice, :err => err) | ||
57 | - err.last_notice_at.should == notice2.created_at | ||
58 | - end | ||
59 | - end | ||
60 | - | ||
61 | - context '#message' do | ||
62 | - it "returns klass by default" do | ||
63 | - err = Factory(:err) | ||
64 | - err.message.should == err.klass | ||
65 | - end | ||
66 | - | ||
67 | - it 'returns the message from the first notice' do | ||
68 | - err = Factory(:err) | ||
69 | - notice1 = Factory(:notice, :err => err, :message => 'ERR 1') | ||
70 | - notice2 = Factory(:notice, :err => err, :message => 'ERR 2') | ||
71 | - err.message.should == notice1.message | ||
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 | ||
79 | - end | ||
80 | - | ||
81 | - context "#resolved?" do | ||
82 | - it "should start out as unresolved" do | ||
83 | - err = Err.new | ||
84 | - err.should_not be_resolved | ||
85 | - err.should be_unresolved | ||
86 | - end | ||
87 | - | ||
88 | - it "should be able to be resolved" do | ||
89 | - err = Factory(:err) | ||
90 | - err.should_not be_resolved | ||
91 | - err.resolve! | ||
92 | - err.reload.should be_resolved | ||
93 | - end | ||
94 | - end | ||
95 | - | ||
96 | - context "resolve!" do | ||
97 | - it "marks the err as resolved" do | ||
98 | - err = Factory(:err) | ||
99 | - err.should_not be_resolved | ||
100 | - err.resolve! | ||
101 | - err.should be_resolved | ||
102 | - end | ||
103 | - | ||
104 | - it "should throw an err if it's not successful" do | ||
105 | - err = Factory(:err) | ||
106 | - err.should_not be_resolved | ||
107 | - err.klass = nil | ||
108 | - err.should_not be_valid | ||
109 | - lambda { | ||
110 | - err.resolve! | ||
111 | - }.should raise_error(Mongoid::Errors::Validations) | ||
112 | - end | ||
113 | - end | ||
114 | - | ||
115 | - context "Scopes" do | ||
116 | - context "resolved" do | ||
117 | - it 'only finds resolved Errs' do | ||
118 | - resolved = Factory(:err, :resolved => true) | ||
119 | - unresolved = Factory(:err, :resolved => false) | ||
120 | - Err.resolved.all.should include(resolved) | ||
121 | - Err.resolved.all.should_not include(unresolved) | ||
122 | - end | ||
123 | - end | ||
124 | - | ||
125 | - context "unresolved" do | ||
126 | - it 'only finds unresolved Errs' do | ||
127 | - resolved = Factory(:err, :resolved => true) | ||
128 | - unresolved = Factory(:err, :resolved => false) | ||
129 | - Err.unresolved.all.should_not include(resolved) | ||
130 | - Err.unresolved.all.should include(unresolved) | ||
131 | - end | ||
132 | - end | ||
133 | - end | ||
134 | - | ||
135 | - context 'being created' do | ||
136 | - context 'when the app has err notifications set to false' do | ||
137 | - it 'should not send an email notification' do | ||
138 | - app = Factory(:app_with_watcher, :notify_on_errs => false) | ||
139 | - Mailer.should_not_receive(:err_notification) | ||
140 | - Factory(:err, :app => app) | ||
141 | - end | ||
142 | - end | ||
143 | - end | ||
144 | - | ||
145 | - context "notice counter cache" do | ||
146 | - | ||
147 | - before do | ||
148 | - @app = Factory(:app) | ||
149 | - @err = Factory(:err, :app => @app) | ||
150 | - end | ||
151 | - | ||
152 | - it "#notices_count returns 0 by default" do | ||
153 | - @err.notices_count.should == 0 | ||
154 | - end | ||
155 | - | ||
156 | - it "adding a notice increases #notices_count by 1" do | ||
157 | - lambda { | ||
158 | - notice1 = Factory(:notice, :err => @err, :message => 'ERR 1')}.should change(@err, :notices_count).from(0).to(1) | ||
159 | - end | ||
160 | - | ||
161 | - it "removing a notice decreases #notices_count by 1" do | ||
162 | - notice1 = Factory(:notice, :err => @err, :message => 'ERR 1') | ||
163 | - lambda { | ||
164 | - @err.notices.first.destroy | ||
165 | - @err.reload | ||
166 | - }.should change(@err, :notices_count).from(1).to(0) | ||
167 | - end | ||
168 | - end | ||
169 | - | ||
170 | - | ||
171 | end | 19 | end |
172 | 20 |
spec/models/issue_trackers/fogbugz_tracker_spec.rb
1 | require 'spec_helper' | 1 | require 'spec_helper' |
2 | 2 | ||
3 | describe FogbugzTracker do | 3 | describe FogbugzTracker do |
4 | - it "should create an issue on Fogbugz with err params, and set issue link for err" do | 4 | + it "should create an issue on Fogbugz with problem params, and set issue link for problem" do |
5 | notice = Factory :notice | 5 | notice = Factory :notice |
6 | - tracker = Factory :fogbugz_tracker, :app => notice.err.app | ||
7 | - err = notice.err | 6 | + tracker = Factory :fogbugz_tracker, :app => notice.app |
7 | + problem = notice.problem | ||
8 | 8 | ||
9 | number = 123 | 9 | number = 123 |
10 | @issue_link = "https://#{tracker.account}.fogbugz.com/default.asp?#{number}" | 10 | @issue_link = "https://#{tracker.account}.fogbugz.com/default.asp?#{number}" |
@@ -14,10 +14,10 @@ describe FogbugzTracker do | @@ -14,10 +14,10 @@ describe FogbugzTracker do | ||
14 | http_mock.should_receive(:request).twice.and_return(response) | 14 | http_mock.should_receive(:request).twice.and_return(response) |
15 | Fogbugz.adapter[:http] = http_mock | 15 | Fogbugz.adapter[:http] = http_mock |
16 | 16 | ||
17 | - err.app.issue_tracker.create_issue(err) | ||
18 | - err.reload | 17 | + problem.app.issue_tracker.create_issue(problem) |
18 | + problem.reload | ||
19 | 19 | ||
20 | - err.issue_link.should == @issue_link | 20 | + problem.issue_link.should == @issue_link |
21 | end | 21 | end |
22 | end | 22 | end |
23 | 23 |
spec/models/issue_trackers/github_issues_tracker_spec.rb
1 | require 'spec_helper' | 1 | require 'spec_helper' |
2 | 2 | ||
3 | describe GithubIssuesTracker do | 3 | describe GithubIssuesTracker do |
4 | - it "should create an issue on Github Issues with err params, and set issue link for err" do | 4 | + it "should create an issue on Github Issues with problem params, and set issue link for problem" do |
5 | notice = Factory :notice | 5 | notice = Factory :notice |
6 | - tracker = Factory :github_issues_tracker, :app => notice.err.app | ||
7 | - err = notice.err | 6 | + tracker = Factory :github_issues_tracker, :app => notice.app |
7 | + problem = notice.problem | ||
8 | 8 | ||
9 | number = 5 | 9 | number = 5 |
10 | @issue_link = "https://github.com/#{tracker.project_id}/issues/#{number}" | 10 | @issue_link = "https://github.com/#{tracker.project_id}/issues/#{number}" |
@@ -27,15 +27,15 @@ EOF | @@ -27,15 +27,15 @@ EOF | ||
27 | stub_request(:post, "https://#{tracker.username}%2Ftoken:#{tracker.api_token}@github.com/api/v2/json/issues/open/#{tracker.project_id}"). | 27 | stub_request(:post, "https://#{tracker.username}%2Ftoken:#{tracker.api_token}@github.com/api/v2/json/issues/open/#{tracker.project_id}"). |
28 | to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body ) | 28 | to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body ) |
29 | 29 | ||
30 | - err.app.issue_tracker.create_issue(err) | ||
31 | - err.reload | 30 | + problem.app.issue_tracker.create_issue(problem) |
31 | + problem.reload | ||
32 | 32 | ||
33 | requested = have_requested(:post, "https://#{tracker.username}%2Ftoken:#{tracker.api_token}@github.com/api/v2/json/issues/open/#{tracker.project_id}") | 33 | requested = have_requested(:post, "https://#{tracker.username}%2Ftoken:#{tracker.api_token}@github.com/api/v2/json/issues/open/#{tracker.project_id}") |
34 | WebMock.should requested.with(:headers => {'Content-Type' => 'application/x-www-form-urlencoded'}) | 34 | WebMock.should requested.with(:headers => {'Content-Type' => 'application/x-www-form-urlencoded'}) |
35 | WebMock.should requested.with(:body => /title=%5Bproduction%5D%5Bfoo%23bar%5D%20FooError%3A%20Too%20Much%20Bar/) | 35 | WebMock.should requested.with(:body => /title=%5Bproduction%5D%5Bfoo%23bar%5D%20FooError%3A%20Too%20Much%20Bar/) |
36 | WebMock.should requested.with(:body => /See%20this%20exception%20on%20Errbit/) | 36 | WebMock.should requested.with(:body => /See%20this%20exception%20on%20Errbit/) |
37 | 37 | ||
38 | - err.issue_link.should == @issue_link | 38 | + problem.issue_link.should == @issue_link |
39 | end | 39 | end |
40 | end | 40 | end |
41 | 41 |
spec/models/issue_trackers/lighthouse_tracker_spec.rb
1 | require 'spec_helper' | 1 | require 'spec_helper' |
2 | 2 | ||
3 | describe LighthouseTracker do | 3 | describe LighthouseTracker do |
4 | - it "should create an issue on Lighthouse with err params, and set issue link for err" do | 4 | + it "should create an issue on Lighthouse with problem params, and set issue link for problem" do |
5 | notice = Factory :notice | 5 | notice = Factory :notice |
6 | - tracker = Factory :lighthouse_tracker, :app => notice.err.app | ||
7 | - err = notice.err | 6 | + tracker = Factory :lighthouse_tracker, :app => notice.app |
7 | + problem = notice.problem | ||
8 | 8 | ||
9 | number = 5 | 9 | number = 5 |
10 | @issue_link = "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets/#{number}.xml" | 10 | @issue_link = "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets/#{number}.xml" |
@@ -12,16 +12,16 @@ describe LighthouseTracker do | @@ -12,16 +12,16 @@ describe LighthouseTracker do | ||
12 | stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml"). | 12 | stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml"). |
13 | to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body ) | 13 | to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body ) |
14 | 14 | ||
15 | - err.app.issue_tracker.create_issue(err) | ||
16 | - err.reload | 15 | + problem.app.issue_tracker.create_issue(problem) |
16 | + problem.reload | ||
17 | 17 | ||
18 | requested = have_requested(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml") | 18 | requested = have_requested(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml") |
19 | WebMock.should requested.with(:headers => {'X-Lighthousetoken' => tracker.api_token}) | 19 | WebMock.should requested.with(:headers => {'X-Lighthousetoken' => tracker.api_token}) |
20 | WebMock.should requested.with(:body => /<tag>errbit<\/tag>/) | 20 | WebMock.should requested.with(:body => /<tag>errbit<\/tag>/) |
21 | - WebMock.should requested.with(:body => /<title>\[#{ err.environment }\]\[#{err.where}\] #{err.message.to_s.truncate(100)}<\/title>/) | 21 | + WebMock.should requested.with(:body => /<title>\[#{ problem.environment }\]\[#{problem.where}\] #{problem.message.to_s.truncate(100)}<\/title>/) |
22 | WebMock.should requested.with(:body => /<body>.+<\/body>/m) | 22 | WebMock.should requested.with(:body => /<body>.+<\/body>/m) |
23 | 23 | ||
24 | - err.issue_link.should == @issue_link.sub(/\.xml$/, '') | 24 | + problem.issue_link.should == @issue_link.sub(/\.xml$/, '') |
25 | end | 25 | end |
26 | end | 26 | end |
27 | 27 |
spec/models/issue_trackers/mingle_tracker_spec.rb
1 | require 'spec_helper' | 1 | require 'spec_helper' |
2 | 2 | ||
3 | describe MingleTracker do | 3 | describe MingleTracker do |
4 | - it "should create an issue on Mingle with err params, and set issue link for err" do | 4 | + it "should create an issue on Mingle with problem params, and set issue link for problem" do |
5 | notice = Factory :notice | 5 | notice = Factory :notice |
6 | - tracker = Factory :mingle_tracker, :app => notice.err.app | ||
7 | - err = notice.err | 6 | + tracker = Factory :mingle_tracker, :app => notice.app |
7 | + problem = notice.problem | ||
8 | 8 | ||
9 | number = 5 | 9 | number = 5 |
10 | @issue_link = "#{tracker.account}/projects/#{tracker.project_id}/cards/#{number}.xml" | 10 | @issue_link = "#{tracker.account}/projects/#{tracker.project_id}/cards/#{number}.xml" |
@@ -13,8 +13,8 @@ describe MingleTracker do | @@ -13,8 +13,8 @@ describe MingleTracker do | ||
13 | stub_request(:post, "#{@basic_auth}/api/v1/projects/#{tracker.project_id}/cards.xml"). | 13 | stub_request(:post, "#{@basic_auth}/api/v1/projects/#{tracker.project_id}/cards.xml"). |
14 | to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body ) | 14 | to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body ) |
15 | 15 | ||
16 | - err.app.issue_tracker.create_issue(err) | ||
17 | - err.reload | 16 | + problem.app.issue_tracker.create_issue(problem) |
17 | + problem.reload | ||
18 | 18 | ||
19 | requested = have_requested(:post, "#{@basic_auth}/api/v1/projects/#{tracker.project_id}/cards.xml") | 19 | requested = have_requested(:post, "#{@basic_auth}/api/v1/projects/#{tracker.project_id}/cards.xml") |
20 | WebMock.should requested.with(:headers => {'Content-Type' => 'application/xml'}) | 20 | WebMock.should requested.with(:headers => {'Content-Type' => 'application/xml'}) |
@@ -22,7 +22,7 @@ describe MingleTracker do | @@ -22,7 +22,7 @@ describe MingleTracker do | ||
22 | WebMock.should requested.with(:body => /See this exception on Errbit/) | 22 | WebMock.should requested.with(:body => /See this exception on Errbit/) |
23 | WebMock.should requested.with(:body => /<card-type-name>Defect<\/card-type-name>/) | 23 | WebMock.should requested.with(:body => /<card-type-name>Defect<\/card-type-name>/) |
24 | 24 | ||
25 | - err.issue_link.should == @issue_link.sub(/\.xml$/, '') | 25 | + problem.issue_link.should == @issue_link.sub(/\.xml$/, '') |
26 | end | 26 | end |
27 | end | 27 | end |
28 | 28 |
spec/models/issue_trackers/pivotal_labs_tracker_spec.rb
1 | require 'spec_helper' | 1 | require 'spec_helper' |
2 | 2 | ||
3 | describe PivotalLabsTracker do | 3 | describe PivotalLabsTracker do |
4 | - it "should create an issue on Pivotal Tracker with err params, and set issue link for err" do | 4 | + it "should create an issue on Pivotal Tracker with problem params, and set issue link for problem" do |
5 | notice = Factory :notice | 5 | notice = Factory :notice |
6 | - tracker = Factory :pivotal_labs_tracker, :app => notice.err.app, :project_id => 10 | ||
7 | - err = notice.err | 6 | + tracker = Factory :pivotal_labs_tracker, :app => notice.app, :project_id => 10 |
7 | + problem = notice.problem | ||
8 | 8 | ||
9 | story_id = 5 | 9 | story_id = 5 |
10 | @issue_link = "https://www.pivotaltracker.com/story/show/#{story_id}" | 10 | @issue_link = "https://www.pivotaltracker.com/story/show/#{story_id}" |
@@ -15,16 +15,16 @@ describe PivotalLabsTracker do | @@ -15,16 +15,16 @@ describe PivotalLabsTracker do | ||
15 | stub_request(:post, "https://www.pivotaltracker.com/services/v3/projects/#{tracker.project_id}/stories"). | 15 | stub_request(:post, "https://www.pivotaltracker.com/services/v3/projects/#{tracker.project_id}/stories"). |
16 | to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => story_body ) | 16 | to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => story_body ) |
17 | 17 | ||
18 | - err.app.issue_tracker.create_issue(err) | ||
19 | - err.reload | 18 | + problem.app.issue_tracker.create_issue(problem) |
19 | + problem.reload | ||
20 | 20 | ||
21 | requested = have_requested(:post, "https://www.pivotaltracker.com/services/v3/projects/#{tracker.project_id}/stories") | 21 | requested = have_requested(:post, "https://www.pivotaltracker.com/services/v3/projects/#{tracker.project_id}/stories") |
22 | WebMock.should requested.with(:headers => {'X-Trackertoken' => tracker.api_token}) | 22 | WebMock.should requested.with(:headers => {'X-Trackertoken' => tracker.api_token}) |
23 | WebMock.should requested.with(:body => /See this exception on Errbit/) | 23 | WebMock.should requested.with(:body => /See this exception on Errbit/) |
24 | - WebMock.should requested.with(:body => /<name>\[#{ err.environment }\]\[#{err.where}\] #{err.message.to_s.truncate(100)}<\/name>/) | 24 | + WebMock.should requested.with(:body => /<name>\[#{ problem.environment }\]\[#{problem.where}\] #{problem.message.to_s.truncate(100)}<\/name>/) |
25 | WebMock.should requested.with(:body => /<description>.+<\/description>/m) | 25 | WebMock.should requested.with(:body => /<description>.+<\/description>/m) |
26 | 26 | ||
27 | - err.issue_link.should == @issue_link | 27 | + problem.issue_link.should == @issue_link |
28 | end | 28 | end |
29 | end | 29 | end |
30 | 30 |
spec/models/issue_trackers/redmine_tracker_spec.rb
1 | require 'spec_helper' | 1 | require 'spec_helper' |
2 | 2 | ||
3 | describe RedmineTracker do | 3 | describe RedmineTracker do |
4 | - it "should create an issue on Redmine with err params, and set issue link for err" do | 4 | + it "should create an issue on Redmine with problem params, and set issue link for problem" do |
5 | notice = Factory(:notice) | 5 | notice = Factory(:notice) |
6 | - tracker = Factory(:redmine_tracker, :app => notice.err.app, :project_id => 10) | ||
7 | - err = notice.err | 6 | + tracker = Factory(:redmine_tracker, :app => notice.app, :project_id => 10) |
7 | + problem = notice.problem | ||
8 | number = 5 | 8 | number = 5 |
9 | @issue_link = "#{tracker.account}/issues/#{number}.xml?project_id=#{tracker.project_id}" | 9 | @issue_link = "#{tracker.account}/issues/#{number}.xml?project_id=#{tracker.project_id}" |
10 | body = "<issue><subject>my subject</subject><id>#{number}</id></issue>" | 10 | body = "<issue><subject>my subject</subject><id>#{number}</id></issue>" |
11 | stub_request(:post, "#{tracker.account}/issues.xml"). | 11 | stub_request(:post, "#{tracker.account}/issues.xml"). |
12 | to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body ) | 12 | to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body ) |
13 | 13 | ||
14 | - err.app.issue_tracker.create_issue(err) | ||
15 | - err.reload | 14 | + problem.app.issue_tracker.create_issue(problem) |
15 | + problem.reload | ||
16 | 16 | ||
17 | requested = have_requested(:post, "#{tracker.account}/issues.xml") | 17 | requested = have_requested(:post, "#{tracker.account}/issues.xml") |
18 | WebMock.should requested.with(:headers => {'X-Redmine-API-Key' => tracker.api_token}) | 18 | WebMock.should requested.with(:headers => {'X-Redmine-API-Key' => tracker.api_token}) |
19 | WebMock.should requested.with(:body => /<project-id>#{tracker.project_id}<\/project-id>/) | 19 | WebMock.should requested.with(:body => /<project-id>#{tracker.project_id}<\/project-id>/) |
20 | - WebMock.should requested.with(:body => /<subject>\[#{ err.environment }\]\[#{err.where}\] #{err.message.to_s.truncate(100)}<\/subject>/) | 20 | + WebMock.should requested.with(:body => /<subject>\[#{ problem.environment }\]\[#{problem.where}\] #{problem.message.to_s.truncate(100)}<\/subject>/) |
21 | WebMock.should requested.with(:body => /<description>.+<\/description>/m) | 21 | WebMock.should requested.with(:body => /<description>.+<\/description>/m) |
22 | 22 | ||
23 | - err.issue_link.should == @issue_link.sub(/\.xml/, '') | 23 | + problem.issue_link.should == @issue_link.sub(/\.xml/, '') |
24 | end | 24 | end |
25 | 25 | ||
26 | it "should generate a url where a file with line number can be viewed" do | 26 | it "should generate a url where a file with line number can be viewed" do |
spec/models/notice_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper' | @@ -2,6 +2,7 @@ require 'spec_helper' | ||
2 | 2 | ||
3 | describe Notice do | 3 | describe Notice do |
4 | 4 | ||
5 | + | ||
5 | context 'validations' do | 6 | context 'validations' do |
6 | it 'requires a backtrace' do | 7 | it 'requires a backtrace' do |
7 | notice = Factory.build(:notice, :backtrace => nil) | 8 | notice = Factory.build(:notice, :backtrace => nil) |
@@ -22,6 +23,7 @@ describe Notice do | @@ -22,6 +23,7 @@ describe Notice do | ||
22 | end | 23 | end |
23 | end | 24 | end |
24 | 25 | ||
26 | + | ||
25 | context '.in_app_backtrace_line?' do | 27 | context '.in_app_backtrace_line?' do |
26 | let(:backtrace) do [{ | 28 | let(:backtrace) do [{ |
27 | 'number' => rand(999), | 29 | 'number' => rand(999), |
@@ -51,93 +53,6 @@ describe Notice do | @@ -51,93 +53,6 @@ describe Notice do | ||
51 | end | 53 | end |
52 | end | 54 | end |
53 | 55 | ||
54 | - context '#from_xml' do | ||
55 | - before do | ||
56 | - @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read | ||
57 | - @app = Factory(:app, :api_key => 'APIKEY') | ||
58 | - Digest::MD5.stub(:hexdigest).and_return('fingerprintdigest') | ||
59 | - end | ||
60 | - | ||
61 | - it 'finds the correct app' do | ||
62 | - @notice = Notice.from_xml(@xml) | ||
63 | - @notice.err.app.should == @app | ||
64 | - end | ||
65 | - | ||
66 | - it 'finds the correct err for the notice' do | ||
67 | - Err.should_receive(:for).with({ | ||
68 | - :app => @app, | ||
69 | - :klass => 'HoptoadTestingException', | ||
70 | - :component => 'application', | ||
71 | - :action => 'verify', | ||
72 | - :environment => 'development', | ||
73 | - :fingerprint => 'fingerprintdigest' | ||
74 | - }).and_return(err = Factory(:err)) | ||
75 | - err.notices.stub(:create!) | ||
76 | - @notice = Notice.from_xml(@xml) | ||
77 | - end | ||
78 | - | ||
79 | - it 'marks the err as unresolve if it was previously resolved' do | ||
80 | - Err.should_receive(:for).with({ | ||
81 | - :app => @app, | ||
82 | - :klass => 'HoptoadTestingException', | ||
83 | - :component => 'application', | ||
84 | - :action => 'verify', | ||
85 | - :environment => 'development', | ||
86 | - :fingerprint => 'fingerprintdigest' | ||
87 | - }).and_return(err = Factory(:err, :resolved => true)) | ||
88 | - err.should be_resolved | ||
89 | - @notice = Notice.from_xml(@xml) | ||
90 | - @notice.err.should == err | ||
91 | - @notice.err.should_not be_resolved | ||
92 | - end | ||
93 | - | ||
94 | - it 'should create a new notice' do | ||
95 | - @notice = Notice.from_xml(@xml) | ||
96 | - @notice.should be_persisted | ||
97 | - end | ||
98 | - | ||
99 | - it 'assigns an err to the notice' do | ||
100 | - @notice = Notice.from_xml(@xml) | ||
101 | - @notice.err.should be_a(Err) | ||
102 | - end | ||
103 | - | ||
104 | - it 'captures the err message' do | ||
105 | - @notice = Notice.from_xml(@xml) | ||
106 | - @notice.message.should == 'HoptoadTestingException: Testing hoptoad via "rake hoptoad:test". If you can see this, it works.' | ||
107 | - end | ||
108 | - | ||
109 | - it 'captures the backtrace' do | ||
110 | - @notice = Notice.from_xml(@xml) | ||
111 | - @notice.backtrace.size.should == 73 | ||
112 | - @notice.backtrace.last['file'].should == '[GEM_ROOT]/bin/rake' | ||
113 | - end | ||
114 | - | ||
115 | - it 'captures the server_environment' do | ||
116 | - @notice = Notice.from_xml(@xml) | ||
117 | - @notice.server_environment['environment-name'].should == 'development' | ||
118 | - end | ||
119 | - | ||
120 | - it 'captures the request' do | ||
121 | - @notice = Notice.from_xml(@xml) | ||
122 | - @notice.request['url'].should == 'http://example.org/verify' | ||
123 | - @notice.request['params']['controller'].should == 'application' | ||
124 | - end | ||
125 | - | ||
126 | - it 'captures the notifier' do | ||
127 | - @notice = Notice.from_xml(@xml) | ||
128 | - @notice.notifier['name'].should == 'Hoptoad Notifier' | ||
129 | - end | ||
130 | - | ||
131 | - it "should handle params without 'request' section" do | ||
132 | - @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice_without_request_section.xml').read | ||
133 | - lambda { Notice.from_xml(@xml) }.should_not raise_error | ||
134 | - end | ||
135 | - | ||
136 | - it "should raise ApiVersionError" do | ||
137 | - @xml = Rails.root.join('spec', 'fixtures', 'hoptoad_test_notice_with_wrong_version.xml').read | ||
138 | - expect { Notice.from_xml(@xml) }.to raise_error(Hoptoad::V2::ApiVersionError) | ||
139 | - end | ||
140 | - end | ||
141 | 56 | ||
142 | describe "key sanitization" do | 57 | describe "key sanitization" do |
143 | before do | 58 | before do |
@@ -153,6 +68,7 @@ describe Notice do | @@ -153,6 +68,7 @@ describe Notice do | ||
153 | end | 68 | end |
154 | end | 69 | end |
155 | 70 | ||
71 | + | ||
156 | describe "user agent" do | 72 | describe "user agent" do |
157 | it "should be parsed and human-readable" do | 73 | it "should be parsed and human-readable" do |
158 | notice = Factory.build(:notice, :request => {'cgi-data' => {'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16'}}) | 74 | notice = Factory.build(:notice, :request => {'cgi-data' => {'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16'}}) |
@@ -166,13 +82,14 @@ describe Notice do | @@ -166,13 +82,14 @@ describe Notice do | ||
166 | end | 82 | end |
167 | end | 83 | end |
168 | 84 | ||
85 | + | ||
169 | describe "email notifications (configured individually for each app)" do | 86 | describe "email notifications (configured individually for each app)" do |
170 | custom_thresholds = [2, 4, 8, 16, 32, 64] | 87 | custom_thresholds = [2, 4, 8, 16, 32, 64] |
171 | 88 | ||
172 | before do | 89 | before do |
173 | Errbit::Config.per_app_email_at_notices = true | 90 | Errbit::Config.per_app_email_at_notices = true |
174 | @app = Factory(:app_with_watcher, :email_at_notices => custom_thresholds) | 91 | @app = Factory(:app_with_watcher, :email_at_notices => custom_thresholds) |
175 | - @err = Factory(:err, :app => @app) | 92 | + @err = Factory(:err, :problem => Factory(:problem, :app => @app)) |
176 | end | 93 | end |
177 | 94 | ||
178 | after do | 95 | after do |
@@ -181,7 +98,7 @@ describe Notice do | @@ -181,7 +98,7 @@ describe Notice do | ||
181 | 98 | ||
182 | custom_thresholds.each do |threshold| | 99 | custom_thresholds.each do |threshold| |
183 | it "sends an email notification after #{threshold} notice(s)" do | 100 | it "sends an email notification after #{threshold} notice(s)" do |
184 | - @err.notices.stub(:count).and_return(threshold) | 101 | + @err.problem.stub(:notices_count).and_return(threshold) |
185 | Mailer.should_receive(:err_notification). | 102 | Mailer.should_receive(:err_notification). |
186 | and_return(mock('email', :deliver => true)) | 103 | and_return(mock('email', :deliver => true)) |
187 | Factory(:notice, :err => @err) | 104 | Factory(:notice, :err => @err) |
@@ -189,5 +106,6 @@ describe Notice do | @@ -189,5 +106,6 @@ describe Notice do | ||
189 | end | 106 | end |
190 | end | 107 | end |
191 | 108 | ||
109 | + | ||
192 | end | 110 | end |
193 | 111 |
@@ -0,0 +1,162 @@ | @@ -0,0 +1,162 @@ | ||
1 | +require 'spec_helper' | ||
2 | + | ||
3 | +describe Problem do | ||
4 | + | ||
5 | + | ||
6 | + context '#last_notice_at' do | ||
7 | + it "returns the created_at timestamp of the latest notice" do | ||
8 | + err = Factory(:err) | ||
9 | + problem = err.problem | ||
10 | + problem.should_not be_nil | ||
11 | + | ||
12 | + problem.last_notice_at.should be_nil | ||
13 | + | ||
14 | + notice1 = Factory(:notice, :err => err) | ||
15 | + problem.last_notice_at.should == notice1.created_at | ||
16 | + | ||
17 | + notice2 = Factory(:notice, :err => err) | ||
18 | + problem.last_notice_at.should == notice2.created_at | ||
19 | + end | ||
20 | + end | ||
21 | + | ||
22 | + | ||
23 | + context '#message' do | ||
24 | + it "adding a notice caches its message" do | ||
25 | + err = Factory(:err) | ||
26 | + problem = err.problem | ||
27 | + lambda { | ||
28 | + Factory(:notice, :err => err, :message => 'ERR 1') | ||
29 | + }.should change(problem, :message).from(nil).to('ERR 1') | ||
30 | + end | ||
31 | + end | ||
32 | + | ||
33 | + | ||
34 | + context 'being created' do | ||
35 | + context 'when the app has err notifications set to false' do | ||
36 | + it 'should not send an email notification' do | ||
37 | + app = Factory(:app_with_watcher, :notify_on_errs => false) | ||
38 | + Mailer.should_not_receive(:err_notification) | ||
39 | + Factory(:problem, :app => app) | ||
40 | + end | ||
41 | + end | ||
42 | + end | ||
43 | + | ||
44 | + | ||
45 | + context "#resolved?" do | ||
46 | + it "should start out as unresolved" do | ||
47 | + problem = Problem.new | ||
48 | + problem.should_not be_resolved | ||
49 | + problem.should be_unresolved | ||
50 | + end | ||
51 | + | ||
52 | + it "should be able to be resolved" do | ||
53 | + problem = Factory(:problem) | ||
54 | + problem.should_not be_resolved | ||
55 | + problem.resolve! | ||
56 | + problem.reload.should be_resolved | ||
57 | + end | ||
58 | + end | ||
59 | + | ||
60 | + | ||
61 | + context "resolve!" do | ||
62 | + it "marks the problem as resolved" do | ||
63 | + problem = Factory(:problem) | ||
64 | + problem.should_not be_resolved | ||
65 | + problem.resolve! | ||
66 | + problem.should be_resolved | ||
67 | + end | ||
68 | + | ||
69 | + it "should throw an err if it's not successful" do | ||
70 | + problem = Factory(:problem) | ||
71 | + problem.should_not be_resolved | ||
72 | + problem.stub!(:valid?).and_return(false) | ||
73 | + problem.should_not be_valid | ||
74 | + lambda { | ||
75 | + problem.resolve! | ||
76 | + }.should raise_error(Mongoid::Errors::Validations) | ||
77 | + end | ||
78 | + end | ||
79 | + | ||
80 | + | ||
81 | + context ".merge!" do | ||
82 | + it "collects the Errs from several problems into one and deletes the other problems" do | ||
83 | + problem1 = Factory(:err).problem | ||
84 | + problem2 = Factory(:err).problem | ||
85 | + problem1.errs.length.should == 1 | ||
86 | + problem2.errs.length.should == 1 | ||
87 | + | ||
88 | + lambda { | ||
89 | + merged_problem = Problem.merge!(problem1, problem2) | ||
90 | + merged_problem.reload.errs.length.should == 2 | ||
91 | + }.should change(Problem, :count).by(-1) | ||
92 | + end | ||
93 | + end | ||
94 | + | ||
95 | + | ||
96 | + context "#unmerge!" do | ||
97 | + it "creates a separate problem for each err" do | ||
98 | + problem1 = Factory(:notice).problem | ||
99 | + problem2 = Factory(:notice).problem | ||
100 | + merged_problem = Problem.merge!(problem1, problem2) | ||
101 | + merged_problem.errs.length.should == 2 | ||
102 | + | ||
103 | + lambda { | ||
104 | + problems = merged_problem.unmerge! | ||
105 | + problems.length.should == 2 | ||
106 | + merged_problem.errs(true).length.should == 1 | ||
107 | + }.should change(Problem, :count).by(1) | ||
108 | + end | ||
109 | + end | ||
110 | + | ||
111 | + | ||
112 | + context "Scopes" do | ||
113 | + context "resolved" do | ||
114 | + it 'only finds resolved Problems' do | ||
115 | + resolved = Factory(:problem, :resolved => true) | ||
116 | + unresolved = Factory(:problem, :resolved => false) | ||
117 | + Problem.resolved.all.should include(resolved) | ||
118 | + Problem.resolved.all.should_not include(unresolved) | ||
119 | + end | ||
120 | + end | ||
121 | + | ||
122 | + context "unresolved" do | ||
123 | + it 'only finds unresolved Problems' do | ||
124 | + resolved = Factory(:problem, :resolved => true) | ||
125 | + unresolved = Factory(:problem, :resolved => false) | ||
126 | + Problem.unresolved.all.should_not include(resolved) | ||
127 | + Problem.unresolved.all.should include(unresolved) | ||
128 | + end | ||
129 | + end | ||
130 | + end | ||
131 | + | ||
132 | + | ||
133 | + context "notice counter cache" do | ||
134 | + | ||
135 | + before do | ||
136 | + @app = Factory(:app) | ||
137 | + @problem = Factory(:problem, :app => @app) | ||
138 | + @err = Factory(:err, :problem => @problem) | ||
139 | + end | ||
140 | + | ||
141 | + it "#notices_count returns 0 by default" do | ||
142 | + @problem.notices_count.should == 0 | ||
143 | + end | ||
144 | + | ||
145 | + it "adding a notice increases #notices_count by 1" do | ||
146 | + lambda { | ||
147 | + Factory(:notice, :err => @err, :message => 'ERR 1') | ||
148 | + }.should change(@problem, :notices_count).from(0).to(1) | ||
149 | + end | ||
150 | + | ||
151 | + it "removing a notice decreases #notices_count by 1" do | ||
152 | + notice1 = Factory(:notice, :err => @err, :message => 'ERR 1') | ||
153 | + lambda { | ||
154 | + @err.notices.first.destroy | ||
155 | + @problem.reload | ||
156 | + }.should change(@problem, :notices_count).from(1).to(0) | ||
157 | + end | ||
158 | + end | ||
159 | + | ||
160 | + | ||
161 | +end | ||
162 | + |
spec/models/user_spec.rb
spec/models/watcher_spec.rb
spec/spec_helper.rb
spec/support/macros.rb
spec/views/errs/show.html.haml_spec.rb
@@ -3,11 +3,12 @@ require 'spec_helper' | @@ -3,11 +3,12 @@ require 'spec_helper' | ||
3 | describe "errs/show.html.haml" do | 3 | describe "errs/show.html.haml" do |
4 | before do | 4 | before do |
5 | err = Factory(:err) | 5 | err = Factory(:err) |
6 | + problem = err.problem | ||
6 | comment = Factory(:comment) | 7 | comment = Factory(:comment) |
7 | - assign :err, err | 8 | + assign :problem, problem |
8 | assign :comment, comment | 9 | assign :comment, comment |
9 | - assign :app, err.app | ||
10 | - assign :notices, err.notices.ordered.paginate(:page => 1, :per_page => 1) | 10 | + assign :app, problem.app |
11 | + assign :notices, err.notices.paginate(:page => 1, :per_page => 1) | ||
11 | assign :notice, err.notices.first | 12 | assign :notice, err.notices.first |
12 | end | 13 | end |
13 | 14 | ||
@@ -44,9 +45,9 @@ describe "errs/show.html.haml" do | @@ -44,9 +45,9 @@ describe "errs/show.html.haml" do | ||
44 | end | 45 | end |
45 | 46 | ||
46 | it 'should display comments and new comment form when no issue tracker' do | 47 | it 'should display comments and new comment form when no issue tracker' do |
47 | - err = Factory(:err_with_comments) | ||
48 | - assign :err, err | ||
49 | - assign :app, err.app | 48 | + problem = Factory(:problem_with_comments) |
49 | + assign :problem, problem | ||
50 | + assign :app, problem.app | ||
50 | render | 51 | render |
51 | comments_section = String.new(view.instance_variable_get(:@_content_for)[:comments]) | 52 | comments_section = String.new(view.instance_variable_get(:@_content_for)[:comments]) |
52 | comments_section.should =~ /Test comment/ | 53 | comments_section.should =~ /Test comment/ |
@@ -54,20 +55,22 @@ describe "errs/show.html.haml" do | @@ -54,20 +55,22 @@ describe "errs/show.html.haml" do | ||
54 | end | 55 | end |
55 | 56 | ||
56 | context "with issue tracker" do | 57 | context "with issue tracker" do |
57 | - def with_issue_tracker(err) | ||
58 | - err.app.issue_tracker = PivotalLabsTracker.new :api_token => "token token token", :project_id => "1234" | ||
59 | - assign :err, err | ||
60 | - assign :app, err.app | 58 | + def with_issue_tracker(problem) |
59 | + problem.app.issue_tracker = PivotalLabsTracker.new :api_token => "token token token", :project_id => "1234" | ||
60 | + assign :problem, problem | ||
61 | + assign :app, problem.app | ||
61 | end | 62 | end |
62 | 63 | ||
63 | it 'should not display the comments section' do | 64 | it 'should not display the comments section' do |
64 | - with_issue_tracker(Factory(:err)) | 65 | + problem = Factory(:problem) |
66 | + with_issue_tracker(problem) | ||
65 | render | 67 | render |
66 | view.instance_variable_get(:@_content_for)[:comments].should be_blank | 68 | view.instance_variable_get(:@_content_for)[:comments].should be_blank |
67 | end | 69 | end |
68 | 70 | ||
69 | it 'should display existing comments' do | 71 | it 'should display existing comments' do |
70 | - with_issue_tracker(Factory(:err_with_comments)) | 72 | + problem = Factory(:problem_with_comments) |
73 | + with_issue_tracker(problem) | ||
71 | render | 74 | render |
72 | comments_section = String.new(view.instance_variable_get(:@_content_for)[:comments]) | 75 | comments_section = String.new(view.instance_variable_get(:@_content_for)[:comments]) |
73 | comments_section.should =~ /Test comment/ | 76 | comments_section.should =~ /Test comment/ |