Commit 740b7309749a19d69b0098f863c4ca45820f43a8
Exists in
master
and in
1 other branch
Merge pull request #917 from stevecrozz/mongoid5
Mongoid5 and performance improvements
Showing
53 changed files
with
871 additions
and
754 deletions
Show diff stats
.env.default
@@ -15,6 +15,8 @@ ERRBIT_PER_APP_EMAIL_AT_NOTICES=false | @@ -15,6 +15,8 @@ ERRBIT_PER_APP_EMAIL_AT_NOTICES=false | ||
15 | ERRBIT_NOTIFY_AT_NOTICES='[0]' | 15 | ERRBIT_NOTIFY_AT_NOTICES='[0]' |
16 | ERRBIT_PER_APP_NOTIFY_AT_NOTICES=false | 16 | ERRBIT_PER_APP_NOTIFY_AT_NOTICES=false |
17 | MONGO_URL='mongodb://localhost' | 17 | MONGO_URL='mongodb://localhost' |
18 | +ERRBIT_LOG_LEVEL=info | ||
19 | +ERRBIT_LOG_LOCATION=STDOUT | ||
18 | GITHUB_URL='https://github.com' | 20 | GITHUB_URL='https://github.com' |
19 | GITHUB_AUTHENTICATION=true | 21 | GITHUB_AUTHENTICATION=true |
20 | GITHUB_ACCESS_SCOPE='[repo]' | 22 | GITHUB_ACCESS_SCOPE='[repo]' |
.travis.yml
@@ -9,8 +9,12 @@ env: | @@ -9,8 +9,12 @@ env: | ||
9 | - RAILS_ENV=test COVERAGE=true JRUBY_OPTS=--debug | 9 | - RAILS_ENV=test COVERAGE=true JRUBY_OPTS=--debug |
10 | sudo: false | 10 | sudo: false |
11 | cache: bundler | 11 | cache: bundler |
12 | -services: mongodb | ||
13 | -before_script: bundle exec rake errbit:bootstrap | 12 | +before_script: |
13 | + - wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.6.10.tgz -O /tmp/mongodb.tgz | ||
14 | + - tar -xvf /tmp/mongodb.tgz | ||
15 | + - mkdir /tmp/data | ||
16 | + - ${PWD}/mongodb-linux-x86_64-2.6.10/bin/mongod --dbpath /tmp/data --bind_ip 127.0.0.1 --auth &> /dev/null & | ||
17 | + - bundle exec rake errbit:bootstrap | ||
14 | script: bundle exec rspec | 18 | script: bundle exec rspec |
15 | matrix: | 19 | matrix: |
16 | allow_failures: | 20 | allow_failures: |
Gemfile
@@ -8,10 +8,8 @@ gem 'actionmailer', RAILS_VERSION | @@ -8,10 +8,8 @@ gem 'actionmailer', RAILS_VERSION | ||
8 | gem 'actionpack', RAILS_VERSION | 8 | gem 'actionpack', RAILS_VERSION |
9 | gem 'railties', RAILS_VERSION | 9 | gem 'railties', RAILS_VERSION |
10 | 10 | ||
11 | -gem 'moped', '~> 2.0.2' | ||
12 | -gem 'mongoid', '~> 4.0.0' | 11 | +gem 'mongoid', '5.0.0.beta' |
13 | 12 | ||
14 | -gem 'mongoid_rails_migrations' | ||
15 | gem 'devise' | 13 | gem 'devise' |
16 | gem 'haml' | 14 | gem 'haml' |
17 | gem 'htmlentities' | 15 | gem 'htmlentities' |
@@ -78,7 +76,7 @@ group :development do | @@ -78,7 +76,7 @@ group :development do | ||
78 | end | 76 | end |
79 | 77 | ||
80 | group :test do | 78 | group :test do |
81 | - gem 'rspec' | 79 | + gem 'rspec', '~> 3.3' |
82 | gem 'rspec-rails', '~> 3.0', require: false | 80 | gem 'rspec-rails', '~> 3.0', require: false |
83 | gem 'rspec-activemodel-mocks' | 81 | gem 'rspec-activemodel-mocks' |
84 | gem 'rspec-its' | 82 | gem 'rspec-its' |
@@ -87,7 +85,6 @@ group :test do | @@ -87,7 +85,6 @@ group :test do | ||
87 | gem 'capybara' | 85 | gem 'capybara' |
88 | gem 'poltergeist' | 86 | gem 'poltergeist' |
89 | gem 'launchy' | 87 | gem 'launchy' |
90 | - gem 'database_cleaner' | ||
91 | gem 'email_spec' | 88 | gem 'email_spec' |
92 | gem 'timecop' | 89 | gem 'timecop' |
93 | gem 'test-unit', require: 'test/unit' | 90 | gem 'test-unit', require: 'test/unit' |
Gemfile.lock
@@ -44,8 +44,8 @@ GEM | @@ -44,8 +44,8 @@ GEM | ||
44 | rack (>= 0.9.0) | 44 | rack (>= 0.9.0) |
45 | binding_of_caller (0.7.2) | 45 | binding_of_caller (0.7.2) |
46 | debug_inspector (>= 0.0.1) | 46 | debug_inspector (>= 0.0.1) |
47 | - bson (3.1.1) | ||
48 | - bson (3.1.1-java) | 47 | + bson (3.2.0) |
48 | + bson (3.2.0-java) | ||
49 | builder (3.2.2) | 49 | builder (3.2.2) |
50 | byebug (4.0.5) | 50 | byebug (4.0.5) |
51 | columnize (= 0.9.0) | 51 | columnize (= 0.9.0) |
@@ -81,7 +81,6 @@ GEM | @@ -81,7 +81,6 @@ GEM | ||
81 | coffee-script-source (1.9.1.1) | 81 | coffee-script-source (1.9.1.1) |
82 | colorize (0.7.7) | 82 | colorize (0.7.7) |
83 | columnize (0.9.0) | 83 | columnize (0.9.0) |
84 | - connection_pool (2.2.0) | ||
85 | coveralls (0.8.2) | 84 | coveralls (0.8.2) |
86 | json (~> 1.8) | 85 | json (~> 1.8) |
87 | rest-client (>= 1.6.8, < 2) | 86 | rest-client (>= 1.6.8, < 2) |
@@ -90,7 +89,6 @@ GEM | @@ -90,7 +89,6 @@ GEM | ||
90 | thor (~> 0.19.1) | 89 | thor (~> 0.19.1) |
91 | css_parser (1.3.6) | 90 | css_parser (1.3.6) |
92 | addressable | 91 | addressable |
93 | - database_cleaner (1.4.1) | ||
94 | debug_inspector (0.0.2) | 92 | debug_inspector (0.0.2) |
95 | decent_exposure (2.3.2) | 93 | decent_exposure (2.3.2) |
96 | devise (3.5.1) | 94 | devise (3.5.1) |
@@ -175,25 +173,18 @@ GEM | @@ -175,25 +173,18 @@ GEM | ||
175 | mimemagic (0.3.0) | 173 | mimemagic (0.3.0) |
176 | mini_portile (0.6.2) | 174 | mini_portile (0.6.2) |
177 | minitest (5.7.0) | 175 | minitest (5.7.0) |
178 | - mongoid (4.0.2) | 176 | + mongo (2.1.0.beta) |
177 | + bson (~> 3.0) | ||
178 | + mongoid (5.0.0.beta) | ||
179 | activemodel (~> 4.0) | 179 | activemodel (~> 4.0) |
180 | - moped (~> 2.0.0) | 180 | + mongo (= 2.1.0.beta) |
181 | origin (~> 2.1) | 181 | origin (~> 2.1) |
182 | tzinfo (>= 0.3.37) | 182 | tzinfo (>= 0.3.37) |
183 | - mongoid-rspec (2.2.0) | ||
184 | - mongoid (~> 4.0.0) | 183 | + mongoid-rspec (1.10.0) |
184 | + mongoid (>= 3.0.1) | ||
185 | rake | 185 | rake |
186 | - rspec (~> 3.1) | ||
187 | - mongoid_rails_migrations (1.0.1) | ||
188 | - activesupport (>= 3.2.0) | ||
189 | - bundler (>= 1.0.0) | ||
190 | - rails (>= 3.2.0) | ||
191 | - railties (>= 3.2.0) | ||
192 | - moped (2.0.6) | ||
193 | - bson (~> 3.0) | ||
194 | - connection_pool (~> 2.0) | ||
195 | - optionable (~> 0.2.0) | ||
196 | - multi_json (1.11.1) | 186 | + rspec (>= 2.14) |
187 | + multi_json (1.11.2) | ||
197 | multi_xml (0.5.5) | 188 | multi_xml (0.5.5) |
198 | multipart-post (2.0.0) | 189 | multipart-post (2.0.0) |
199 | net-scp (1.2.1) | 190 | net-scp (1.2.1) |
@@ -363,7 +354,7 @@ GEM | @@ -363,7 +354,7 @@ GEM | ||
363 | thread_safe (0.3.5-java) | 354 | thread_safe (0.3.5-java) |
364 | tilt (1.4.1) | 355 | tilt (1.4.1) |
365 | timecop (0.7.4) | 356 | timecop (0.7.4) |
366 | - tins (1.5.4) | 357 | + tins (1.6.0) |
367 | tzinfo (1.2.2) | 358 | tzinfo (1.2.2) |
368 | thread_safe (~> 0.1) | 359 | thread_safe (~> 0.1) |
369 | uglifier (2.7.1) | 360 | uglifier (2.7.1) |
@@ -410,7 +401,6 @@ DEPENDENCIES | @@ -410,7 +401,6 @@ DEPENDENCIES | ||
410 | capybara | 401 | capybara |
411 | coffee-rails | 402 | coffee-rails |
412 | coveralls | 403 | coveralls |
413 | - database_cleaner | ||
414 | decent_exposure | 404 | decent_exposure |
415 | devise | 405 | devise |
416 | dotenv-rails | 406 | dotenv-rails |
@@ -431,10 +421,8 @@ DEPENDENCIES | @@ -431,10 +421,8 @@ DEPENDENCIES | ||
431 | kaminari (>= 0.14.1) | 421 | kaminari (>= 0.14.1) |
432 | launchy | 422 | launchy |
433 | meta_request | 423 | meta_request |
434 | - mongoid (~> 4.0.0) | 424 | + mongoid (= 5.0.0.beta) |
435 | mongoid-rspec | 425 | mongoid-rspec |
436 | - mongoid_rails_migrations | ||
437 | - moped (~> 2.0.2) | ||
438 | omniauth-github | 426 | omniauth-github |
439 | pjax_rails | 427 | pjax_rails |
440 | poltergeist | 428 | poltergeist |
@@ -447,7 +435,7 @@ DEPENDENCIES | @@ -447,7 +435,7 @@ DEPENDENCIES | ||
447 | rails_autolink | 435 | rails_autolink |
448 | railties (~> 4.1.11) | 436 | railties (~> 4.1.11) |
449 | ri_cal | 437 | ri_cal |
450 | - rspec | 438 | + rspec (~> 3.3) |
451 | rspec-activemodel-mocks | 439 | rspec-activemodel-mocks |
452 | rspec-its | 440 | rspec-its |
453 | rspec-rails (~> 3.0) | 441 | rspec-rails (~> 3.0) |
README.md
@@ -71,7 +71,7 @@ updates and notifications. | @@ -71,7 +71,7 @@ updates and notifications. | ||
71 | The list of requirements to install Errbit are: | 71 | The list of requirements to install Errbit are: |
72 | 72 | ||
73 | * Ruby 2.1.0 or higher | 73 | * Ruby 2.1.0 or higher |
74 | -* MongoDB 2.2.0 or higher | 74 | +* MongoDB 2.6.0 or higher |
75 | 75 | ||
76 | Installation | 76 | Installation |
77 | ------------ | 77 | ------------ |
@@ -188,6 +188,8 @@ When upgrading Errbit, please run: | @@ -188,6 +188,8 @@ When upgrading Errbit, please run: | ||
188 | git pull origin master # assuming origin is the github.com/errbit/errbit repo | 188 | git pull origin master # assuming origin is the github.com/errbit/errbit repo |
189 | bundle install | 189 | bundle install |
190 | rake db:migrate | 190 | rake db:migrate |
191 | +rake db:mongoid:create_indexes | ||
192 | +rake db:mongoid:remove_undefined_indexes | ||
191 | rake assets:precompile | 193 | rake assets:precompile |
192 | ``` | 194 | ``` |
193 | 195 |
app/controllers/notices_controller.rb
@@ -2,7 +2,8 @@ class NoticesController < ApplicationController | @@ -2,7 +2,8 @@ class NoticesController < ApplicationController | ||
2 | 2 | ||
3 | class ParamsError < StandardError; end | 3 | class ParamsError < StandardError; end |
4 | 4 | ||
5 | - skip_before_action :authenticate_user!, :only => :create | 5 | + skip_before_action :authenticate_user!, only: :create |
6 | + skip_before_filter :verify_authenticity_token, only: :create | ||
6 | 7 | ||
7 | rescue_from ParamsError, :with => :bad_params | 8 | rescue_from ParamsError, :with => :bad_params |
8 | 9 |
app/controllers/problems_controller.rb
@@ -22,7 +22,7 @@ class ProblemsController < ApplicationController | @@ -22,7 +22,7 @@ class ProblemsController < ApplicationController | ||
22 | } | 22 | } |
23 | 23 | ||
24 | expose(:problem) { | 24 | expose(:problem) { |
25 | - app.problems.find(params[:id]) | 25 | + ProblemDecorator.new app.problems.find(params[:id]) |
26 | } | 26 | } |
27 | 27 | ||
28 | expose(:all_errs) { | 28 | expose(:all_errs) { |
@@ -50,8 +50,9 @@ class ProblemsController < ApplicationController | @@ -50,8 +50,9 @@ class ProblemsController < ApplicationController | ||
50 | def index; end | 50 | def index; end |
51 | 51 | ||
52 | def show | 52 | def show |
53 | - @notices = problem.notices.reverse_ordered.page(params[:notice]).per(1) | ||
54 | - @notice = @notices.first | 53 | + @notices = problem.object.notices.reverse_ordered |
54 | + .page(params[:notice]).per(1) | ||
55 | + @notice = NoticeDecorator.new @notices.first | ||
55 | @comment = Comment.new | 56 | @comment = Comment.new |
56 | end | 57 | end |
57 | 58 |
@@ -0,0 +1,88 @@ | @@ -0,0 +1,88 @@ | ||
1 | +class BacktraceLineDecorator < Draper::Decorator | ||
2 | + EMPTY_STRING = ''.freeze | ||
3 | + | ||
4 | + def in_app? | ||
5 | + object[:file].match Backtrace::IN_APP_PATH | ||
6 | + end | ||
7 | + | ||
8 | + def number | ||
9 | + object[:number] | ||
10 | + end | ||
11 | + | ||
12 | + def column | ||
13 | + object[:column] | ||
14 | + end | ||
15 | + | ||
16 | + def file | ||
17 | + object[:file] | ||
18 | + end | ||
19 | + | ||
20 | + def method | ||
21 | + object[:method] | ||
22 | + end | ||
23 | + | ||
24 | + def file_relative | ||
25 | + file.to_s.sub(Backtrace::IN_APP_PATH, EMPTY_STRING) | ||
26 | + end | ||
27 | + | ||
28 | + def file_name | ||
29 | + File.basename file | ||
30 | + end | ||
31 | + | ||
32 | + def to_s | ||
33 | + column = object.try(:[], :column) | ||
34 | + "#{file_relative}:#{number}#{column.present? ? ":#{column}" : ''}" | ||
35 | + end | ||
36 | + | ||
37 | + def link_to_source_file(app, &block) | ||
38 | + text = h.capture_haml(&block) | ||
39 | + link_to_in_app_source_file(app, text) || text | ||
40 | + end | ||
41 | + | ||
42 | + def path | ||
43 | + File.dirname(object[:file]).gsub(/^\.$/, '') + "/" | ||
44 | + end | ||
45 | + | ||
46 | + def decorated_path | ||
47 | + file_relative.sub(Backtrace::GEMS_PATH, "<strong>\\1</strong>") | ||
48 | + end | ||
49 | + | ||
50 | + private | ||
51 | + def link_to_in_app_source_file(app, text) | ||
52 | + return unless in_app? | ||
53 | + if file_name =~ /\.js$/ | ||
54 | + link_to_hosted_javascript(app, text) | ||
55 | + else | ||
56 | + link_to_repo_source_file(app, text) || | ||
57 | + link_to_issue_tracker_file(app, text) | ||
58 | + end | ||
59 | + end | ||
60 | + | ||
61 | + def link_to_repo_source_file(app, text) | ||
62 | + link_to_github(app, text) || link_to_bitbucket(app, text) | ||
63 | + end | ||
64 | + | ||
65 | + def link_to_hosted_javascript(app, text) | ||
66 | + if app.asset_host? | ||
67 | + h.link_to(text, "#{app.asset_host}/#{file_relative}", :target => '_blank') | ||
68 | + end | ||
69 | + end | ||
70 | + | ||
71 | + def link_to_github(app, text = nil) | ||
72 | + return unless app.github_repo? | ||
73 | + href = "%s#L%s" % [app.github_url_to_file(decorated_path + file_name), number] | ||
74 | + h.link_to(text || file_name, href, :target => '_blank') | ||
75 | + end | ||
76 | + | ||
77 | + def link_to_bitbucket(app, text = nil) | ||
78 | + return unless app.bitbucket_repo? | ||
79 | + href = "%s#cl-%s" % [app.bitbucket_url_to_file(decorated_path + file_name), number] | ||
80 | + h.link_to(text || file_name, href, :target => '_blank') | ||
81 | + end | ||
82 | + | ||
83 | + def link_to_issue_tracker_file(app, text = nil) | ||
84 | + return unless app.issue_tracker && app.issue_tracker.respond_to?(:url_to_file) | ||
85 | + href = app.issue_tracker.url_to_file(file_relative, number) | ||
86 | + h.link_to(text || file_name, href, :target => '_blank') | ||
87 | + end | ||
88 | +end |
app/helpers/backtrace_line_helper.rb
1 | module BacktraceLineHelper | 1 | module BacktraceLineHelper |
2 | - def link_to_source_file(line, &block) | 2 | + def link_to_source_file(line, app, &block) |
3 | text = capture_haml(&block) | 3 | text = capture_haml(&block) |
4 | - link_to_in_app_source_file(line, text) || link_to_external_source_file(text) | 4 | + link_to_in_app_source_file(line, app, text) || text |
5 | end | 5 | end |
6 | 6 | ||
7 | private | 7 | private |
8 | - def link_to_in_app_source_file(line, text) | 8 | + def link_to_in_app_source_file(line, app, text) |
9 | return unless line.in_app? | 9 | return unless line.in_app? |
10 | if line.file_name =~ /\.js$/ | 10 | if line.file_name =~ /\.js$/ |
11 | - link_to_hosted_javascript(line, text) | 11 | + link_to_hosted_javascript(line, app, text) |
12 | else | 12 | else |
13 | - link_to_repo_source_file(line, text) || | ||
14 | - link_to_issue_tracker_file(line, text) | 13 | + link_to_repo_source_file(line, app, text) || |
14 | + link_to_issue_tracker_file(line, app, text) | ||
15 | end | 15 | end |
16 | end | 16 | end |
17 | 17 | ||
18 | - def link_to_repo_source_file(line, text) | ||
19 | - link_to_github(line, text) || link_to_bitbucket(line, text) | 18 | + def link_to_repo_source_file(line, app, text) |
19 | + link_to_github(line, app, text) || link_to_bitbucket(line, app, text) | ||
20 | end | 20 | end |
21 | 21 | ||
22 | - def link_to_hosted_javascript(line, text) | ||
23 | - if line.app.asset_host? | ||
24 | - link_to(text, "#{line.app.asset_host}/#{line.file_relative}", :target => '_blank') | 22 | + def link_to_hosted_javascript(line, app, text) |
23 | + if app.asset_host? | ||
24 | + link_to(text, "#{app.asset_host}/#{line.file_relative}", :target => '_blank') | ||
25 | end | 25 | end |
26 | end | 26 | end |
27 | 27 | ||
28 | - def link_to_external_source_file(text) | ||
29 | - text | ||
30 | - end | ||
31 | - | ||
32 | - def link_to_github(line, text = nil) | ||
33 | - return unless line.app.github_repo? | ||
34 | - href = "%s#L%s" % [line.app.github_url_to_file(line.decorated_path + line.file_name), line.number] | 28 | + def link_to_github(line, app, text = nil) |
29 | + return unless app.github_repo? | ||
30 | + href = "%s#L%s" % [app.github_url_to_file(line.decorated_path + line.file_name), line.number] | ||
35 | link_to(text || line.file_name, href, :target => '_blank') | 31 | link_to(text || line.file_name, href, :target => '_blank') |
36 | end | 32 | end |
37 | 33 | ||
38 | - def link_to_bitbucket(line, text = nil) | ||
39 | - return unless line.app.bitbucket_repo? | ||
40 | - href = "%s#cl-%s" % [line.app.bitbucket_url_to_file(line.decorated_path + line.file_name), line.number] | 34 | + def link_to_bitbucket(line, app, text = nil) |
35 | + return unless app.bitbucket_repo? | ||
36 | + href = "%s#cl-%s" % [app.bitbucket_url_to_file(line.decorated_path + line.file_name), line.number] | ||
41 | link_to(text || line.file_name, href, :target => '_blank') | 37 | link_to(text || line.file_name, href, :target => '_blank') |
42 | end | 38 | end |
43 | 39 | ||
44 | - def link_to_issue_tracker_file(line, text = nil) | ||
45 | - return unless line.app.issue_tracker && line.app.issue_tracker.respond_to?(:url_to_file) | ||
46 | - href = line.app.issue_tracker.url_to_file(line.file_relative, line.number) | 40 | + def link_to_issue_tracker_file(line, app, text = nil) |
41 | + return unless app.issue_tracker && app.issue_tracker.respond_to?(:url_to_file) | ||
42 | + href = app.issue_tracker.url_to_file(line.file_relative, line.number) | ||
47 | link_to(text || line.file_name, href, :target => '_blank') | 43 | link_to(text || line.file_name, href, :target => '_blank') |
48 | end | 44 | end |
49 | 45 |
app/interactors/resolved_problem_clearer.rb
@@ -27,6 +27,6 @@ class ResolvedProblemClearer | @@ -27,6 +27,6 @@ class ResolvedProblemClearer | ||
27 | end | 27 | end |
28 | 28 | ||
29 | def repair_database | 29 | def repair_database |
30 | - Mongoid.default_session.command :repairDatabase => 1 | 30 | + Mongoid.default_client.command :repairDatabase => 1 |
31 | end | 31 | end |
32 | end | 32 | end |
app/mailers/mailer.rb
@@ -13,11 +13,11 @@ class Mailer < ActionMailer::Base | @@ -13,11 +13,11 @@ class Mailer < ActionMailer::Base | ||
13 | 'Precedence' => 'bulk', | 13 | 'Precedence' => 'bulk', |
14 | 'Auto-Submitted' => 'auto-generated' | 14 | 'Auto-Submitted' => 'auto-generated' |
15 | 15 | ||
16 | - def err_notification(notice) | ||
17 | - @notice = notice | ||
18 | - @app = notice.app | 16 | + def err_notification(error_report) |
17 | + @notice = NoticeDecorator.new error_report.notice | ||
18 | + @app = AppDecorator.new error_report.app | ||
19 | 19 | ||
20 | - count = @notice.similar_count | 20 | + count = error_report.problem.notices_count |
21 | count = count > 1 ? "(#{count}) " : "" | 21 | count = count > 1 ? "(#{count}) " : "" |
22 | 22 | ||
23 | errbit_headers 'App' => @app.name, | 23 | errbit_headers 'App' => @app.name, |
@@ -30,7 +30,7 @@ class Mailer < ActionMailer::Base | @@ -30,7 +30,7 @@ class Mailer < ActionMailer::Base | ||
30 | 30 | ||
31 | def deploy_notification(deploy) | 31 | def deploy_notification(deploy) |
32 | @deploy = deploy | 32 | @deploy = deploy |
33 | - @app = deploy.app | 33 | + @app = AppDecorator.new deploy.app |
34 | 34 | ||
35 | errbit_headers 'App' => @app.name, | 35 | errbit_headers 'App' => @app.name, |
36 | 'Environment' => @deploy.environment, | 36 | 'Environment' => @deploy.environment, |
@@ -44,7 +44,7 @@ class Mailer < ActionMailer::Base | @@ -44,7 +44,7 @@ class Mailer < ActionMailer::Base | ||
44 | def comment_notification(comment) | 44 | def comment_notification(comment) |
45 | @comment = comment | 45 | @comment = comment |
46 | @user = comment.user | 46 | @user = comment.user |
47 | - @problem = comment.err | 47 | + @problem = ProblemDecorator.new comment.err |
48 | @notice = @problem.notices.first | 48 | @notice = @problem.notices.first |
49 | @app = @problem.app | 49 | @app = @problem.app |
50 | 50 |
app/models/app.rb
@@ -57,8 +57,14 @@ class App | @@ -57,8 +57,14 @@ class App | ||
57 | def find_or_create_err!(attrs) | 57 | def find_or_create_err!(attrs) |
58 | Err.where( | 58 | Err.where( |
59 | :fingerprint => attrs[:fingerprint] | 59 | :fingerprint => attrs[:fingerprint] |
60 | - ).first || | ||
61 | - problems.create!(attrs.slice(:error_class, :environment)).errs.create!(attrs.slice(:fingerprint, :problem_id)) | 60 | + ).first || ( |
61 | + problem = problems.create!( | ||
62 | + error_class: attrs[:error_class], | ||
63 | + environment: attrs[:environment], | ||
64 | + app_name: name | ||
65 | + ) | ||
66 | + problem.errs.create!(attrs.slice(:fingerprint, :problem_id)) | ||
67 | + ) | ||
62 | end | 68 | end |
63 | 69 | ||
64 | # Mongoid Bug: find(id) on association proxies returns an Enumerator | 70 | # Mongoid Bug: find(id) on association proxies returns an Enumerator |
@@ -178,7 +184,9 @@ class App | @@ -178,7 +184,9 @@ class App | ||
178 | protected | 184 | protected |
179 | 185 | ||
180 | def store_cached_attributes_on_problems | 186 | def store_cached_attributes_on_problems |
181 | - problems.each(&:cache_app_attributes) | 187 | + Problem.where(:app_id => id).update_all( |
188 | + app_name: name | ||
189 | + ) | ||
182 | end | 190 | end |
183 | 191 | ||
184 | def generate_api_key | 192 | def generate_api_key |
app/models/backtrace.rb
@@ -2,35 +2,28 @@ class Backtrace | @@ -2,35 +2,28 @@ class Backtrace | ||
2 | include Mongoid::Document | 2 | include Mongoid::Document |
3 | include Mongoid::Timestamps | 3 | include Mongoid::Timestamps |
4 | 4 | ||
5 | - field :fingerprint | ||
6 | - index :fingerprint => 1 | ||
7 | - | ||
8 | - has_many :notices | ||
9 | - has_one :notice | ||
10 | - | ||
11 | - embeds_many :lines, :class_name => "BacktraceLine" | 5 | + IN_APP_PATH = %r{^\[PROJECT_ROOT\](?!(\/vendor))/?} |
6 | + GEMS_PATH = %r{\[GEM_ROOT\]\/gems\/([^\/]+)} | ||
12 | 7 | ||
13 | - after_initialize :generate_fingerprint | 8 | + field :fingerprint |
9 | + field :lines | ||
14 | 10 | ||
15 | - delegate :app, :to => :notice | 11 | + index :fingerprint => 1 |
16 | 12 | ||
17 | - def self.find_or_create(attributes = {}) | ||
18 | - new(attributes).similar || create(attributes) | ||
19 | - end | 13 | + def self.find_or_create(lines) |
14 | + fingerprint = generate_fingerprint(lines) | ||
20 | 15 | ||
21 | - def similar | ||
22 | - Backtrace.where(:fingerprint => fingerprint).first | 16 | + where(fingerprint: fingerprint).find_one_and_update( |
17 | + { '$setOnInsert' => { fingerprint: fingerprint, lines: lines } }, | ||
18 | + { return_document: :after, upsert: true }) | ||
23 | end | 19 | end |
24 | 20 | ||
25 | - def raw=(raw) | ||
26 | - raw.compact.each do |raw_line| | ||
27 | - lines << BacktraceLine.new(BacktraceLineNormalizer.new(raw_line).call) | ||
28 | - end | 21 | + def self.generate_fingerprint(lines) |
22 | + Digest::SHA1.hexdigest(lines.map(&:to_s).join) | ||
29 | end | 23 | end |
30 | 24 | ||
31 | private | 25 | private |
32 | def generate_fingerprint | 26 | def generate_fingerprint |
33 | - self.fingerprint = Digest::SHA1.hexdigest(lines.map(&:to_s).join) | 27 | + self.fingerprint = self.class.generate_fingerprint(lines) |
34 | end | 28 | end |
35 | - | ||
36 | end | 29 | end |
app/models/backtrace_line.rb
@@ -1,42 +0,0 @@ | @@ -1,42 +0,0 @@ | ||
1 | -class BacktraceLine | ||
2 | - include Mongoid::Document | ||
3 | - IN_APP_PATH = %r{^\[PROJECT_ROOT\](?!(\/vendor))/?} | ||
4 | - GEMS_PATH = %r{\[GEM_ROOT\]\/gems\/([^\/]+)} | ||
5 | - | ||
6 | - field :number, :type => Integer | ||
7 | - field :column, :type => Integer | ||
8 | - field :file | ||
9 | - field :method | ||
10 | - | ||
11 | - embedded_in :backtrace | ||
12 | - | ||
13 | - scope :in_app, ->{ where(:file => IN_APP_PATH) } | ||
14 | - | ||
15 | - delegate :app, :to => :backtrace | ||
16 | - | ||
17 | - def to_s | ||
18 | - "#{file_relative}:#{number}" << (column.present? ? ":#{column}" : "") | ||
19 | - end | ||
20 | - | ||
21 | - def in_app? | ||
22 | - !!(file =~ IN_APP_PATH) | ||
23 | - end | ||
24 | - | ||
25 | - def path | ||
26 | - File.dirname(file).gsub(/^\.$/, '') + "/" | ||
27 | - end | ||
28 | - | ||
29 | - def file_relative | ||
30 | - file.to_s.sub(IN_APP_PATH, '') | ||
31 | - end | ||
32 | - | ||
33 | - def file_name | ||
34 | - File.basename file | ||
35 | - end | ||
36 | - | ||
37 | - def decorated_path | ||
38 | - path.sub(BacktraceLine::IN_APP_PATH, ''). | ||
39 | - sub(BacktraceLine::GEMS_PATH, "<strong>\\1</strong>") | ||
40 | - end | ||
41 | - | ||
42 | -end |
app/models/backtrace_line_normalizer.rb
@@ -1,32 +0,0 @@ | @@ -1,32 +0,0 @@ | ||
1 | -class BacktraceLineNormalizer | ||
2 | - def initialize(raw_line) | ||
3 | - @raw_line = raw_line || {} | ||
4 | - end | ||
5 | - | ||
6 | - def call | ||
7 | - @raw_line.merge 'file' => normalized_file, 'method' => normalized_method | ||
8 | - end | ||
9 | - | ||
10 | - private | ||
11 | - def normalized_file | ||
12 | - if @raw_line['file'].blank? | ||
13 | - "[unknown source]" | ||
14 | - else | ||
15 | - file = @raw_line['file'].to_s | ||
16 | - # Detect lines from gem | ||
17 | - file.gsub!(/\[PROJECT_ROOT\]\/.*\/ruby\/[0-9.]+\/gems/, '[GEM_ROOT]/gems') | ||
18 | - # Strip any query strings | ||
19 | - file.gsub!(/\?[^\?]*$/, '') | ||
20 | - @raw_line['file'] = file | ||
21 | - end | ||
22 | - end | ||
23 | - | ||
24 | - def normalized_method | ||
25 | - if @raw_line['method'].blank? | ||
26 | - "[unknown method]" | ||
27 | - else | ||
28 | - @raw_line['method'].to_s.gsub(/[0-9_]{10,}+/, "__FRAGMENT__") | ||
29 | - end | ||
30 | - end | ||
31 | - | ||
32 | -end |
app/models/deploy.rb
@@ -33,7 +33,9 @@ class Deploy | @@ -33,7 +33,9 @@ class Deploy | ||
33 | end | 33 | end |
34 | 34 | ||
35 | def store_cached_attributes_on_problems | 35 | def store_cached_attributes_on_problems |
36 | - Problem.where(:app_id => app.id).each(&:cache_app_attributes) | 36 | + Problem.where(:app_id => app.id).update_all( |
37 | + last_deploy_at: created_at | ||
38 | + ) | ||
37 | end | 39 | end |
38 | 40 | ||
39 | def deliver_email | 41 | def deliver_email |
app/models/error_report.rb
@@ -15,8 +15,16 @@ require 'hoptoad_notifier' | @@ -15,8 +15,16 @@ require 'hoptoad_notifier' | ||
15 | # * <tt>:notifier</tt> - information to identify the source of the error report | 15 | # * <tt>:notifier</tt> - information to identify the source of the error report |
16 | # | 16 | # |
17 | class ErrorReport | 17 | class ErrorReport |
18 | - attr_reader :error_class, :message, :request, :server_environment, :api_key, | ||
19 | - :notifier, :user_attributes, :framework, :notice | 18 | + attr_reader :api_key |
19 | + attr_reader :error_class | ||
20 | + attr_reader :framework | ||
21 | + attr_reader :message | ||
22 | + attr_reader :notice | ||
23 | + attr_reader :notifier | ||
24 | + attr_reader :problem | ||
25 | + attr_reader :request | ||
26 | + attr_reader :server_environment | ||
27 | + attr_reader :user_attributes | ||
20 | 28 | ||
21 | cattr_accessor :fingerprint_strategy do | 29 | cattr_accessor :fingerprint_strategy do |
22 | Fingerprint::Sha1 | 30 | Fingerprint::Sha1 |
@@ -40,24 +48,59 @@ class ErrorReport | @@ -40,24 +48,59 @@ class ErrorReport | ||
40 | end | 48 | end |
41 | 49 | ||
42 | def backtrace | 50 | def backtrace |
43 | - @normalized_backtrace ||= Backtrace.find_or_create(raw: @backtrace) | 51 | + @normalized_backtrace ||= Backtrace.find_or_create(@backtrace) |
44 | end | 52 | end |
45 | 53 | ||
46 | def generate_notice! | 54 | def generate_notice! |
47 | return unless valid? | 55 | return unless valid? |
48 | return @notice if @notice | 56 | return @notice if @notice |
57 | + | ||
58 | + make_notice | ||
59 | + error.notices << @notice | ||
60 | + cache_attributes_on_problem | ||
61 | + email_notification | ||
62 | + services_notification | ||
63 | + @notice | ||
64 | + end | ||
65 | + | ||
66 | + def make_notice | ||
49 | @notice = Notice.new( | 67 | @notice = Notice.new( |
50 | message: message, | 68 | message: message, |
51 | error_class: error_class, | 69 | error_class: error_class, |
52 | - backtrace_id: backtrace.id, | 70 | + backtrace: backtrace, |
53 | request: request, | 71 | request: request, |
54 | server_environment: server_environment, | 72 | server_environment: server_environment, |
55 | notifier: notifier, | 73 | notifier: notifier, |
56 | user_attributes: user_attributes, | 74 | user_attributes: user_attributes, |
57 | framework: framework | 75 | framework: framework |
58 | ) | 76 | ) |
59 | - error.notices << @notice | ||
60 | - @notice | 77 | + end |
78 | + | ||
79 | + # Update problem cache with information about this notice | ||
80 | + def cache_attributes_on_problem | ||
81 | + @problem = Problem.cache_notice(@error.problem_id, @notice) | ||
82 | + end | ||
83 | + | ||
84 | + # Send email notification if needed | ||
85 | + def email_notification | ||
86 | + return false unless app.emailable? | ||
87 | + return false unless app.email_at_notices.include?(@problem.notices_count) | ||
88 | + Mailer.err_notification(self).deliver | ||
89 | + rescue => e | ||
90 | + HoptoadNotifier.notify(e) | ||
91 | + end | ||
92 | + | ||
93 | + def should_notify? | ||
94 | + app.notification_service.notify_at_notices.include?(0) || | ||
95 | + app.notification_service.notify_at_notices.include?(@problem.notices_count) | ||
96 | + end | ||
97 | + | ||
98 | + # Launch all notification define on the app associate to this notice | ||
99 | + def services_notification | ||
100 | + return true unless app.notification_service_configured? and should_notify? | ||
101 | + app.notification_service.create_notification(problem) | ||
102 | + rescue => e | ||
103 | + HoptoadNotifier.notify(e) | ||
61 | end | 104 | end |
62 | 105 | ||
63 | ## | 106 | ## |
app/models/notice.rb
@@ -20,13 +20,10 @@ class Notice | @@ -20,13 +20,10 @@ class Notice | ||
20 | index(:created_at => 1) | 20 | index(:created_at => 1) |
21 | index(:err_id => 1, :created_at => 1, :_id => 1) | 21 | index(:err_id => 1, :created_at => 1, :_id => 1) |
22 | 22 | ||
23 | - after_create :cache_attributes_on_problem, :unresolve_problem | ||
24 | - after_create :email_notification | ||
25 | - after_create :services_notification | ||
26 | before_save :sanitize | 23 | before_save :sanitize |
27 | - before_destroy :decrease_counter_cache, :remove_cached_attributes_from_problem | 24 | + before_destroy :problem_recache |
28 | 25 | ||
29 | - validates_presence_of :backtrace, :server_environment, :notifier | 26 | + validates_presence_of :backtrace_id, :server_environment, :notifier |
30 | 27 | ||
31 | scope :ordered, ->{ order_by(:created_at.asc) } | 28 | scope :ordered, ->{ order_by(:created_at.asc) } |
32 | scope :reverse_ordered, ->{ order_by(:created_at.desc) } | 29 | scope :reverse_ordered, ->{ order_by(:created_at.desc) } |
@@ -106,26 +103,6 @@ class Notice | @@ -106,26 +103,6 @@ class Notice | ||
106 | request['session'] || {} | 103 | request['session'] || {} |
107 | end | 104 | end |
108 | 105 | ||
109 | - def in_app_backtrace_lines | ||
110 | - backtrace_lines.in_app | ||
111 | - end | ||
112 | - | ||
113 | - def similar_count | ||
114 | - problem.notices_count | ||
115 | - end | ||
116 | - | ||
117 | - def emailable? | ||
118 | - app.email_at_notices.include?(similar_count) | ||
119 | - end | ||
120 | - | ||
121 | - def should_email? | ||
122 | - app.emailable? && emailable? | ||
123 | - end | ||
124 | - | ||
125 | - def should_notify? | ||
126 | - app.notification_service.notify_at_notices.include?(0) || app.notification_service.notify_at_notices.include?(similar_count) | ||
127 | - end | ||
128 | - | ||
129 | ## | 106 | ## |
130 | # TODO: Move on decorator maybe | 107 | # TODO: Move on decorator maybe |
131 | # | 108 | # |
@@ -143,20 +120,8 @@ class Notice | @@ -143,20 +120,8 @@ class Notice | ||
143 | 120 | ||
144 | protected | 121 | protected |
145 | 122 | ||
146 | - def decrease_counter_cache | ||
147 | - problem.inc(notices_count: -1) if err | ||
148 | - end | ||
149 | - | ||
150 | - def remove_cached_attributes_from_problem | ||
151 | - problem.remove_cached_notice_attributes(self) if err | ||
152 | - end | ||
153 | - | ||
154 | - def unresolve_problem | ||
155 | - problem.update_attributes!(:resolved => false, :resolved_at => nil, :notices_count => 1) if problem.resolved? | ||
156 | - end | ||
157 | - | ||
158 | - def cache_attributes_on_problem | ||
159 | - ProblemUpdaterCache.new(problem, self).update | 123 | + def problem_recache |
124 | + problem.uncache_notice(self) | ||
160 | end | 125 | end |
161 | 126 | ||
162 | def sanitize | 127 | def sanitize |
@@ -165,7 +130,6 @@ class Notice | @@ -165,7 +130,6 @@ class Notice | ||
165 | end | 130 | end |
166 | end | 131 | end |
167 | 132 | ||
168 | - | ||
169 | def sanitize_hash(h) | 133 | def sanitize_hash(h) |
170 | h.recurse do |h| | 134 | h.recurse do |h| |
171 | h.inject({}) do |h,(k,v)| | 135 | h.inject({}) do |h,(k,v)| |
@@ -178,25 +142,4 @@ class Notice | @@ -178,25 +142,4 @@ class Notice | ||
178 | end | 142 | end |
179 | end | 143 | end |
180 | end | 144 | end |
181 | - | ||
182 | - private | ||
183 | - | ||
184 | - ## | ||
185 | - # Send email notification if needed | ||
186 | - def email_notification | ||
187 | - return true unless should_email? | ||
188 | - Mailer.err_notification(self).deliver | ||
189 | - rescue => e | ||
190 | - HoptoadNotifier.notify(e) | ||
191 | - end | ||
192 | - | ||
193 | - ## | ||
194 | - # Launch all notification define on the app associate to this notice | ||
195 | - def services_notification | ||
196 | - return true unless app.notification_service_configured? and should_notify? | ||
197 | - app.notification_service.create_notification(problem) | ||
198 | - rescue => e | ||
199 | - HoptoadNotifier.notify(e) | ||
200 | - end | ||
201 | - | ||
202 | end | 145 | end |
app/models/notification_service.rb
@@ -26,7 +26,7 @@ class NotificationService | @@ -26,7 +26,7 @@ class NotificationService | ||
26 | else | 26 | else |
27 | Fields = [] | 27 | Fields = [] |
28 | end | 28 | end |
29 | - | 29 | + |
30 | def notify_at_notices | 30 | def notify_at_notices |
31 | Errbit::Config.per_app_notify_at_notices ? super : Errbit::Config.notify_at_notices | 31 | Errbit::Config.per_app_notify_at_notices ? super : Errbit::Config.notify_at_notices |
32 | end | 32 | end |
app/models/problem.rb
@@ -6,8 +6,15 @@ class Problem | @@ -6,8 +6,15 @@ class Problem | ||
6 | include Mongoid::Document | 6 | include Mongoid::Document |
7 | include Mongoid::Timestamps | 7 | include Mongoid::Timestamps |
8 | 8 | ||
9 | - field :last_notice_at, :type => DateTime, :default => Proc.new { Time.now } | ||
10 | - field :first_notice_at, :type => DateTime, :default => Proc.new { Time.now } | 9 | + CACHED_NOTICE_ATTRIBUTES = { |
10 | + messages: :message, | ||
11 | + hosts: :host, | ||
12 | + user_agents: :user_agent_string | ||
13 | + }.freeze | ||
14 | + | ||
15 | + | ||
16 | + field :last_notice_at, :type => ActiveSupport::TimeWithZone, :default => Proc.new { Time.now } | ||
17 | + field :first_notice_at, :type => ActiveSupport::TimeWithZone, :default => Proc.new { Time.now } | ||
11 | field :last_deploy_at, :type => Time | 18 | field :last_deploy_at, :type => Time |
12 | field :resolved, :type => Boolean, :default => false | 19 | field :resolved, :type => Boolean, :default => false |
13 | field :resolved_at, :type => Time | 20 | field :resolved_at, :type => Time |
@@ -35,6 +42,14 @@ class Problem | @@ -35,6 +42,14 @@ class Problem | ||
35 | index :resolved_at => 1 | 42 | index :resolved_at => 1 |
36 | index :notices_count => 1 | 43 | index :notices_count => 1 |
37 | 44 | ||
45 | + index({ | ||
46 | + error_class: "text", | ||
47 | + where: "text", | ||
48 | + message: "text", | ||
49 | + app_name: "text", | ||
50 | + environment: "text" | ||
51 | + }, default_language: "english") | ||
52 | + | ||
38 | belongs_to :app | 53 | belongs_to :app |
39 | has_many :errs, :inverse_of => :problem, :dependent => :destroy | 54 | has_many :errs, :inverse_of => :problem, :dependent => :destroy |
40 | has_many :comments, :inverse_of => :err, :dependent => :destroy | 55 | has_many :comments, :inverse_of => :err, :dependent => :destroy |
@@ -63,6 +78,83 @@ class Problem | @@ -63,6 +78,83 @@ class Problem | ||
63 | env.present? ? where(:environment => env) : scoped | 78 | env.present? ? where(:environment => env) : scoped |
64 | end | 79 | end |
65 | 80 | ||
81 | + def self.cache_notice(id, notice) | ||
82 | + # increment notice count | ||
83 | + message_digest = Digest::MD5.hexdigest(notice.message) | ||
84 | + host_digest = Digest::MD5.hexdigest(notice.host) | ||
85 | + user_agent_digest = Digest::MD5.hexdigest(notice.user_agent_string) | ||
86 | + | ||
87 | + Problem.where('_id' => id).find_one_and_update({ | ||
88 | + '$set' => { | ||
89 | + 'environment' => notice.environment_name, | ||
90 | + 'error_class' => notice.error_class, | ||
91 | + 'last_notice_at' => notice.created_at.utc, | ||
92 | + 'message' => notice.message, | ||
93 | + 'resolved' => false, | ||
94 | + 'resolved_at' => nil, | ||
95 | + 'where' => notice.where, | ||
96 | + "messages.#{message_digest}.value" => notice.message, | ||
97 | + "hosts.#{host_digest}.value" => notice.host, | ||
98 | + "user_agents.#{user_agent_digest}.value" => notice.user_agent_string, | ||
99 | + }, | ||
100 | + '$inc' => { | ||
101 | + 'notices_count' => 1, | ||
102 | + "messages.#{message_digest}.count" => 1, | ||
103 | + "hosts.#{host_digest}.count" => 1, | ||
104 | + "user_agents.#{user_agent_digest}.count" => 1, | ||
105 | + } | ||
106 | + }, return_document: :after) | ||
107 | + end | ||
108 | + | ||
109 | + def uncache_notice(notice) | ||
110 | + last_notice = notices.last | ||
111 | + | ||
112 | + atomically do |doc| | ||
113 | + doc.set( | ||
114 | + 'environment' => last_notice.environment_name, | ||
115 | + 'error_class' => last_notice.error_class, | ||
116 | + 'last_notice_at' => last_notice.created_at, | ||
117 | + 'message' => last_notice.message, | ||
118 | + 'where' => last_notice.where, | ||
119 | + 'notices_count' => notices_count.to_i > 1 ? notices_count - 1 : 0 | ||
120 | + ) | ||
121 | + | ||
122 | + CACHED_NOTICE_ATTRIBUTES.each do |k,v| | ||
123 | + digest = Digest::MD5.hexdigest(notice.send(v)) | ||
124 | + field = "#{k}.#{digest}" | ||
125 | + | ||
126 | + if (doc[k].try(:[], digest).try(:[], :count)).to_i > 1 | ||
127 | + doc.inc("#{field}.count" => -1) | ||
128 | + else | ||
129 | + doc.unset(field) | ||
130 | + end | ||
131 | + end | ||
132 | + end | ||
133 | + end | ||
134 | + | ||
135 | + def recache | ||
136 | + CACHED_NOTICE_ATTRIBUTES.each do |k,v| | ||
137 | + # clear all cached attributes | ||
138 | + send("#{k}=", {}) | ||
139 | + | ||
140 | + # find only notices related to this problem | ||
141 | + Notice.collection.find.aggregate([ | ||
142 | + { "$match" => { err_id: { "$in" => err_ids } } }, | ||
143 | + { "$group" => { _id: "$#{v}", count: {"$sum" => 1} } } | ||
144 | + ]).each do |agg| | ||
145 | + next if agg[:_id] == nil | ||
146 | + | ||
147 | + send(k)[Digest::MD5.hexdigest(agg[:_id])] = { | ||
148 | + value: agg[:_id], | ||
149 | + count: agg[:count] | ||
150 | + } | ||
151 | + end | ||
152 | + end | ||
153 | + | ||
154 | + self.notices_count = Notice.where({ err_id: { "$in" => err_ids }}).count | ||
155 | + save | ||
156 | + end | ||
157 | + | ||
66 | def url | 158 | def url |
67 | Rails.application.routes.url_helpers.app_problem_url(app, self, | 159 | Rails.application.routes.url_helpers.app_problem_url(app, self, |
68 | :host => Errbit::Config.host, | 160 | :host => Errbit::Config.host, |
@@ -98,16 +190,21 @@ class Problem | @@ -98,16 +190,21 @@ class Problem | ||
98 | def unmerge! | 190 | def unmerge! |
99 | attrs = {:error_class => error_class, :environment => environment} | 191 | attrs = {:error_class => error_class, :environment => environment} |
100 | problem_errs = errs.to_a | 192 | problem_errs = errs.to_a |
101 | - problem_errs.shift | ||
102 | - [self] + problem_errs.map(&:id).map do |err_id| | ||
103 | - err = Err.find(err_id) | ||
104 | - app.problems.create(attrs).tap do |new_problem| | ||
105 | - err.update_attribute(:problem_id, new_problem.id) | ||
106 | - new_problem.reset_cached_attributes | ||
107 | - end | 193 | + |
194 | + # associate and return all the problems | ||
195 | + new_problems = [self] | ||
196 | + | ||
197 | + # create new problems for each err that needs one | ||
198 | + (problem_errs[1..-1] || []).each do |err| | ||
199 | + new_problems << app.problems.create(attrs) | ||
200 | + err.update_attribute(:problem, new_problems.last) | ||
108 | end | 201 | end |
109 | - end | ||
110 | 202 | ||
203 | + # recache each new problem | ||
204 | + new_problems.each(&:recache) | ||
205 | + | ||
206 | + new_problems | ||
207 | + end | ||
111 | 208 | ||
112 | def self.ordered_by(sort, order) | 209 | def self.ordered_by(sort, order) |
113 | case sort | 210 | case sort |
@@ -124,20 +221,10 @@ class Problem | @@ -124,20 +221,10 @@ class Problem | ||
124 | where(:first_notice_at.lte => date_range.end).where("$or" => [{:resolved_at => nil}, {:resolved_at.gte => date_range.begin}]) | 221 | where(:first_notice_at.lte => date_range.end).where("$or" => [{:resolved_at => nil}, {:resolved_at.gte => date_range.begin}]) |
125 | end | 222 | end |
126 | 223 | ||
127 | - | ||
128 | - def reset_cached_attributes | ||
129 | - ProblemUpdaterCache.new(self).update | ||
130 | - end | ||
131 | - | ||
132 | def cache_app_attributes | 224 | def cache_app_attributes |
133 | if app | 225 | if app |
134 | self.app_name = app.name | 226 | self.app_name = app.name |
135 | - self.last_deploy_at = if (last_deploy = app.deploys.where(:environment => self.environment).last) | ||
136 | - last_deploy.created_at.utc | ||
137 | - end | ||
138 | - collection.find('_id' => self.id) | ||
139 | - .update({'$set' => {'app_name' => self.app_name, | ||
140 | - 'last_deploy_at' => self.last_deploy_at.try(:utc)}}) | 227 | + self.last_deploy_at = app.last_deploy_at |
141 | end | 228 | end |
142 | end | 229 | end |
143 | 230 | ||
@@ -145,14 +232,6 @@ class Problem | @@ -145,14 +232,6 @@ class Problem | ||
145 | self.message = self.message[0, 1000] if self.message | 232 | self.message = self.message[0, 1000] if self.message |
146 | end | 233 | end |
147 | 234 | ||
148 | - def remove_cached_notice_attributes(notice) | ||
149 | - update_attributes!( | ||
150 | - :messages => attribute_count_descrease(:messages, notice.message), | ||
151 | - :hosts => attribute_count_descrease(:hosts, notice.host), | ||
152 | - :user_agents => attribute_count_descrease(:user_agents, notice.user_agent_string) | ||
153 | - ) | ||
154 | - end | ||
155 | - | ||
156 | def issue_type | 235 | def issue_type |
157 | # Return issue_type if configured, but fall back to detecting app's issue tracker | 236 | # Return issue_type if configured, but fall back to detecting app's issue tracker |
158 | attributes['issue_type'] ||= | 237 | attributes['issue_type'] ||= |
@@ -160,13 +239,7 @@ class Problem | @@ -160,13 +239,7 @@ class Problem | ||
160 | end | 239 | end |
161 | 240 | ||
162 | def self.search(value) | 241 | def self.search(value) |
163 | - any_of( | ||
164 | - {:error_class => /#{value}/i}, | ||
165 | - {:where => /#{value}/i}, | ||
166 | - {:message => /#{value}/i}, | ||
167 | - {:app_name => /#{value}/i}, | ||
168 | - {:environment => /#{value}/i} | ||
169 | - ) | 242 | + Problem.where({'$text' => {'$search' => value}}) |
170 | end | 243 | end |
171 | 244 | ||
172 | private | 245 | private |
app/views/issue_trackers/issue.md.erb
@@ -27,7 +27,7 @@ | @@ -27,7 +27,7 @@ | ||
27 | 27 | ||
28 | ## Backtrace ## | 28 | ## Backtrace ## |
29 | ~~~ | 29 | ~~~ |
30 | -<% notice.backtrace_lines.each do |line| %><%= line.number %>: <%= line.file_relative %> -> **<%= line.method %>** | 30 | +<% notice.backtrace.lines.each do |line| %><%= line.number %>: <%= line.file_relative %> -> **<%= line.method %>** |
31 | <% end %> | 31 | <% end %> |
32 | ~~~ | 32 | ~~~ |
33 | 33 |
app/views/issue_trackers/issue.txt.erb
@@ -16,6 +16,6 @@ Env: <%= pretty_hash notice.env_vars %> | @@ -16,6 +16,6 @@ Env: <%= pretty_hash notice.env_vars %> | ||
16 | 16 | ||
17 | Backtrace | 17 | Backtrace |
18 | --------- | 18 | --------- |
19 | -<% notice.backtrace_lines.each do |line| %><%= sprintf('%5d: %s **%s', line.number, line.file_relative, line.method) %> | 19 | +<% notice.backtrace.lines.each do |line| %><%= sprintf('%5d: %s **%s', line.number, line.file_relative, line.method) %> |
20 | <% end %> | 20 | <% end %> |
21 | <% end %> | 21 | <% end %> |
app/views/mailer/comment_notification.text.erb
@@ -21,8 +21,8 @@ WHERE: | @@ -21,8 +21,8 @@ WHERE: | ||
21 | 21 | ||
22 | <%= @notice.where %> | 22 | <%= @notice.where %> |
23 | 23 | ||
24 | -<% @notice.in_app_backtrace_lines.each do |line| %> | ||
25 | - <%= line %> | 24 | +<% @notice.backtrace.lines.each do |line| %> |
25 | + <% next unless line.in_app? %><%= line %> | ||
26 | <% end %> | 26 | <% end %> |
27 | 27 | ||
28 | 28 |
app/views/mailer/err_notification.html.haml
@@ -27,9 +27,10 @@ | @@ -27,9 +27,10 @@ | ||
27 | %p.heading WHERE: | 27 | %p.heading WHERE: |
28 | %p.monospace | 28 | %p.monospace |
29 | = @notice.where | 29 | = @notice.where |
30 | - - @notice.in_app_backtrace_lines.each do |line| | 30 | + - @notice.backtrace.lines.each do |line| |
31 | + - next unless line.in_app? | ||
31 | %p.backtrace | 32 | %p.backtrace |
32 | - = link_to_source_file(line) do | 33 | + = line.link_to_source_file(@app) do |
33 | = line.to_s | 34 | = line.to_s |
34 | %br | 35 | %br |
35 | - if @notice.app_version.present? | 36 | - if @notice.app_version.present? |
@@ -59,11 +60,11 @@ | @@ -59,11 +60,11 @@ | ||
59 | %td(style="text-align: right; padding-right: 10px; color: #6a6a6a;")= key.to_s.titleize + ":" | 60 | %td(style="text-align: right; padding-right: 10px; color: #6a6a6a;")= key.to_s.titleize + ":" |
60 | %td= auto_link(value.to_s) | 61 | %td= auto_link(value.to_s) |
61 | %br | 62 | %br |
62 | - - if @notice.backtrace_lines.any? | 63 | + - if @notice.backtrace.lines.any? |
63 | %br | 64 | %br |
64 | %p.heading FULL BACKTRACE: | 65 | %p.heading FULL BACKTRACE: |
65 | - - @notice.backtrace_lines.each do |line| | 66 | + - @notice.backtrace.lines.each do |line| |
66 | %p.backtrace | 67 | %p.backtrace |
67 | - = link_to_source_file(line) do | 68 | + = link_to_source_file(line, @app) do |
68 | = line.to_s | 69 | = line.to_s |
69 | %br | 70 | %br |
app/views/mailer/err_notification.text.erb
@@ -14,7 +14,8 @@ WHERE: | @@ -14,7 +14,8 @@ WHERE: | ||
14 | 14 | ||
15 | <%= @notice.where %> | 15 | <%= @notice.where %> |
16 | 16 | ||
17 | -<% @notice.in_app_backtrace_lines.each do |line| %> | 17 | +<% @notice.backtrace.lines.each do |line| %> |
18 | + <% next unless line.in_app? %> | ||
18 | <%= line %> | 19 | <%= line %> |
19 | <% end %> | 20 | <% end %> |
20 | 21 | ||
@@ -51,7 +52,7 @@ USER: | @@ -51,7 +52,7 @@ USER: | ||
51 | 52 | ||
52 | BACKTRACE: | 53 | BACKTRACE: |
53 | 54 | ||
54 | -<% @notice.backtrace_lines.each do |line| %> | 55 | +<% @notice.backtrace.lines.each do |line| %> |
55 | <%= line %> | 56 | <%= line %> |
56 | <% end %> | 57 | <% end %> |
57 | 58 |
app/views/notices/_backtrace_line.html.haml
1 | %tr{:class => defined?(row_class) && row_class} | 1 | %tr{:class => defined?(row_class) && row_class} |
2 | %td.line{:class => line.in_app? && 'in-app' } | 2 | %td.line{:class => line.in_app? && 'in-app' } |
3 | - = link_to_source_file(line) do | 3 | + = line.link_to_source_file(app) do |
4 | %span.path>= raw line.decorated_path | 4 | %span.path>= raw line.decorated_path |
5 | %span.file>= line.file_name | 5 | %span.file>= line.file_name |
6 | - if line.number.present? | 6 | - if line.number.present? |
app/views/problems/show.html.haml
@@ -68,7 +68,7 @@ | @@ -68,7 +68,7 @@ | ||
68 | 68 | ||
69 | #backtrace | 69 | #backtrace |
70 | %h3 Backtrace | 70 | %h3 Backtrace |
71 | - = render 'notices/backtrace', :lines => @notice.backtrace_lines | 71 | + = render 'notices/backtrace', :lines => @notice.backtrace.lines |
72 | 72 | ||
73 | - if @notice.user_attributes.present? | 73 | - if @notice.user_attributes.present? |
74 | #user_attributes | 74 | #user_attributes |
config/application.rb
@@ -2,7 +2,7 @@ require File.expand_path('../boot', __FILE__) | @@ -2,7 +2,7 @@ require File.expand_path('../boot', __FILE__) | ||
2 | 2 | ||
3 | require 'action_controller/railtie' | 3 | require 'action_controller/railtie' |
4 | require 'action_mailer/railtie' | 4 | require 'action_mailer/railtie' |
5 | -require 'mongoid/railtie' | 5 | +# require 'mongoid/railtie' |
6 | require 'sprockets/railtie' | 6 | require 'sprockets/railtie' |
7 | 7 | ||
8 | # Require the gems listed in Gemfile, including any gems | 8 | # Require the gems listed in Gemfile, including any gems |
@@ -19,9 +19,6 @@ module Errbit | @@ -19,9 +19,6 @@ module Errbit | ||
19 | config.autoload_paths += [Rails.root.join('lib')] | 19 | config.autoload_paths += [Rails.root.join('lib')] |
20 | 20 | ||
21 | config.before_initialize do | 21 | config.before_initialize do |
22 | - # Load up Errbit::Config with values from the environment | ||
23 | - require Rails.root.join('config/load') | ||
24 | - | ||
25 | config.secret_key_base = Errbit::Config.secret_key_base | 22 | config.secret_key_base = Errbit::Config.secret_key_base |
26 | config.serve_static_assets = Errbit::Config.serve_static_assets | 23 | config.serve_static_assets = Errbit::Config.serve_static_assets |
27 | end | 24 | end |
config/environment.rb
1 | # Load the Rails application. | 1 | # Load the Rails application. |
2 | require File.expand_path('../application', __FILE__) | 2 | require File.expand_path('../application', __FILE__) |
3 | 3 | ||
4 | +# Load up Errbit::Config with values from the environment | ||
5 | +require Rails.root.join('config/load') | ||
6 | + | ||
7 | +if Errbit::Config.log_location == 'STDOUT' | ||
8 | + Rails.logger = ActiveSupport::Logger.new STDOUT | ||
9 | +else | ||
10 | + Rails.logger = ActiveSupport::Logger.new Errbit::Config.log_location | ||
11 | +end | ||
12 | + | ||
13 | +Rails.logger.level = Errbit::Config.log_level.to_sym | ||
14 | + | ||
4 | # Initialize the Rails application. | 15 | # Initialize the Rails application. |
5 | Rails.application.initialize! | 16 | Rails.application.initialize! |
config/initializers/mongo.rb
config/load.rb
1 | # load default ENV values (without overwriting any existing value) | 1 | # load default ENV values (without overwriting any existing value) |
2 | Dotenv.load('.env.default') | 2 | Dotenv.load('.env.default') |
3 | 3 | ||
4 | +require_relative '../lib/configurator' | ||
5 | + | ||
4 | # map config keys to environment variables | 6 | # map config keys to environment variables |
5 | # | 7 | # |
6 | # We use the first non-nil environment variable in the list. If the last array | 8 | # We use the first non-nil environment variable in the list. If the last array |
@@ -19,6 +21,8 @@ Errbit::Config = Configurator.run({ | @@ -19,6 +21,8 @@ Errbit::Config = Configurator.run({ | ||
19 | per_app_email_at_notices: ['ERRBIT_PER_APP_EMAIL_AT_NOTICES'], | 21 | per_app_email_at_notices: ['ERRBIT_PER_APP_EMAIL_AT_NOTICES'], |
20 | notify_at_notices: ['ERRBIT_NOTIFY_AT_NOTICES'], | 22 | notify_at_notices: ['ERRBIT_NOTIFY_AT_NOTICES'], |
21 | per_app_notify_at_notices: ['ERRBIT_PER_APP_NOTIFY_AT_NOTICES'], | 23 | per_app_notify_at_notices: ['ERRBIT_PER_APP_NOTIFY_AT_NOTICES'], |
24 | + log_location: ['ERRBIT_LOG_LOCATION'], | ||
25 | + log_level: ['ERRBIT_LOG_LEVEL'], | ||
22 | 26 | ||
23 | serve_static_assets: ['SERVE_STATIC_ASSETS'], | 27 | serve_static_assets: ['SERVE_STATIC_ASSETS'], |
24 | secret_key_base: ['SECRET_KEY_BASE'], | 28 | secret_key_base: ['SECRET_KEY_BASE'], |
config/mongo.rb
1 | +log_level = Logger.const_get Errbit::Config.log_level.upcase | ||
2 | + | ||
3 | +Mongoid.logger.level = log_level | ||
4 | +Mongo::Logger.level = log_level | ||
5 | + | ||
1 | Mongoid.configure do |config| | 6 | Mongoid.configure do |config| |
2 | uri = if Errbit::Config.mongo_url == 'mongodb://localhost' | 7 | uri = if Errbit::Config.mongo_url == 'mongodb://localhost' |
3 | "mongodb://localhost/errbit_#{Rails.env}" | 8 | "mongodb://localhost/errbit_#{Rails.env}" |
@@ -6,7 +11,7 @@ Mongoid.configure do |config| | @@ -6,7 +11,7 @@ Mongoid.configure do |config| | ||
6 | end | 11 | end |
7 | 12 | ||
8 | config.load_configuration({ | 13 | config.load_configuration({ |
9 | - sessions: { | 14 | + clients: { |
10 | default: { | 15 | default: { |
11 | uri: uri | 16 | uri: uri |
12 | } | 17 | } |
spec/acceptance/app_regenerate_api_key_spec.rb
@@ -52,8 +52,8 @@ feature "Create an application" do | @@ -52,8 +52,8 @@ feature "Create an application" do | ||
52 | fill_in 'app_name', :with => 'My new app' | 52 | fill_in 'app_name', :with => 'My new app' |
53 | click_on I18n.t('apps.new.add_app') | 53 | click_on I18n.t('apps.new.add_app') |
54 | page.has_content?(I18n.t('controllers.apps.flash.create.success')) | 54 | page.has_content?(I18n.t('controllers.apps.flash.create.success')) |
55 | - expect(App.where(:name => 'My new app').count).to eql 1 | ||
56 | - expect(App.where(:name => 'My new app 2').count).to eql 0 | 55 | + expect(App.where(:name => 'My new app').count).to eq 1 |
56 | + expect(App.where(:name => 'My new app 2').count).to eq 0 | ||
57 | 57 | ||
58 | 58 | ||
59 | click_on I18n.t('shared.navigation.apps') | 59 | click_on I18n.t('shared.navigation.apps') |
@@ -62,8 +62,8 @@ feature "Create an application" do | @@ -62,8 +62,8 @@ feature "Create an application" do | ||
62 | fill_in 'app_name', :with => 'My new app 2' | 62 | fill_in 'app_name', :with => 'My new app 2' |
63 | click_on I18n.t('apps.edit.update') | 63 | click_on I18n.t('apps.edit.update') |
64 | page.has_content?(I18n.t('controllers.apps.flash.update.success')) | 64 | page.has_content?(I18n.t('controllers.apps.flash.update.success')) |
65 | - expect(App.where(:name => 'My new app').count).to eql 0 | ||
66 | - expect(App.where(:name => 'My new app 2').count).to eql 1 | 65 | + expect(App.where(:name => 'My new app').count).to eq 0 |
66 | + expect(App.where(:name => 'My new app 2').count).to eq 1 | ||
67 | 67 | ||
68 | end | 68 | end |
69 | 69 |
spec/controllers/problems_controller_spec.rb
@@ -138,15 +138,13 @@ describe ProblemsController, type: 'controller' do | @@ -138,15 +138,13 @@ describe ProblemsController, type: 'controller' do | ||
138 | end | 138 | end |
139 | 139 | ||
140 | it "searches problems for given string" do | 140 | it "searches problems for given string" do |
141 | - get :search, :search => "Most important" | 141 | + get :search, :search => "\"Most important\"" |
142 | expect(controller.problems).to include(@problem1) | 142 | expect(controller.problems).to include(@problem1) |
143 | expect(controller.problems).to_not include(@problem2) | 143 | expect(controller.problems).to_not include(@problem2) |
144 | end | 144 | end |
145 | end | 145 | end |
146 | 146 | ||
147 | describe "GET /apps/:app_id/problems/:id" do | 147 | describe "GET /apps/:app_id/problems/:id" do |
148 | - #render_views | ||
149 | - | ||
150 | context 'when logged in as an admin' do | 148 | context 'when logged in as an admin' do |
151 | before do | 149 | before do |
152 | sign_in admin | 150 | sign_in admin |
@@ -250,8 +248,8 @@ describe ProblemsController, type: 'controller' do | @@ -250,8 +248,8 @@ describe ProblemsController, type: 'controller' do | ||
250 | before { sign_in admin } | 248 | before { sign_in admin } |
251 | 249 | ||
252 | context "when app has a issue tracker" do | 250 | context "when app has a issue tracker" do |
253 | - let(:notice) { Fabricate :notice } | ||
254 | - let(:problem) { notice.problem } | 251 | + let(:notice) { NoticeDecorator.new(Fabricate :notice) } |
252 | + let(:problem) { ProblemDecorator.new(notice.problem) } | ||
255 | let(:issue_tracker) do | 253 | let(:issue_tracker) do |
256 | Fabricate(:issue_tracker).tap do |t| | 254 | Fabricate(:issue_tracker).tap do |t| |
257 | t.instance_variable_set(:@tracker, ErrbitPlugin::MockIssueTracker.new(t.options)) | 255 | t.instance_variable_set(:@tracker, ErrbitPlugin::MockIssueTracker.new(t.options)) |
spec/fabricators/err_fabricator.rb
@@ -10,15 +10,19 @@ Fabricator :notice do | @@ -10,15 +10,19 @@ Fabricator :notice do | ||
10 | server_environment { {'environment-name' => 'production'} } | 10 | server_environment { {'environment-name' => 'production'} } |
11 | request {{ 'component' => 'foo', 'action' => 'bar' }} | 11 | request {{ 'component' => 'foo', 'action' => 'bar' }} |
12 | notifier {{ 'name' => 'Notifier', 'version' => '1', 'url' => 'http://toad.com' }} | 12 | notifier {{ 'name' => 'Notifier', 'version' => '1', 'url' => 'http://toad.com' }} |
13 | -end | ||
14 | 13 | ||
15 | -Fabricator :backtrace do | ||
16 | - lines(:count => 99) { Fabricate.build(:backtrace_line) } | 14 | + after_create do |
15 | + Problem.cache_notice(err.problem_id, self) | ||
16 | + problem.reload | ||
17 | + end | ||
17 | end | 18 | end |
18 | 19 | ||
19 | -Fabricator :backtrace_line do | ||
20 | - number { rand(999) } | ||
21 | - file { "/path/to/file/#{SecureRandom.hex(4)}.rb" } | ||
22 | - method(:method) { ActiveSupport.methods.shuffle.first } | 20 | +Fabricator :backtrace do |
21 | + lines(:count => 99) do | ||
22 | + { | ||
23 | + number: rand(999), | ||
24 | + file: "/path/to/file/#{SecureRandom.hex(4)}.rb", | ||
25 | + method: ActiveSupport.methods.shuffle.first | ||
26 | + } | ||
27 | + end | ||
23 | end | 28 | end |
24 | - |
spec/fabricators/problem_fabricator.rb
@@ -23,8 +23,7 @@ end | @@ -23,8 +23,7 @@ end | ||
23 | 23 | ||
24 | Fabricator(:problem_resolved, :from => :problem) do | 24 | Fabricator(:problem_resolved, :from => :problem) do |
25 | after_create do |pr| | 25 | after_create do |pr| |
26 | - Fabricate(:notice, | ||
27 | - :err => Fabricate(:err, :problem => pr)) | 26 | + Fabricate(:notice, :err => Fabricate(:err, :problem => pr)) |
28 | pr.resolve! | 27 | pr.resolve! |
29 | end | 28 | end |
30 | end | 29 | end |
spec/interactors/resolved_problem_clearer_spec.rb
@@ -19,16 +19,16 @@ describe ResolvedProblemClearer do | @@ -19,16 +19,16 @@ describe ResolvedProblemClearer do | ||
19 | } | 19 | } |
20 | end | 20 | end |
21 | it 'not repair database' do | 21 | it 'not repair database' do |
22 | - allow(Mongoid.default_session).to receive(:command).and_call_original | ||
23 | - expect(Mongoid.default_session).to_not receive(:command).with({:repairDatabase => 1}) | 22 | + allow(Mongoid.default_client).to receive(:command).and_call_original |
23 | + expect(Mongoid.default_client).to_not receive(:command).with({:repairDatabase => 1}) | ||
24 | resolved_problem_clearer.execute | 24 | resolved_problem_clearer.execute |
25 | end | 25 | end |
26 | end | 26 | end |
27 | 27 | ||
28 | context "with problem resolve" do | 28 | context "with problem resolve" do |
29 | before do | 29 | before do |
30 | - allow(Mongoid.default_session).to receive(:command).and_call_original | ||
31 | - allow(Mongoid.default_session).to receive(:command).with({:repairDatabase => 1}) | 30 | + allow(Mongoid.default_client).to receive(:command).and_call_original |
31 | + allow(Mongoid.default_client).to receive(:command).with({:repairDatabase => 1}) | ||
32 | problems.first.resolve! | 32 | problems.first.resolve! |
33 | problems.second.resolve! | 33 | problems.second.resolve! |
34 | end | 34 | end |
@@ -44,7 +44,7 @@ describe ResolvedProblemClearer do | @@ -44,7 +44,7 @@ describe ResolvedProblemClearer do | ||
44 | end | 44 | end |
45 | 45 | ||
46 | it 'repair database' do | 46 | it 'repair database' do |
47 | - expect(Mongoid.default_session).to receive(:command).with({:repairDatabase => 1}) | 47 | + expect(Mongoid.default_client).to receive(:command).with({:repairDatabase => 1}) |
48 | resolved_problem_clearer.execute | 48 | resolved_problem_clearer.execute |
49 | end | 49 | end |
50 | end | 50 | end |
spec/mailers/mailer_spec.rb
1 | shared_examples "a notification email" do | 1 | shared_examples "a notification email" do |
2 | it "should have X-Mailer header" do | 2 | it "should have X-Mailer header" do |
3 | - expect(@email).to have_header('X-Mailer', 'Errbit') | 3 | + expect(email).to have_header('X-Mailer', 'Errbit') |
4 | end | 4 | end |
5 | 5 | ||
6 | it "should have X-Errbit-Host header" do | 6 | it "should have X-Errbit-Host header" do |
7 | - expect(@email).to have_header('X-Errbit-Host', Errbit::Config.host) | 7 | + expect(email).to have_header('X-Errbit-Host', Errbit::Config.host) |
8 | end | 8 | end |
9 | 9 | ||
10 | it "should have Precedence header" do | 10 | it "should have Precedence header" do |
11 | - expect(@email).to have_header('Precedence', 'bulk') | 11 | + expect(email).to have_header('Precedence', 'bulk') |
12 | end | 12 | end |
13 | 13 | ||
14 | it "should have Auto-Submitted header" do | 14 | it "should have Auto-Submitted header" do |
15 | - expect(@email).to have_header('Auto-Submitted', 'auto-generated') | 15 | + expect(email).to have_header('Auto-Submitted', 'auto-generated') |
16 | end | 16 | end |
17 | 17 | ||
18 | it "should have X-Auto-Response-Suppress header" do | 18 | it "should have X-Auto-Response-Suppress header" do |
19 | # http://msdn.microsoft.com/en-us/library/ee219609(v=EXCHG.80).aspx | 19 | # http://msdn.microsoft.com/en-us/library/ee219609(v=EXCHG.80).aspx |
20 | - expect(@email).to have_header('X-Auto-Response-Suppress', 'OOF, AutoReply') | 20 | + expect(email).to have_header('X-Auto-Response-Suppress', 'OOF, AutoReply') |
21 | end | 21 | end |
22 | 22 | ||
23 | it "should send the email" do | 23 | it "should send the email" do |
24 | |||
24 | expect(ActionMailer::Base.deliveries.size).to eq 1 | 25 | expect(ActionMailer::Base.deliveries.size).to eq 1 |
25 | end | 26 | end |
26 | end | 27 | end |
@@ -30,44 +31,63 @@ describe Mailer do | @@ -30,44 +31,63 @@ describe Mailer do | ||
30 | include EmailSpec::Helpers | 31 | include EmailSpec::Helpers |
31 | include EmailSpec::Matchers | 32 | include EmailSpec::Matchers |
32 | 33 | ||
33 | - let(:notice) { Fabricate(:notice, :message => "class < ActionController::Base") } | ||
34 | - let!(:user) { Fabricate(:admin) } | 34 | + let(:notice) do |
35 | + n = Fabricate(:notice, message: "class < ActionController::Base") | ||
36 | + n.backtrace.lines.last[:file] = '[PROJECT_ROOT]/path/to/file.js' | ||
37 | + # notice.backtrace.update_attributes(lines: lines) | ||
38 | + n | ||
39 | + end | ||
35 | 40 | ||
36 | - before do | ||
37 | - ActionMailer::Base.deliveries = [] | ||
38 | - notice.backtrace.lines.last.update_attributes(:file => "[PROJECT_ROOT]/path/to/file.js") | ||
39 | - notice.app.update_attributes( | 41 | + let(:app) do |
42 | + a = notice.app | ||
43 | + a.update_attributes( | ||
40 | :asset_host => "http://example.com", | 44 | :asset_host => "http://example.com", |
41 | :notify_all_users => true | 45 | :notify_all_users => true |
42 | ) | 46 | ) |
43 | - notice.problem.update_attributes :notices_count => 3 | ||
44 | - | ||
45 | - @email = Mailer.err_notification(notice).deliver | 47 | + a |
48 | + end | ||
49 | + let(:problem) do | ||
50 | + p = notice.problem | ||
51 | + p.notices_count = 3 | ||
52 | + p | ||
53 | + end | ||
54 | + let!(:user) { Fabricate(:admin) } | ||
55 | + let(:error_report) do | ||
56 | + instance_double( | ||
57 | + 'ErrorReport', | ||
58 | + notice: notice, | ||
59 | + app: app, | ||
60 | + problem: problem | ||
61 | + ) | ||
62 | + end | ||
63 | + let(:email) do | ||
64 | + Mailer.err_notification(error_report).deliver | ||
46 | end | 65 | end |
47 | 66 | ||
48 | - it_should_behave_like "a notification email" | 67 | + before { email } |
49 | 68 | ||
69 | + it_should_behave_like "a notification email" | ||
50 | 70 | ||
51 | it "should html-escape the notice's message for the html part" do | 71 | it "should html-escape the notice's message for the html part" do |
52 | - expect(@email).to have_body_text("class < ActionController::Base") | 72 | + expect(email).to have_body_text("class < ActionController::Base") |
53 | end | 73 | end |
54 | 74 | ||
55 | it "should have inline css" do | 75 | it "should have inline css" do |
56 | - expect(@email).to have_body_text('<p class="backtrace" style="') | 76 | + expect(email).to have_body_text('<p class="backtrace" style="') |
57 | end | 77 | end |
58 | 78 | ||
59 | it "should have links to source files" do | 79 | it "should have links to source files" do |
60 | - expect(@email).to have_body_text('<a href="http://example.com/path/to/file.js" target="_blank">path/to/file.js') | 80 | + expect(email).to have_body_text('<a href="http://example.com/path/to/file.js" target="_blank">path/to/file.js') |
61 | end | 81 | end |
62 | 82 | ||
63 | it "should have the error count in the subject" do | 83 | it "should have the error count in the subject" do |
64 | - expect(@email.subject).to match( /^\(3\) / ) | 84 | + expect(email.subject).to match( /^\(3\) / ) |
65 | end | 85 | end |
66 | 86 | ||
67 | context 'with a very long message' do | 87 | context 'with a very long message' do |
68 | let(:notice) { Fabricate(:notice, :message => 6.times.collect{|a| "0123456789" }.join('')) } | 88 | let(:notice) { Fabricate(:notice, :message => 6.times.collect{|a| "0123456789" }.join('')) } |
69 | it "should truncate the long message" do | 89 | it "should truncate the long message" do |
70 | - expect(@email.subject).to match( / \d{47}\.{3}$/ ) | 90 | + expect(email.subject).to match( / \d{47}\.{3}$/ ) |
71 | end | 91 | end |
72 | end | 92 | end |
73 | end | 93 | end |
spec/models/backtrace_line_normalizer_spec.rb
@@ -1,25 +0,0 @@ | @@ -1,25 +0,0 @@ | ||
1 | -describe BacktraceLineNormalizer, type: 'model' do | ||
2 | - subject { described_class.new(raw_line).call } | ||
3 | - | ||
4 | - describe "sanitize" do | ||
5 | - context "unknown file and method" do | ||
6 | - let(:raw_line) { { 'number' => rand(999), 'file' => nil, 'method' => nil } } | ||
7 | - | ||
8 | - it "should replace nil file with [unknown source]" do | ||
9 | - expect(subject['file']).to eq "[unknown source]" | ||
10 | - end | ||
11 | - | ||
12 | - it "should replace nil method with [unknown method]" do | ||
13 | - expect(subject['method']).to eq "[unknown method]" | ||
14 | - end | ||
15 | - end | ||
16 | - | ||
17 | - context "in app file" do | ||
18 | - let(:raw_line) { { 'number' => rand(999), 'file' => "[PROJECT_ROOT]/assets/file.js?body=1", 'method' => nil } } | ||
19 | - | ||
20 | - it "should strip query strings from files" do | ||
21 | - expect(subject['file']).to eq "[PROJECT_ROOT]/assets/file.js" | ||
22 | - end | ||
23 | - end | ||
24 | - end | ||
25 | -end |
spec/models/backtrace_line_spec.rb
@@ -1,10 +0,0 @@ | @@ -1,10 +0,0 @@ | ||
1 | -describe BacktraceLine, type: 'model' do | ||
2 | - subject { described_class.new(raw_line) } | ||
3 | - | ||
4 | - describe "root at the start of decorated filename" do | ||
5 | - let(:raw_line) { { 'number' => rand(999), 'file' => '[PROJECT_ROOT]/app/controllers/pages_controller.rb', 'method' => ActiveSupport.methods.shuffle.first.to_s } } | ||
6 | - it "should leave leading root symbol in filepath" do | ||
7 | - expect(subject.decorated_path).to eq 'app/controllers/' | ||
8 | - end | ||
9 | - end | ||
10 | -end |
spec/models/backtrace_spec.rb
1 | describe Backtrace, type: 'model' do | 1 | describe Backtrace, type: 'model' do |
2 | - subject { described_class.new } | ||
3 | - | ||
4 | - its(:fingerprint) { should be_present } | ||
5 | - | ||
6 | - describe "#similar" do | ||
7 | - context "no similar backtrace" do | ||
8 | - its(:similar) { should be_nil } | 2 | + describe '.find_or_create' do |
3 | + let(:lines) do | ||
4 | + [ | ||
5 | + { 'number' => '123', 'file' => '/some/path/to.rb', 'method' => 'abc' }, | ||
6 | + { 'number' => '345', 'file' => '/path/to.rb', 'method' => 'dowhat' } | ||
7 | + ] | ||
9 | end | 8 | end |
9 | + let(:fingerprint) { Backtrace.generate_fingerprint(lines) } | ||
10 | 10 | ||
11 | - context "similar backtrace exist" do | ||
12 | - let!(:similar_backtrace) { | ||
13 | - b = Fabricate(:backtrace) | ||
14 | - b.fingerprint = fingerprint | ||
15 | - b.save! | ||
16 | - b | ||
17 | - } | ||
18 | - let(:fingerprint) { "fingerprint" } | ||
19 | - | ||
20 | - before { allow(subject).to receive(:fingerprint).and_return(fingerprint) } | ||
21 | - | ||
22 | - its(:similar) { should == similar_backtrace } | ||
23 | - end | ||
24 | - end | ||
25 | - | ||
26 | - describe "find_or_create" do | ||
27 | - subject { described_class.find_or_create(attributes) } | ||
28 | - let(:attributes) { double :attributes } | ||
29 | - let(:backtrace) { double :backtrace } | ||
30 | - | ||
31 | - before { allow(described_class).to receive(:new).and_return(backtrace) } | ||
32 | - | ||
33 | - context "no similar backtrace" do | ||
34 | - before { allow(backtrace).to receive(:similar).and_return(nil) } | ||
35 | - it "create new backtrace" do | ||
36 | - expect(described_class).to receive(:create).with(attributes) | 11 | + it 'create new backtrace' do |
12 | + backtrace = described_class.find_or_create(lines) | ||
37 | 13 | ||
38 | - described_class.find_or_create(attributes) | ||
39 | - end | 14 | + expect(backtrace.lines).to eq(lines) |
15 | + expect(backtrace.fingerprint).to eq(fingerprint) | ||
40 | end | 16 | end |
41 | 17 | ||
42 | - context "similar backtrace exist" do | ||
43 | - let(:similar_backtrace) { double :similar_backtrace } | ||
44 | - before { allow(backtrace).to receive(:similar).and_return(similar_backtrace) } | 18 | + it 'creates one backtrace for two identical ones' do |
19 | + described_class.find_or_create(lines) | ||
20 | + described_class.find_or_create(lines) | ||
45 | 21 | ||
46 | - it { should == similar_backtrace } | 22 | + expect(Backtrace.where(fingerprint: fingerprint).count).to eq(1) |
47 | end | 23 | end |
48 | end | 24 | end |
49 | end | 25 | end |
spec/models/error_report_spec.rb
@@ -17,259 +17,310 @@ module Airbrake | @@ -17,259 +17,310 @@ module Airbrake | ||
17 | end | 17 | end |
18 | 18 | ||
19 | describe ErrorReport do | 19 | describe ErrorReport do |
20 | - context "with notice without line of backtrace" do | ||
21 | - let(:xml){ | ||
22 | - Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read | ||
23 | - } | ||
24 | - | ||
25 | - let(:error_report) { | ||
26 | - ErrorReport.new(xml) | ||
27 | - } | ||
28 | - | ||
29 | - let!(:app) { | ||
30 | - Fabricate( | ||
31 | - :app, | ||
32 | - :api_key => 'APIKEY' | ||
33 | - ) | ||
34 | - } | ||
35 | - | ||
36 | - describe "#app" do | ||
37 | - it 'find the good app' do | ||
38 | - expect(error_report.app).to eq app | ||
39 | - end | 20 | + let(:xml){ |
21 | + Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read | ||
22 | + } | ||
23 | + | ||
24 | + let(:error_report) { ErrorReport.new(xml) } | ||
25 | + | ||
26 | + let!(:app) { | ||
27 | + Fabricate( | ||
28 | + :app, | ||
29 | + :api_key => 'APIKEY' | ||
30 | + ) | ||
31 | + } | ||
32 | + | ||
33 | + describe "#app" do | ||
34 | + it 'find the good app' do | ||
35 | + expect(error_report.app).to eq app | ||
40 | end | 36 | end |
37 | + end | ||
41 | 38 | ||
42 | - describe "#backtrace" do | ||
43 | - it 'should have valid backtrace' do | ||
44 | - expect(error_report.backtrace).to be_valid | ||
45 | - end | 39 | + describe "#backtrace" do |
40 | + it 'should have valid backtrace' do | ||
41 | + expect(error_report.backtrace).to be_valid | ||
46 | end | 42 | end |
43 | + end | ||
47 | 44 | ||
48 | - describe "#fingerprint_strategy" do | ||
49 | - it "should be possible to change how fingerprints are generated" do | ||
50 | - def error_report.fingerprint_strategy | ||
51 | - Class.new do | ||
52 | - def self.generate(*args) | ||
53 | - 'fingerprintzzz' | ||
54 | - end | 45 | + describe "#fingerprint_strategy" do |
46 | + it "should be possible to change how fingerprints are generated" do | ||
47 | + def error_report.fingerprint_strategy | ||
48 | + Class.new do | ||
49 | + def self.generate(*args) | ||
50 | + 'fingerprintzzz' | ||
55 | end | 51 | end |
56 | end | 52 | end |
57 | - | ||
58 | - expect(error_report.error.fingerprint).to eq('fingerprintzzz') | ||
59 | end | 53 | end |
54 | + | ||
55 | + expect(error_report.error.fingerprint).to eq('fingerprintzzz') | ||
60 | end | 56 | end |
57 | + end | ||
61 | 58 | ||
62 | - describe "#generate_notice!" do | ||
63 | - it "save a notice" do | 59 | + describe "#generate_notice!" do |
60 | + it "save a notice" do | ||
61 | + expect { | ||
62 | + error_report.generate_notice! | ||
63 | + }.to change { | ||
64 | + app.reload.problems.count | ||
65 | + }.by(1) | ||
66 | + end | ||
67 | + | ||
68 | + context "with notice generate by Airbrake gem" do | ||
69 | + let(:xml) { Airbrake::Notice.new( | ||
70 | + :exception => Exception.new, | ||
71 | + :api_key => 'APIKEY', | ||
72 | + :project_root => Rails.root | ||
73 | + ).to_xml } | ||
74 | + it 'save a notice' do | ||
64 | expect { | 75 | expect { |
65 | error_report.generate_notice! | 76 | error_report.generate_notice! |
66 | }.to change { | 77 | }.to change { |
67 | app.reload.problems.count | 78 | app.reload.problems.count |
68 | }.by(1) | 79 | }.by(1) |
69 | end | 80 | end |
81 | + end | ||
70 | 82 | ||
71 | - context "with notice generate by Airbrake gem" do | ||
72 | - let(:xml) { Airbrake::Notice.new( | ||
73 | - :exception => Exception.new, | ||
74 | - :api_key => 'APIKEY', | ||
75 | - :project_root => Rails.root | ||
76 | - ).to_xml } | ||
77 | - it 'save a notice' do | ||
78 | - expect { | ||
79 | - error_report.generate_notice! | ||
80 | - }.to change { | ||
81 | - app.reload.problems.count | ||
82 | - }.by(1) | ||
83 | - end | ||
84 | - end | 83 | + describe "notice create" do |
84 | + before { error_report.generate_notice! } | ||
85 | + subject { error_report.notice } | ||
86 | + its(:message) { 'HoptoadTestingException: Testing hoptoad via "rake hoptoad:test". If you can see this, it works.' } | ||
87 | + its(:framework) { should == 'Rails: 3.2.11' } | ||
85 | 88 | ||
86 | - describe "notice create" do | ||
87 | - before { error_report.generate_notice! } | ||
88 | - subject { error_report.notice } | ||
89 | - its(:message) { 'HoptoadTestingException: Testing hoptoad via "rake hoptoad:test". If you can see this, it works.' } | ||
90 | - its(:framework) { should == 'Rails: 3.2.11' } | 89 | + it 'has complete backtrace' do |
90 | + expect(subject.backtrace_lines.size).to eq 73 | ||
91 | + expect(subject.backtrace_lines.last['file']).to eq '[GEM_ROOT]/bin/rake' | ||
92 | + end | ||
91 | 93 | ||
92 | - it 'has complete backtrace' do | ||
93 | - expect(subject.backtrace_lines.size).to eq 73 | ||
94 | - expect(subject.backtrace_lines.last['file']).to eq '[GEM_ROOT]/bin/rake' | ||
95 | - end | ||
96 | - it 'has server_environement' do | ||
97 | - expect(subject.server_environment['environment-name']).to eq 'development' | ||
98 | - end | 94 | + it 'has server_environement' do |
95 | + expect(subject.server_environment['environment-name']).to eq 'development' | ||
96 | + end | ||
99 | 97 | ||
100 | - it 'has request' do | ||
101 | - expect(subject.request['url']).to eq 'http://example.org/verify/cupcake=fistfight&lovebird=doomsayer' | ||
102 | - expect(subject.request['params']['controller']).to eq 'application' | ||
103 | - end | 98 | + it 'has request' do |
99 | + expect(subject.request['url']).to eq 'http://example.org/verify/cupcake=fistfight&lovebird=doomsayer' | ||
100 | + expect(subject.request['params']['controller']).to eq 'application' | ||
101 | + end | ||
104 | 102 | ||
105 | - it 'has notifier' do | ||
106 | - expect(subject.notifier['name']).to eq 'Hoptoad Notifier' | ||
107 | - end | 103 | + it 'has notifier' do |
104 | + expect(subject.notifier['name']).to eq 'Hoptoad Notifier' | ||
105 | + end | ||
108 | 106 | ||
109 | - it 'get user_attributes' do | ||
110 | - expect(subject.user_attributes['id']).to eq '123' | ||
111 | - expect(subject.user_attributes['name']).to eq 'Mr. Bean' | ||
112 | - expect(subject.user_attributes['email']).to eq 'mr.bean@example.com' | ||
113 | - expect(subject.user_attributes['username']).to eq 'mrbean' | ||
114 | - end | 107 | + it 'get user_attributes' do |
108 | + expect(subject.user_attributes['id']).to eq '123' | ||
109 | + expect(subject.user_attributes['name']).to eq 'Mr. Bean' | ||
110 | + expect(subject.user_attributes['email']).to eq 'mr.bean@example.com' | ||
111 | + expect(subject.user_attributes['username']).to eq 'mrbean' | ||
112 | + end | ||
115 | 113 | ||
116 | - it 'valid env_vars' do | ||
117 | - # XML: <var key="SCRIPT_NAME"/> | ||
118 | - expect(subject.env_vars).to have_key('SCRIPT_NAME') | ||
119 | - expect(subject.env_vars['SCRIPT_NAME']).to be_nil # blank ends up nil | ||
120 | - | ||
121 | - # XML representation: | ||
122 | - # <var key="rack.session.options"> | ||
123 | - # <var key="secure">false</var> | ||
124 | - # <var key="httponly">true</var> | ||
125 | - # <var key="path">/</var> | ||
126 | - # <var key="expire_after"/> | ||
127 | - # <var key="domain"/> | ||
128 | - # <var key="id"/> | ||
129 | - # </var> | ||
130 | - expected = { | ||
131 | - 'secure' => 'false', | ||
132 | - 'httponly' => 'true', | ||
133 | - 'path' => '/', | ||
134 | - 'expire_after' => nil, | ||
135 | - 'domain' => nil, | ||
136 | - 'id' => nil | ||
137 | - } | ||
138 | - expect(subject.env_vars).to have_key('rack_session_options') | ||
139 | - expect(subject.env_vars['rack_session_options']).to eql(expected) | ||
140 | - end | 114 | + it 'valid env_vars' do |
115 | + # XML: <var key="SCRIPT_NAME"/> | ||
116 | + expect(subject.env_vars).to have_key('SCRIPT_NAME') | ||
117 | + expect(subject.env_vars['SCRIPT_NAME']).to be_nil # blank ends up nil | ||
118 | + | ||
119 | + # XML representation: | ||
120 | + # <var key="rack.session.options"> | ||
121 | + # <var key="secure">false</var> | ||
122 | + # <var key="httponly">true</var> | ||
123 | + # <var key="path">/</var> | ||
124 | + # <var key="expire_after"/> | ||
125 | + # <var key="domain"/> | ||
126 | + # <var key="id"/> | ||
127 | + # </var> | ||
128 | + expected = { | ||
129 | + 'secure' => 'false', | ||
130 | + 'httponly' => 'true', | ||
131 | + 'path' => '/', | ||
132 | + 'expire_after' => nil, | ||
133 | + 'domain' => nil, | ||
134 | + 'id' => nil | ||
135 | + } | ||
136 | + expect(subject.env_vars).to have_key('rack_session_options') | ||
137 | + expect(subject.env_vars['rack_session_options']).to eql(expected) | ||
141 | end | 138 | end |
142 | end | 139 | end |
140 | + end | ||
143 | 141 | ||
144 | - it 'save a notice assignes to err' do | 142 | + describe '#cache_attributes_on_problem' do |
143 | + it 'sets the latest notice properties on the problem' do | ||
145 | error_report.generate_notice! | 144 | error_report.generate_notice! |
146 | - expect(error_report.notice.err).to be_a(Err) | 145 | + problem = error_report.problem.reload |
146 | + notice = error_report.notice.reload | ||
147 | + | ||
148 | + expect(problem.environment).to eq('development') | ||
149 | + expect(problem.error_class).to eq('HoptoadTestingException') | ||
150 | + expect(problem.last_notice_at).to eq(notice.created_at) | ||
151 | + expect(problem.message).to eq(notice.message) | ||
152 | + expect(problem.where).to eq(notice.where) | ||
147 | end | 153 | end |
148 | 154 | ||
149 | - it 'memoize the notice' do | ||
150 | - expect { | ||
151 | - error_report.generate_notice! | ||
152 | - error_report.generate_notice! | ||
153 | - }.to change { | ||
154 | - Notice.count | ||
155 | - }.by(1) | 155 | + it 'unresolves the problem' do |
156 | + error_report.generate_notice! | ||
157 | + problem = error_report.problem | ||
158 | + problem.update( | ||
159 | + resolved_at: Time.now, | ||
160 | + resolved: true | ||
161 | + ) | ||
162 | + | ||
163 | + error_report = ErrorReport.new(xml) | ||
164 | + error_report.generate_notice! | ||
165 | + problem.reload | ||
166 | + | ||
167 | + expect(problem.resolved_at).to be(nil) | ||
168 | + expect(problem.resolved).to be(false) | ||
156 | end | 169 | end |
157 | 170 | ||
158 | - it 'find the correct err for the notice' do | ||
159 | - err = Fabricate(:err, :problem => Fabricate(:problem, :resolved => true)) | 171 | + it 'caches notice counts' do |
172 | + error_report.generate_notice! | ||
173 | + problem = error_report.problem | ||
174 | + problem.reload | ||
175 | + | ||
176 | + expect(problem.notices_count).to be(1) | ||
177 | + expect(problem.user_agents['382b0f5185773fa0f67a8ed8056c7759']['count']).to be(1) | ||
178 | + expect(problem.messages['9449f087eee0499e2d9029ae3dacaf53']['count']).to be(1) | ||
179 | + expect(problem.hosts['1bdf72e04d6b50c82a48c7e4dd38cc69']['count']).to be(1) | ||
180 | + end | ||
160 | 181 | ||
161 | - allow(error_report).to receive(:fingerprint).and_return(err.fingerprint) | 182 | + it 'increments notice counts' do |
183 | + error_report.generate_notice! | ||
184 | + error_report = ErrorReport.new(xml) | ||
185 | + error_report.generate_notice! | ||
186 | + problem = error_report.problem | ||
187 | + problem.reload | ||
162 | 188 | ||
163 | - expect { | ||
164 | - error_report.generate_notice! | ||
165 | - }.to change { | ||
166 | - error_report.error.resolved? | ||
167 | - }.from(true).to(false) | 189 | + expect(problem.notices_count).to be(2) |
190 | + expect(problem.user_agents['382b0f5185773fa0f67a8ed8056c7759']['count']).to be(2) | ||
191 | + expect(problem.messages['9449f087eee0499e2d9029ae3dacaf53']['count']).to be(2) | ||
192 | + expect(problem.hosts['1bdf72e04d6b50c82a48c7e4dd38cc69']['count']).to be(2) | ||
168 | end | 193 | end |
194 | + end | ||
169 | 195 | ||
170 | - context "with notification service configured" do | ||
171 | - before do | ||
172 | - app.notify_on_errs = true | ||
173 | - app.watchers.build(:email => 'foo@example.com') | ||
174 | - app.save | ||
175 | - end | ||
176 | - it 'send email' do | ||
177 | - notice = error_report.generate_notice! | ||
178 | - email = ActionMailer::Base.deliveries.last | ||
179 | - expect(email.to).to include(app.watchers.first.email) | ||
180 | - expect(email.subject).to include(notice.message.truncate(50)) | ||
181 | - expect(email.subject).to include("[#{app.name}]") | ||
182 | - expect(email.subject).to include("[#{notice.environment_name}]") | ||
183 | - end | 196 | + it 'save a notice assignes to err' do |
197 | + error_report.generate_notice! | ||
198 | + expect(error_report.notice.err).to be_a(Err) | ||
199 | + end | ||
184 | 200 | ||
185 | - context "with xml without request section" do | ||
186 | - let(:xml){ | ||
187 | - Rails.root.join('spec','fixtures','hoptoad_test_notice_without_request_section.xml').read | ||
188 | - } | ||
189 | - it "save a notice" do | ||
190 | - expect { | ||
191 | - error_report.generate_notice! | ||
192 | - }.to change { | ||
193 | - app.reload.problems.count | ||
194 | - }.by(1) | ||
195 | - end | 201 | + it 'memoize the notice' do |
202 | + expect { | ||
203 | + error_report.generate_notice! | ||
204 | + error_report.generate_notice! | ||
205 | + }.to change { | ||
206 | + Notice.count | ||
207 | + }.by(1) | ||
208 | + end | ||
209 | + | ||
210 | + it 'find the correct err for the notice' do | ||
211 | + error_report.generate_notice! | ||
212 | + error_report.problem.resolve! | ||
213 | + | ||
214 | + expect { | ||
215 | + ErrorReport.new(xml).generate_notice! | ||
216 | + }.to change { | ||
217 | + error_report.problem.reload.resolved? | ||
218 | + }.from(true).to(false) | ||
219 | + end | ||
220 | + | ||
221 | + context "with notification service configured" do | ||
222 | + before do | ||
223 | + app.notify_on_errs = true | ||
224 | + app.watchers.build(:email => 'foo@example.com') | ||
225 | + app.save | ||
226 | + end | ||
227 | + | ||
228 | + it 'send email' do | ||
229 | + notice = error_report.generate_notice! | ||
230 | + email = ActionMailer::Base.deliveries.last | ||
231 | + expect(email.to).to include(app.watchers.first.email) | ||
232 | + expect(email.subject).to include(notice.message.truncate(50)) | ||
233 | + expect(email.subject).to include("[#{app.name}]") | ||
234 | + expect(email.subject).to include("[#{notice.environment_name}]") | ||
235 | + end | ||
236 | + | ||
237 | + context "with xml without request section" do | ||
238 | + let(:xml){ | ||
239 | + Rails.root.join('spec','fixtures','hoptoad_test_notice_without_request_section.xml').read | ||
240 | + } | ||
241 | + it "save a notice" do | ||
242 | + expect { | ||
243 | + error_report.generate_notice! | ||
244 | + }.to change { | ||
245 | + app.reload.problems.count | ||
246 | + }.by(1) | ||
196 | end | 247 | end |
248 | + end | ||
197 | 249 | ||
198 | - context "with xml with only a single line of backtrace" do | ||
199 | - let(:xml){ | ||
200 | - Rails.root.join('spec','fixtures','hoptoad_test_notice_with_one_line_of_backtrace.xml').read | ||
201 | - } | ||
202 | - it "save a notice" do | ||
203 | - expect { | ||
204 | - error_report.generate_notice! | ||
205 | - }.to change { | ||
206 | - app.reload.problems.count | ||
207 | - }.by(1) | ||
208 | - end | 250 | + context "with xml with only a single line of backtrace" do |
251 | + let(:xml){ | ||
252 | + Rails.root.join('spec','fixtures','hoptoad_test_notice_with_one_line_of_backtrace.xml').read | ||
253 | + } | ||
254 | + it "save a notice" do | ||
255 | + expect { | ||
256 | + error_report.generate_notice! | ||
257 | + }.to change { | ||
258 | + app.reload.problems.count | ||
259 | + }.by(1) | ||
209 | end | 260 | end |
210 | end | 261 | end |
262 | + end | ||
211 | 263 | ||
212 | - describe "#valid?" do | ||
213 | - context "with valid error report" do | ||
214 | - it "return true" do | ||
215 | - expect(error_report.valid?).to be true | ||
216 | - end | 264 | + describe "#valid?" do |
265 | + context "with valid error report" do | ||
266 | + it "return true" do | ||
267 | + expect(error_report.valid?).to be true | ||
217 | end | 268 | end |
218 | - context "with not valid api_key" do | ||
219 | - before do | ||
220 | - App.where(:api_key => app.api_key).delete_all | ||
221 | - end | ||
222 | - it "return false" do | ||
223 | - expect(error_report.valid?).to be false | ||
224 | - end | 269 | + end |
270 | + context "with not valid api_key" do | ||
271 | + before do | ||
272 | + App.where(:api_key => app.api_key).delete_all | ||
273 | + end | ||
274 | + it "return false" do | ||
275 | + expect(error_report.valid?).to be false | ||
225 | end | 276 | end |
226 | end | 277 | end |
278 | + end | ||
227 | 279 | ||
228 | - describe "#notice" do | ||
229 | - context "before generate_notice!" do | ||
230 | - it 'return nil' do | ||
231 | - expect(error_report.notice).to be nil | ||
232 | - end | 280 | + describe "#notice" do |
281 | + context "before generate_notice!" do | ||
282 | + it 'return nil' do | ||
283 | + expect(error_report.notice).to be nil | ||
233 | end | 284 | end |
285 | + end | ||
234 | 286 | ||
235 | - context "after generate_notice!" do | ||
236 | - before do | ||
237 | - error_report.generate_notice! | ||
238 | - end | ||
239 | - | ||
240 | - it 'return the notice' do | ||
241 | - expect(error_report.notice).to be_a Notice | ||
242 | - end | 287 | + context "after generate_notice!" do |
288 | + before do | ||
289 | + error_report.generate_notice! | ||
290 | + end | ||
243 | 291 | ||
292 | + it 'return the notice' do | ||
293 | + expect(error_report.notice).to be_a Notice | ||
244 | end | 294 | end |
295 | + | ||
245 | end | 296 | end |
297 | + end | ||
246 | 298 | ||
247 | - describe "#should_keep?" do | ||
248 | - context "with current app version not set" do | ||
249 | - before do | ||
250 | - error_report.app.current_app_version = nil | ||
251 | - error_report.server_environment['app-version'] = '1.0' | ||
252 | - end | 299 | + describe "#should_keep?" do |
300 | + context "with current app version not set" do | ||
301 | + before do | ||
302 | + error_report.app.current_app_version = nil | ||
303 | + error_report.server_environment['app-version'] = '1.0' | ||
304 | + end | ||
253 | 305 | ||
254 | - it "return true" do | ||
255 | - expect(error_report.should_keep?).to be true | ||
256 | - end | 306 | + it "return true" do |
307 | + expect(error_report.should_keep?).to be true | ||
257 | end | 308 | end |
309 | + end | ||
258 | 310 | ||
259 | - context "with current app version set" do | ||
260 | - before do | ||
261 | - error_report.app.current_app_version = '1.0' | ||
262 | - end | 311 | + context "with current app version set" do |
312 | + before do | ||
313 | + error_report.app.current_app_version = '1.0' | ||
314 | + end | ||
263 | 315 | ||
264 | - it "return true if current or newer" do | ||
265 | - error_report.server_environment['app-version'] = '1.0' | ||
266 | - expect(error_report.should_keep?).to be true | ||
267 | - end | 316 | + it "return true if current or newer" do |
317 | + error_report.server_environment['app-version'] = '1.0' | ||
318 | + expect(error_report.should_keep?).to be true | ||
319 | + end | ||
268 | 320 | ||
269 | - it "return false if older" do | ||
270 | - error_report.server_environment['app-version'] = '0.9' | ||
271 | - expect(error_report.should_keep?).to be false | ||
272 | - end | 321 | + it "return false if older" do |
322 | + error_report.server_environment['app-version'] = '0.9' | ||
323 | + expect(error_report.should_keep?).to be false | ||
273 | end | 324 | end |
274 | end | 325 | end |
275 | end | 326 | end |
spec/models/fingerprint/md5_spec.rb
1 | describe Fingerprint::MD5, type: 'model' do | 1 | describe Fingerprint::MD5, type: 'model' do |
2 | context 'being created' do | 2 | context 'being created' do |
3 | let(:backtrace) do | 3 | let(:backtrace) do |
4 | - Backtrace.create(:raw => [ | 4 | + Backtrace.find_or_create([ |
5 | { | 5 | { |
6 | "number"=>"17", | 6 | "number"=>"17", |
7 | "file"=>"[GEM_ROOT]/gems/activesupport/lib/active_support/callbacks.rb", | 7 | "file"=>"[GEM_ROOT]/gems/activesupport/lib/active_support/callbacks.rb", |
@@ -14,10 +14,9 @@ describe Fingerprint::MD5, type: 'model' do | @@ -14,10 +14,9 @@ describe Fingerprint::MD5, type: 'model' do | ||
14 | 14 | ||
15 | context "with same backtrace" do | 15 | context "with same backtrace" do |
16 | let(:backtrace_2) do | 16 | let(:backtrace_2) do |
17 | - backtrace | ||
18 | - backtrace.lines.last.method = '_run__FRAGMENT__process_action__FRAGMENT__callbacks' | ||
19 | - backtrace.save | ||
20 | - backtrace | 17 | + new_lines = backtrace.lines.dup |
18 | + new_lines.last[:method] = '_run__FRAGMENT__process_action__FRAGMENT__callbacks' | ||
19 | + Backtrace.find_or_create(new_lines) | ||
21 | end | 20 | end |
22 | 21 | ||
23 | it "normalizes the fingerprint of generated methods" do | 22 | it "normalizes the fingerprint of generated methods" do |
@@ -27,10 +26,9 @@ describe Fingerprint::MD5, type: 'model' do | @@ -27,10 +26,9 @@ describe Fingerprint::MD5, type: 'model' do | ||
27 | 26 | ||
28 | context "with same backtrace where FRAGMENT has not been extracted" do | 27 | context "with same backtrace where FRAGMENT has not been extracted" do |
29 | let(:backtrace_2) do | 28 | let(:backtrace_2) do |
30 | - backtrace | ||
31 | - backtrace.lines.last.method = '_run__998857585768765__process_action__1231231312321313__callbacks' | ||
32 | - backtrace.save | ||
33 | - backtrace | 29 | + new_lines = backtrace.lines.dup |
30 | + new_lines.last[:method] = '_run__998857585768765__process_action__1231231312321313__callbacks' | ||
31 | + Backtrace.find_or_create(new_lines) | ||
34 | end | 32 | end |
35 | 33 | ||
36 | it "normalizes the fingerprint of generated methods" do | 34 | it "normalizes the fingerprint of generated methods" do |
spec/models/fingerprint/sha1_spec.rb
1 | describe Fingerprint::Sha1, type: 'model' do | 1 | describe Fingerprint::Sha1, type: 'model' do |
2 | context '#generate' do | 2 | context '#generate' do |
3 | let(:backtrace) { | 3 | let(:backtrace) { |
4 | - Backtrace.create(:raw => [ | 4 | + Backtrace.find_or_create([ |
5 | {"number"=>"425", "file"=>"[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/callbacks.rb", "method"=>"_run__2115867319__process_action__262109504__callbacks"}, | 5 | {"number"=>"425", "file"=>"[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/callbacks.rb", "method"=>"_run__2115867319__process_action__262109504__callbacks"}, |
6 | {"number"=>"404", "file"=>"[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/callbacks.rb", "method"=>"send"}, | 6 | {"number"=>"404", "file"=>"[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/callbacks.rb", "method"=>"send"}, |
7 | {"number"=>"404", "file"=>"[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/callbacks.rb", "method"=>"_run_process_action_callbacks"} | 7 | {"number"=>"404", "file"=>"[GEM_ROOT]/gems/activesupport-3.0.0.rc/lib/active_support/callbacks.rb", "method"=>"_run_process_action_callbacks"} |
@@ -19,11 +19,9 @@ describe Fingerprint::Sha1, type: 'model' do | @@ -19,11 +19,9 @@ describe Fingerprint::Sha1, type: 'model' do | ||
19 | 19 | ||
20 | context "with different backtrace with only last line change" do | 20 | context "with different backtrace with only last line change" do |
21 | let(:backtrace_2) { | 21 | let(:backtrace_2) { |
22 | - backtrace | ||
23 | - backtrace.lines.last.number = 401 | ||
24 | - backtrace.send(:generate_fingerprint) | ||
25 | - backtrace.save | ||
26 | - backtrace | 22 | + new_lines = backtrace.lines.dup |
23 | + new_lines.last[:number] = 401 | ||
24 | + Backtrace.find_or_create backtrace.lines | ||
27 | } | 25 | } |
28 | it 'should not same fingerprint' do | 26 | it 'should not same fingerprint' do |
29 | expect( | 27 | expect( |
spec/models/notice_observer_spec.rb
1 | describe "Callback on Notice", type: 'model' do | 1 | describe "Callback on Notice", type: 'model' do |
2 | - describe "email notifications (configured individually for each app)" do | 2 | + let(:notice_attrs_for) do |
3 | + ->(api_key) do | ||
4 | + { | ||
5 | + error_class: "HoptoadTestingException", | ||
6 | + message: "some message", | ||
7 | + backtrace: [ | ||
8 | + { | ||
9 | + "number"=>"425", | ||
10 | + "file"=>"[GEM_ROOT]/callbacks.rb", | ||
11 | + "method"=>"__callbacks" | ||
12 | + } | ||
13 | + ], | ||
14 | + request: { "component" => "application" }, | ||
15 | + server_environment: { | ||
16 | + "project-root" => "/path/to/sample/project", | ||
17 | + "environment-name" => "development" | ||
18 | + }, | ||
19 | + api_key: api_key, | ||
20 | + notifier: { | ||
21 | + "name"=>"Hoptoad Notifier", | ||
22 | + "version"=>"2.3.2", | ||
23 | + "url"=>"http://hoptoadapp.com" | ||
24 | + }, | ||
25 | + framework: "Rails: 3.2.11" | ||
26 | + } | ||
27 | + end | ||
28 | + end | ||
29 | + | ||
30 | + describe 'email notifications (configured individually for each app)' do | ||
31 | + let(:notice_attrs) { notice_attrs_for.call(app.api_key) } | ||
3 | custom_thresholds = [2, 4, 8, 16, 32, 64] | 32 | custom_thresholds = [2, 4, 8, 16, 32, 64] |
33 | + let(:app) do | ||
34 | + Fabricate(:app_with_watcher, email_at_notices: custom_thresholds) | ||
35 | + end | ||
4 | 36 | ||
5 | before do | 37 | before do |
6 | Errbit::Config.per_app_email_at_notices = true | 38 | Errbit::Config.per_app_email_at_notices = true |
7 | - @app = Fabricate(:app_with_watcher, :email_at_notices => custom_thresholds) | ||
8 | - @err = Fabricate(:err, :problem => Fabricate(:problem, :app => @app)) | 39 | + error_report = ErrorReport.new(notice_attrs) |
40 | + error_report.generate_notice! | ||
41 | + @problem = error_report.notice.err.problem | ||
9 | end | 42 | end |
10 | 43 | ||
11 | - after do | ||
12 | - Errbit::Config.per_app_email_at_notices = false | ||
13 | - end | 44 | + after { Errbit::Config.per_app_email_at_notices = false } |
14 | 45 | ||
15 | custom_thresholds.each do |threshold| | 46 | custom_thresholds.each do |threshold| |
16 | it "sends an email notification after #{threshold} notice(s)" do | 47 | it "sends an email notification after #{threshold} notice(s)" do |
17 | - allow(@err.problem).to receive(:notices_count).and_return(threshold) | 48 | + # set to just before the threshold |
49 | + @problem.update_attributes notices_count: threshold - 1 | ||
50 | + | ||
18 | expect(Mailer).to receive(:err_notification). | 51 | expect(Mailer).to receive(:err_notification). |
19 | and_return(double('email', :deliver => true)) | 52 | and_return(double('email', :deliver => true)) |
20 | - Fabricate(:notice, :err => @err) | 53 | + |
54 | + error_report = ErrorReport.new(notice_attrs) | ||
55 | + error_report.generate_notice! | ||
21 | end | 56 | end |
22 | end | 57 | end |
23 | - end | ||
24 | 58 | ||
25 | - describe "email notifications for a resolved issue" do | ||
26 | - before do | ||
27 | - Errbit::Config.per_app_email_at_notices = true | ||
28 | - @app = Fabricate(:app_with_watcher, :email_at_notices => [1]) | ||
29 | - @err = Fabricate(:err, :problem => Fabricate(:problem, :app => @app, :notices_count => 100)) | ||
30 | - end | 59 | + it "doesn't email after 5 notices" do |
60 | + @problem.update_attributes notices_count: 5 | ||
31 | 61 | ||
32 | - after do | ||
33 | - Errbit::Config.per_app_email_at_notices = false | ||
34 | - end | 62 | + expect(Mailer).to_not receive(:err_notification) |
35 | 63 | ||
36 | - it "should send email notification after 1 notice since an error has been resolved" do | ||
37 | - @err.problem.resolve! | ||
38 | - expect(Mailer).to receive(:err_notification). | ||
39 | - and_return(double('email', :deliver => true)) | ||
40 | - Fabricate(:notice, :err => @err) | 64 | + error_report = ErrorReport.new(notice_attrs) |
65 | + error_report.generate_notice! | ||
41 | end | 66 | end |
42 | - it 'self notify if mailer failed' do | ||
43 | - @err.problem.resolve! | ||
44 | - expect(Mailer).to receive(:err_notification). | ||
45 | - and_raise(ArgumentError) | 67 | + |
68 | + it 'notify self if mailer fails' do | ||
69 | + expect(Mailer).to receive(:err_notification).and_raise(ArgumentError) | ||
46 | expect(HoptoadNotifier).to receive(:notify) | 70 | expect(HoptoadNotifier).to receive(:notify) |
47 | - Fabricate(:notice, :err => @err) | 71 | + ErrorReport.new(notice_attrs).generate_notice! |
48 | end | 72 | end |
49 | end | 73 | end |
50 | 74 | ||
51 | - describe "should send a notification if a notification service is configured with defaults" do | ||
52 | - let(:app) { Fabricate(:app, :email_at_notices => [1], :notification_service => Fabricate(:campfire_notification_service))} | ||
53 | - let(:err) { Fabricate(:err, :problem => Fabricate(:problem, :app => app, :notices_count => 100)) } | ||
54 | - let(:backtrace) { Fabricate(:backtrace) } | ||
55 | - | ||
56 | - before do | ||
57 | - Errbit::Config.per_app_email_at_notices = true | 75 | + describe 'email notifications for resolved issues' do |
76 | + let(:notification_service) { Fabricate(:campfire_notification_service) } | ||
77 | + let(:app) do | ||
78 | + Fabricate( | ||
79 | + :app_with_watcher, | ||
80 | + notify_on_errs: true, | ||
81 | + email_at_notices: [1,100] | ||
82 | + ) | ||
58 | end | 83 | end |
84 | + let(:notice_attrs) { notice_attrs_for.call(app.api_key) } | ||
59 | 85 | ||
60 | - after do | ||
61 | - Errbit::Config.per_app_email_at_notices = false | ||
62 | - end | 86 | + before { Errbit::Config.per_app_email_at_notices = true } |
87 | + after { Errbit::Config.per_app_email_at_notices = false } | ||
63 | 88 | ||
64 | - it "should create a campfire notification" do | ||
65 | - expect(app.notification_service).to receive(:create_notification) | 89 | + it 'sends email the first time after the error is resolved' do |
90 | + error_report = ErrorReport.new(notice_attrs) | ||
91 | + error_report.generate_notice! | ||
92 | + err = error_report.notice.err | ||
66 | 93 | ||
67 | - Notice.create!(:err => err, :message => 'FooError: Too Much Bar', :server_environment => {'environment-name' => 'production'}, | ||
68 | - :backtrace => backtrace, :notifier => { 'name' => 'Notifier', 'version' => '1', 'url' => 'http://toad.com' }) | ||
69 | - end | ||
70 | - end | 94 | + err.problem.update_attributes notices_count: 99 |
95 | + err.problem.resolve! | ||
71 | 96 | ||
72 | - describe "send a notification if a notification service is configured with defaults but failed" do | ||
73 | - let(:app) { Fabricate(:app_with_watcher, | ||
74 | - :notify_on_errs => true, | ||
75 | - :email_at_notices => [1, 100], :notification_service => Fabricate(:campfire_notification_service))} | ||
76 | - let(:err) { Fabricate(:err, :problem => Fabricate(:problem, :app => app, :notices_count => 99)) } | ||
77 | - let(:backtrace) { Fabricate(:backtrace) } | 97 | + expect(Mailer).to receive(:err_notification) |
98 | + .and_return(double('email', :deliver => true)) | ||
78 | 99 | ||
79 | - before do | ||
80 | - Errbit::Config.per_app_email_at_notices = true | 100 | + ErrorReport.new(notice_attrs).generate_notice! |
81 | end | 101 | end |
102 | + end | ||
82 | 103 | ||
83 | - after do | ||
84 | - Errbit::Config.per_app_email_at_notices = false | 104 | + describe 'send email when notification service is configured but fails' do |
105 | + let(:notification_service) {Fabricate(:campfire_notification_service)} | ||
106 | + let(:app) do | ||
107 | + Fabricate( | ||
108 | + :app_with_watcher, | ||
109 | + notify_on_errs: true, | ||
110 | + notification_service: notification_service | ||
111 | + ) | ||
85 | end | 112 | end |
113 | + let(:notice_attrs) { notice_attrs_for.call(app.api_key) } | ||
114 | + | ||
115 | + before { Errbit::Config.per_app_notify_at_notices = true } | ||
116 | + after { Errbit::Config.per_app_notify_at_notices = false } | ||
117 | + | ||
118 | + it 'sends email' do | ||
119 | + error_report = ErrorReport.new(notice_attrs) | ||
86 | 120 | ||
87 | - it "send email" do | ||
88 | - expect(app.notification_service).to receive(:create_notification).and_raise(ArgumentError) | ||
89 | - expect(Mailer).to receive(:err_notification).and_return(double(:deliver => true)) | 121 | + expect(error_report.app.notification_service) |
122 | + .to receive(:create_notification).and_raise(ArgumentError) | ||
123 | + expect(Mailer) | ||
124 | + .to receive(:err_notification).and_return(double(:deliver => true)) | ||
90 | 125 | ||
91 | - Notice.create!(:err => err, :message => 'FooError: Too Much Bar', :server_environment => {'environment-name' => 'production'}, | ||
92 | - :backtrace => backtrace, :notifier => { 'name' => 'Notifier', 'version' => '1', 'url' => 'http://toad.com' }) | 126 | + error_report.generate_notice! |
93 | end | 127 | end |
94 | end | 128 | end |
95 | 129 | ||
96 | - describe "should not send a notification if a notification service is not configured" do | ||
97 | - let(:app) { Fabricate(:app, :email_at_notices => [1], :notification_service => Fabricate(:notification_service))} | ||
98 | - let(:err) { Fabricate(:err, :problem => Fabricate(:problem, :app => app, :notices_count => 100)) } | ||
99 | - let(:backtrace) { Fabricate(:backtrace) } | 130 | + describe 'should not send a notification if a notification service is not' \ |
131 | + 'configured' do | ||
100 | 132 | ||
101 | - before do | ||
102 | - Errbit::Config.per_app_email_at_notices = true | ||
103 | - end | 133 | + let(:notification_service) { Fabricate(:notification_service) } |
134 | + let(:app) { Fabricate(:app, notification_service: notification_service )} | ||
135 | + let(:notice_attrs) { notice_attrs_for.call(app.api_key) } | ||
104 | 136 | ||
105 | - after do | ||
106 | - Errbit::Config.per_app_email_at_notices = false | ||
107 | - end | 137 | + before { Errbit::Config.per_app_notify_at_notices = true } |
138 | + after { Errbit::Config.per_app_notify_at_notices = false } | ||
108 | 139 | ||
109 | it "should not create a campfire notification" do | 140 | it "should not create a campfire notification" do |
110 | - expect(app.notification_service).to_not receive(:create_notification) | ||
111 | - | ||
112 | - Notice.create!(:err => err, :message => 'FooError: Too Much Bar', :server_environment => {'environment-name' => 'production'}, | ||
113 | - :backtrace => backtrace, :notifier => { 'name' => 'Notifier', 'version' => '1', 'url' => 'http://toad.com' }) | 141 | + error_report = ErrorReport.new(notice_attrs) |
142 | + expect(error_report.app.notification_service).to_not receive(:create_notification) | ||
143 | + error_report.generate_notice! | ||
114 | end | 144 | end |
115 | end | 145 | end |
116 | 146 | ||
117 | describe 'hipcat notifications' do | 147 | describe 'hipcat notifications' do |
118 | - let(:app) { Fabricate(:app, :email_at_notices => [1], :notification_service => Fabricate(:hipchat_notification_service))} | ||
119 | - let(:err) { Fabricate(:err, :problem => Fabricate(:problem, :app => app, :notices_count => 100)) } | 148 | + let(:notification_service) { Fabricate(:hipchat_notification_service) } |
149 | + let(:notice_attrs) { notice_attrs_for.call(app.api_key) } | ||
150 | + let(:app) { Fabricate(:app, notification_service: notification_service) } | ||
120 | 151 | ||
121 | - before do | ||
122 | - Errbit::Config.per_app_email_at_notices = true | ||
123 | - end | ||
124 | - | ||
125 | - after do | ||
126 | - Errbit::Config.per_app_email_at_notices = false | ||
127 | - end | 152 | + before { Errbit::Config.per_app_notify_at_notices = true } |
153 | + after { Errbit::Config.per_app_notify_at_notices = false } | ||
128 | 154 | ||
129 | it 'creates a hipchat notification' do | 155 | it 'creates a hipchat notification' do |
130 | - expect(app.notification_service).to receive(:create_notification) | ||
131 | - | ||
132 | - Fabricate(:notice, :err => err) | 156 | + error_report = ErrorReport.new(notice_attrs) |
157 | + expect(error_report.app.notification_service) | ||
158 | + .to receive(:create_notification) | ||
159 | + error_report.generate_notice! | ||
133 | end | 160 | end |
134 | end | 161 | end |
135 | 162 | ||
136 | describe "should send a notification at desired intervals" do | 163 | describe "should send a notification at desired intervals" do |
137 | - let(:app) { Fabricate(:app, :email_at_notices => [1], :notification_service => Fabricate(:campfire_notification_service, :notify_at_notices => [1,2]))} | ||
138 | - let(:backtrace) { Fabricate(:backtrace) } | ||
139 | - | ||
140 | - before do | ||
141 | - Errbit::Config.per_app_email_at_notices = true | 164 | + let(:notification_service) do |
165 | + Fabricate(:campfire_notification_service, notify_at_notices: [1,2]) | ||
142 | end | 166 | end |
167 | + let(:app) { Fabricate(:app, notification_service: notification_service) } | ||
168 | + let(:notice_attrs) { notice_attrs_for.call(app.api_key) } | ||
143 | 169 | ||
144 | - after do | ||
145 | - Errbit::Config.per_app_email_at_notices = false | ||
146 | - end | 170 | + before { Errbit::Config.per_app_notify_at_notices = true } |
171 | + after { Errbit::Config.per_app_notify_at_notices = false } | ||
147 | 172 | ||
148 | it "should create a campfire notification on first notice" do | 173 | it "should create a campfire notification on first notice" do |
149 | - err = Fabricate(:err, :problem => Fabricate(:problem, :app => app, :notices_count => 1)) | ||
150 | - expect(app.notification_service).to receive(:create_notification) | ||
151 | - | ||
152 | - Notice.create!(:err => err, :message => 'FooError: Too Much Bar', :server_environment => {'environment-name' => 'production'}, | ||
153 | - :backtrace => backtrace, :notifier => { 'name' => 'Notifier', 'version' => '1', 'url' => 'http://toad.com' }) | 174 | + error_report = ErrorReport.new(notice_attrs) |
175 | + expect(error_report.app.notification_service) | ||
176 | + .to receive(:create_notification) | ||
177 | + error_report.generate_notice! # one | ||
154 | end | 178 | end |
155 | 179 | ||
156 | it "should create a campfire notification on second notice" do | 180 | it "should create a campfire notification on second notice" do |
157 | - err = Fabricate(:err, :problem => Fabricate(:problem, :app => app, :notices_count => 1)) | ||
158 | - expect(app.notification_service).to receive(:create_notification) | ||
159 | - | ||
160 | - Notice.create!(:err => err, :message => 'FooError: Too Much Bar', :server_environment => {'environment-name' => 'production'}, | ||
161 | - :backtrace => backtrace, :notifier => { 'name' => 'Notifier', 'version' => '1', 'url' => 'http://toad.com' }) | 181 | + ErrorReport.new(notice_attrs).generate_notice! # one |
182 | + error_report = ErrorReport.new(notice_attrs) | ||
183 | + expect(error_report.app.notification_service) | ||
184 | + .to receive(:create_notification) | ||
185 | + error_report.generate_notice! # two | ||
162 | end | 186 | end |
163 | 187 | ||
164 | it "should not create a campfire notification on third notice" do | 188 | it "should not create a campfire notification on third notice" do |
165 | - err = Fabricate(:err, :problem => Fabricate(:problem, :app => app, :notices_count => 1)) | ||
166 | - expect(app.notification_service).to receive(:create_notification) | ||
167 | - | ||
168 | - Notice.create!(:err => err, :message => 'FooError: Too Much Bar', :server_environment => {'environment-name' => 'production'}, | ||
169 | - :backtrace => backtrace, :notifier => { 'name' => 'Notifier', 'version' => '1', 'url' => 'http://toad.com' }) | 189 | + ErrorReport.new(notice_attrs).generate_notice! # one |
190 | + ErrorReport.new(notice_attrs).generate_notice! # two | ||
191 | + error_report = ErrorReport.new(notice_attrs) | ||
192 | + expect(error_report.app.notification_service) | ||
193 | + .to_not receive(:create_notification) | ||
194 | + error_report.generate_notice! # three | ||
170 | end | 195 | end |
171 | end | 196 | end |
172 | end | 197 | end |
spec/models/notice_spec.rb
@@ -3,7 +3,7 @@ describe Notice, type: 'model' do | @@ -3,7 +3,7 @@ describe Notice, type: 'model' do | ||
3 | it 'requires a backtrace' do | 3 | it 'requires a backtrace' do |
4 | notice = Fabricate.build(:notice, :backtrace => nil) | 4 | notice = Fabricate.build(:notice, :backtrace => nil) |
5 | expect(notice).to_not be_valid | 5 | expect(notice).to_not be_valid |
6 | - expect(notice.errors[:backtrace]).to include("can't be blank") | 6 | + expect(notice.errors[:backtrace_id]).to include("can't be blank") |
7 | end | 7 | end |
8 | 8 | ||
9 | it 'requires the server_environment' do | 9 | it 'requires the server_environment' do |
spec/models/problem_spec.rb
@@ -40,10 +40,10 @@ describe Problem, type: 'model' do | @@ -40,10 +40,10 @@ describe Problem, type: 'model' do | ||
40 | expect(problem).to_not be_nil | 40 | expect(problem).to_not be_nil |
41 | 41 | ||
42 | notice1 = Fabricate(:notice, :err => err) | 42 | notice1 = Fabricate(:notice, :err => err) |
43 | - expect(problem.last_notice_at).to eq notice1.created_at | 43 | + expect(problem.last_notice_at).to eq notice1.reload.created_at |
44 | 44 | ||
45 | notice2 = Fabricate(:notice, :err => err) | 45 | notice2 = Fabricate(:notice, :err => err) |
46 | - expect(problem.last_notice_at).to eq notice2.created_at | 46 | + expect(problem.last_notice_at).to eq notice2.reload.created_at |
47 | end | 47 | end |
48 | end | 48 | end |
49 | 49 | ||
@@ -266,12 +266,6 @@ describe Problem, type: 'model' do | @@ -266,12 +266,6 @@ describe Problem, type: 'model' do | ||
266 | expect(@problem.messages).to eq ({}) | 266 | expect(@problem.messages).to eq ({}) |
267 | end | 267 | end |
268 | 268 | ||
269 | - it "adding a notice adds a string to #messages" do | ||
270 | - expect { | ||
271 | - Fabricate(:notice, :err => @err, :message => 'ERR 1') | ||
272 | - }.to change(@problem, :messages).from({}).to({Digest::MD5.hexdigest('ERR 1') => {'value' => 'ERR 1', 'count' => 1}}) | ||
273 | - end | ||
274 | - | ||
275 | it "removing a notice removes string from #messages" do | 269 | it "removing a notice removes string from #messages" do |
276 | Fabricate(:notice, :err => @err, :message => 'ERR 1') | 270 | Fabricate(:notice, :err => @err, :message => 'ERR 1') |
277 | expect { | 271 | expect { |
@@ -299,12 +293,6 @@ describe Problem, type: 'model' do | @@ -299,12 +293,6 @@ describe Problem, type: 'model' do | ||
299 | expect(@problem.hosts).to eq ({}) | 293 | expect(@problem.hosts).to eq ({}) |
300 | end | 294 | end |
301 | 295 | ||
302 | - it "adding a notice adds a string to #hosts" do | ||
303 | - expect { | ||
304 | - Fabricate(:notice, :err => @err, :request => {'url' => "http://example.com/resource/12"}) | ||
305 | - }.to change(@problem, :hosts).from({}).to({Digest::MD5.hexdigest('example.com') => {'value' => 'example.com', 'count' => 1}}) | ||
306 | - end | ||
307 | - | ||
308 | it "removing a notice removes string from #hosts" do | 296 | it "removing a notice removes string from #hosts" do |
309 | Fabricate(:notice, :err => @err, :request => {'url' => "http://example.com/resource/12"}) | 297 | Fabricate(:notice, :err => @err, :request => {'url' => "http://example.com/resource/12"}) |
310 | expect { | 298 | expect { |
@@ -325,12 +313,6 @@ describe Problem, type: 'model' do | @@ -325,12 +313,6 @@ describe Problem, type: 'model' do | ||
325 | expect(@problem.user_agents).to eq ({}) | 313 | expect(@problem.user_agents).to eq ({}) |
326 | end | 314 | end |
327 | 315 | ||
328 | - it "adding a notice adds a string to #user_agents" do | ||
329 | - expect { | ||
330 | - Fabricate(:notice, :err => @err, :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'}}) | ||
331 | - }.to change(@problem, :user_agents).from({}).to({Digest::MD5.hexdigest('Chrome 10.0.648.204 (OS X 10.6.7)') => {'value' => 'Chrome 10.0.648.204 (OS X 10.6.7)', 'count' => 1}}) | ||
332 | - end | ||
333 | - | ||
334 | it "removing a notice removes string from #user_agents" do | 316 | it "removing a notice removes string from #user_agents" do |
335 | Fabricate(:notice, :err => @err, :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'}}) | 317 | Fabricate(:notice, :err => @err, :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'}}) |
336 | expect { | 318 | expect { |
spec/spec_helper.rb
1 | # This file is copied to ~/spec when you run 'ruby script/generate rspec' | 1 | # This file is copied to ~/spec when you run 'ruby script/generate rspec' |
2 | # from the project root directory. | 2 | # from the project root directory. |
3 | -ENV["RAILS_ENV"] ||= 'test' | 3 | +ENV["RAILS_ENV"] = 'test' |
4 | +ENV["ERRBIT_LOG_LEVEL"] = 'fatal' | ||
4 | 5 | ||
5 | if ENV['COVERAGE'] | 6 | if ENV['COVERAGE'] |
6 | require 'coveralls' | 7 | require 'coveralls' |
@@ -21,7 +22,6 @@ require File.expand_path("../../config/environment", __FILE__) | @@ -21,7 +22,6 @@ require File.expand_path("../../config/environment", __FILE__) | ||
21 | require 'rspec/rails' | 22 | require 'rspec/rails' |
22 | require 'rspec/its' | 23 | require 'rspec/its' |
23 | require 'email_spec' | 24 | require 'email_spec' |
24 | -require 'database_cleaner' | ||
25 | require 'xmpp4r' | 25 | require 'xmpp4r' |
26 | require 'xmpp4r/muc' | 26 | require 'xmpp4r/muc' |
27 | require 'mongoid-rspec' | 27 | require 'mongoid-rspec' |
@@ -31,6 +31,8 @@ require 'errbit_plugin/mock_issue_tracker' | @@ -31,6 +31,8 @@ require 'errbit_plugin/mock_issue_tracker' | ||
31 | # Requires supporting files with custom matchers and macros, etc, | 31 | # Requires supporting files with custom matchers and macros, etc, |
32 | # in ./support/ and its subdirectories. | 32 | # in ./support/ and its subdirectories. |
33 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} | 33 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} |
34 | +Mongoid::Config.truncate! | ||
35 | +Mongoid::Tasks::Database.create_indexes | ||
34 | 36 | ||
35 | RSpec.configure do |config| | 37 | RSpec.configure do |config| |
36 | config.include Devise::TestHelpers, :type => :controller | 38 | config.include Devise::TestHelpers, :type => :controller |
@@ -38,8 +40,7 @@ RSpec.configure do |config| | @@ -38,8 +40,7 @@ RSpec.configure do |config| | ||
38 | config.alias_example_to :fit, :focused => true | 40 | config.alias_example_to :fit, :focused => true |
39 | 41 | ||
40 | config.before(:each) do | 42 | config.before(:each) do |
41 | - DatabaseCleaner[:mongoid].strategy = :truncation | ||
42 | - DatabaseCleaner.clean | 43 | + Mongoid::Config.truncate! |
43 | end | 44 | end |
44 | 45 | ||
45 | config.include Haml, type: :helper | 46 | config.include Haml, type: :helper |
spec/views/issue_trackers/issue.md.erb_spec.rb
@@ -6,7 +6,7 @@ describe "issue_trackers/issue.md.erb", type: 'view' do | @@ -6,7 +6,7 @@ describe "issue_trackers/issue.md.erb", type: 'view' do | ||
6 | } | 6 | } |
7 | 7 | ||
8 | before do | 8 | before do |
9 | - allow(view).to receive(:problem).and_return(problem) | 9 | + allow(view).to receive(:problem).and_return(ProblemDecorator.new(problem)) |
10 | end | 10 | end |
11 | 11 | ||
12 | it "has the problem url" do | 12 | it "has the problem url" do |
spec/views/issue_trackers/issue.txt.erb_spec.rb
@@ -6,7 +6,8 @@ describe "issue_trackers/issue.txt.erb", type: 'view' do | @@ -6,7 +6,8 @@ describe "issue_trackers/issue.txt.erb", type: 'view' do | ||
6 | } | 6 | } |
7 | 7 | ||
8 | before do | 8 | before do |
9 | - allow(view).to receive(:problem).and_return(problem) | 9 | + allow(view).to receive(:problem).and_return( |
10 | + ProblemDecorator.new(problem)) | ||
10 | end | 11 | end |
11 | 12 | ||
12 | it "has the problem url" do | 13 | it "has the problem url" do |