Commit 76db16871d2e726d4bf1e31b935adb6ef98a5a2f
Exists in
master
and in
1 other branch
Merge branch 'master' into features/extract_issue_tracker
Conflicts: Gemfile Gemfile.lock app/controllers/apps_controller.rb app/models/issue_trackers/github_issues_tracker.rb app/views/apps/_fields.html.haml app/views/apps/edit.html.haml spec/models/issue_trackers/github_issues_tracker_spec.rb
Showing
37 changed files
with
460 additions
and
113 deletions
Show diff stats
CHANGELOG.md
| ... | ... | @@ -13,12 +13,30 @@ |
| 13 | 13 | use ([@shingara][]) |
| 14 | 14 | - [#588][] Change documentation to see Airbrake gem instead of |
| 15 | 15 | hoptoad_nofier ([@ugisozols][]) |
| 16 | +- [#592][] Avoid taskmapper dependencies ([@arthurnn][]) | |
| 17 | +- [#597][] Improve fingerprinting if only change by object memory adress | |
| 18 | + ([@1st8][]) | |
| 19 | +- [#603][] Improve the page rendering of App list ([@arthurnn][]) | |
| 20 | +- [#606][] Add ability to deploy on Cloud 66 ([@pkallberg][]) | |
| 21 | +- [#613][] Improve text about delete thing ([@mildavw][]) | |
| 22 | +- [#616][] Improvement about our test suite ([@durrantm][]) | |
| 23 | +- [#620][] Add App version information in email notification | |
| 24 | + ([@soberstadt][]) | |
| 25 | +- [#624][] Allow configuration of port if errbit not on 80 | |
| 26 | + ([@rsutphin][]) | |
| 27 | +- [#617][] Allow GET request on notice API ([@soberstadt][]) | |
| 28 | +- [#618][] Improve the stack information in JS notifier ([@soberstadt][]) | |
| 29 | +- [#619][] Handle the App version information in the API ([@soberstadt][]) | |
| 16 | 30 | |
| 17 | 31 | ### Bug Fixes |
| 18 | 32 | |
| 19 | 33 | - [#565][] Treat request URL as CDATA so query string params don't |
| 20 | 34 | result in invalid XML ([@mildavw][]) |
| 21 | 35 | - [#594][] Dont use ^ and $ on email_regexp([@arthurnn][]) |
| 36 | +- [#598][] Fix issue about gitlab integration ([@jozefvaclavik][]) | |
| 37 | +- [#609][] Fix issue about page system on search error ([@zhekanax][]) | |
| 38 | +- [#611][] Fix issue about select all error in search page ([@zhekanax][]) | |
| 39 | +- [#615][] Fix some LDAP information in README ([@felixbuenemann][]) | |
| 22 | 40 | |
| 23 | 41 | |
| 24 | 42 | [@arthurnn]: https://github.com/arthurnn |
| ... | ... | @@ -26,13 +44,36 @@ |
| 26 | 44 | [@numbata]: https://github.com/numbata |
| 27 | 45 | [@shingara]: https://github.com/shingara |
| 28 | 46 | [@ugisozols]: https://github.com/ugisozols |
| 47 | +[@1st8]: https://github.com/1st8 | |
| 48 | +[@jozefvaclavik]: https://github.com/jozefvaclavik | |
| 49 | +[@zhekanax]: https://github.com/zhekanax | |
| 50 | +[@pkallberg]: https://github.com/pkallberg | |
| 51 | +[@durrantm]: https://github.com/durrantm | |
| 52 | +[@felixbuenemann]: https://github.com/felixbuenemann | |
| 53 | +[@soberstadt]: https://github.com/soberstadt | |
| 54 | +[@rsutphin]: https://github.com/rsutphin | |
| 29 | 55 | |
| 30 | 56 | [#289]: https://github.com/errbit/errbit/issues/289 |
| 31 | 57 | [#515]: https://github.com/errbit/errbit/issues/515 |
| 32 | 58 | [#547]: https://github.com/errbit/errbit/issues/547 |
| 33 | 59 | [#565]: https://github.com/errbit/errbit/issues/565 |
| 34 | 60 | [#588]: https://github.com/errbit/errbit/issues/588 |
| 61 | +[#592]: https://github.com/errbit/errbit/pull/592 | |
| 35 | 62 | [#594]: https://github.com/errbit/errbit/pull/594 |
| 63 | +[#597]: https://github.com/errbit/errbit/pull/597 | |
| 64 | +[#598]: https://github.com/errbit/errbit/pull/598 | |
| 65 | +[#603]: https://github.com/errbit/errbit/pull/603 | |
| 66 | +[#606]: https://github.com/errbit/errbit/pull/606 | |
| 67 | +[#609]: https://github.com/errbit/errbit/pull/609 | |
| 68 | +[#611]: https://github.com/errbit/errbit/pull/611 | |
| 69 | +[#613]: https://github.com/errbit/errbit/pull/613 | |
| 70 | +[#616]: https://github.com/errbit/errbit/pull/616 | |
| 71 | +[#615]: https://github.com/errbit/errbit/pull/615 | |
| 72 | +[#617]: https://github.com/errbit/errbit/pull/617 | |
| 73 | +[#618]: https://github.com/errbit/errbit/pull/618 | |
| 74 | +[#619]: https://github.com/errbit/errbit/pull/619 | |
| 75 | +[#620]: https://github.com/errbit/errbit/pull/620 | |
| 76 | +[#624]: https://github.com/errbit/errbit/pull/624 | |
| 36 | 77 | |
| 37 | 78 | ## 0.2.1 - Not released Yet |
| 38 | 79 | ... | ... |
CONTRIBUTORS.md
| ... | ... | @@ -9,6 +9,14 @@ |
| 9 | 9 | - [@shingara][] |
| 10 | 10 | - [@simi][] |
| 11 | 11 | - [@ugisozols][] |
| 12 | +- [@1st8][] | |
| 13 | +- [@jozefvaclavik][] | |
| 14 | +- [@zhekanax][] | |
| 15 | +- [@pkallberg][] | |
| 16 | +- [@durrantm][] | |
| 17 | +- [@felixbuenemann][] | |
| 18 | +- [@soberstadt][] | |
| 19 | +- [@rsutphin][] | |
| 12 | 20 | |
| 13 | 21 | [@arthurnn]: https://github.com/arthurnn |
| 14 | 22 | [@emgiezet]: https://github.com/emgiezet |
| ... | ... | @@ -19,6 +27,13 @@ |
| 19 | 27 | [@shingara]: https://github.com/shingara |
| 20 | 28 | [@simi]: https://github.com/simi |
| 21 | 29 | [@ugisozols]: https://github.com/ugisozols |
| 30 | +[@1st8]: https://github.com/1st8 | |
| 31 | +[@jozefvaclavik]: https://github.com/jozefvaclavik | |
| 32 | +[@zhekanax]: https://github.com/zhekanax | |
| 33 | +[@pkallberg]: https://github.com/pkallberg | |
| 34 | +[@durrantm]: https://github.com/durrantm | |
| 35 | +[@felixbuenemann]: https://github.com/felixbuenemann | |
| 36 | + | |
| 22 | 37 | |
| 23 | 38 | |
| 24 | 39 | ## 0.2.1 - Not released yet |
| ... | ... | @@ -92,3 +107,5 @@ |
| 92 | 107 | [@tvdeyen]: https://github.com/tvdeyen |
| 93 | 108 | [@williamn]: https://github.com/williamn |
| 94 | 109 | [@xenji]: https://github.com/xenji |
| 110 | +[@soberstadt]: https://github.com/soberstadt | |
| 111 | +[@rsutphin]: https://github.com/rsutphin | ... | ... |
Gemfile
| 1 | 1 | source 'https://rubygems.org' |
| 2 | 2 | |
| 3 | -RAILS_VERSION = '~> 3.2.15' | |
| 3 | +RAILS_VERSION = '~> 3.2.18' | |
| 4 | 4 | |
| 5 | 5 | gem 'actionmailer', RAILS_VERSION |
| 6 | 6 | gem 'actionpack', RAILS_VERSION |
| ... | ... | @@ -25,7 +25,6 @@ gem 'rails_autolink' |
| 25 | 25 | # Please don't update hoptoad_notifier to airbrake. |
| 26 | 26 | # It's for internal use only, and we monkeypatch certain methods |
| 27 | 27 | gem 'hoptoad_notifier', "~> 2.4" |
| 28 | -gem 'draper', :require => false | |
| 29 | 28 | |
| 30 | 29 | gem 'errbit_plugin', |
| 31 | 30 | :git => 'https://github.com/errbit/errbit_plugin.git' |
| ... | ... | @@ -62,7 +61,6 @@ group :development, :test do |
| 62 | 61 | gem 'rspec-rails' |
| 63 | 62 | gem 'webmock', :require => false |
| 64 | 63 | gem 'airbrake', :require => false |
| 65 | - gem 'debugger', :platform => :mri_19 | |
| 66 | 64 | gem 'pry-rails' |
| 67 | 65 | # gem 'rpm_contrib' |
| 68 | 66 | # gem 'newrelic_rpm' |
| ... | ... | @@ -84,10 +82,9 @@ group :development do |
| 84 | 82 | end |
| 85 | 83 | |
| 86 | 84 | group :test do |
| 87 | - gem 'capybara', :require => false | |
| 88 | - gem 'poltergeist', :require => false | |
| 89 | - gem 'launchy', :require => false | |
| 90 | - gem 'database_cleaner', :require => false | |
| 85 | + gem 'capybara' | |
| 86 | + gem 'launchy' | |
| 87 | + gem 'database_cleaner' | |
| 91 | 88 | gem 'email_spec' |
| 92 | 89 | gem 'timecop' |
| 93 | 90 | gem 'coveralls', :require => false |
| ... | ... | @@ -98,6 +95,7 @@ group :heroku, :production do |
| 98 | 95 | gem 'unicorn', :require => false |
| 99 | 96 | end |
| 100 | 97 | |
| 98 | + | |
| 101 | 99 | # Gems used only for assets and not required |
| 102 | 100 | # in production environments by default. |
| 103 | 101 | group :assets do | ... | ... |
Gemfile.lock
| ... | ... | @@ -15,16 +15,16 @@ GIT |
| 15 | 15 | GEM |
| 16 | 16 | remote: https://rubygems.org/ |
| 17 | 17 | specs: |
| 18 | - actionmailer (3.2.16) | |
| 19 | - actionpack (= 3.2.16) | |
| 18 | + actionmailer (3.2.18) | |
| 19 | + actionpack (= 3.2.18) | |
| 20 | 20 | mail (~> 2.5.4) |
| 21 | 21 | actionmailer_inline_css (1.5.3) |
| 22 | 22 | actionmailer (>= 3.0.0) |
| 23 | 23 | nokogiri (>= 1.4.4) |
| 24 | 24 | premailer (>= 1.7.1) |
| 25 | - actionpack (3.2.16) | |
| 26 | - activemodel (= 3.2.16) | |
| 27 | - activesupport (= 3.2.16) | |
| 25 | + actionpack (3.2.18) | |
| 26 | + activemodel (= 3.2.18) | |
| 27 | + activesupport (= 3.2.18) | |
| 28 | 28 | builder (~> 3.0.0) |
| 29 | 29 | erubis (~> 2.7.0) |
| 30 | 30 | journey (~> 1.0.4) |
| ... | ... | @@ -32,18 +32,18 @@ GEM |
| 32 | 32 | rack-cache (~> 1.2) |
| 33 | 33 | rack-test (~> 0.6.1) |
| 34 | 34 | sprockets (~> 2.2.1) |
| 35 | - activemodel (3.2.16) | |
| 36 | - activesupport (= 3.2.16) | |
| 35 | + activemodel (3.2.18) | |
| 36 | + activesupport (= 3.2.18) | |
| 37 | 37 | builder (~> 3.0.0) |
| 38 | - activerecord (3.2.16) | |
| 39 | - activemodel (= 3.2.16) | |
| 40 | - activesupport (= 3.2.16) | |
| 38 | + activerecord (3.2.18) | |
| 39 | + activemodel (= 3.2.18) | |
| 40 | + activesupport (= 3.2.18) | |
| 41 | 41 | arel (~> 3.0.2) |
| 42 | 42 | tzinfo (~> 0.3.29) |
| 43 | - activeresource (3.2.16) | |
| 44 | - activemodel (= 3.2.16) | |
| 45 | - activesupport (= 3.2.16) | |
| 46 | - activesupport (3.2.16) | |
| 43 | + activeresource (3.2.18) | |
| 44 | + activemodel (= 3.2.18) | |
| 45 | + activesupport (= 3.2.18) | |
| 46 | + activesupport (3.2.18) | |
| 47 | 47 | i18n (~> 0.6, >= 0.6.4) |
| 48 | 48 | multi_json (~> 1.0) |
| 49 | 49 | addressable (2.3.5) |
| ... | ... | @@ -73,9 +73,7 @@ GEM |
| 73 | 73 | rack (>= 1.0.0) |
| 74 | 74 | rack-test (>= 0.5.4) |
| 75 | 75 | xpath (~> 2.0) |
| 76 | - cliver (0.2.2) | |
| 77 | 76 | coderay (1.0.9) |
| 78 | - columnize (0.3.6) | |
| 79 | 77 | coveralls (0.7.0) |
| 80 | 78 | multi_json (~> 1.3) |
| 81 | 79 | rest-client |
| ... | ... | @@ -88,12 +86,6 @@ GEM |
| 88 | 86 | addressable |
| 89 | 87 | database_cleaner (1.2.0) |
| 90 | 88 | debug_inspector (0.0.2) |
| 91 | - debugger (1.6.3) | |
| 92 | - columnize (>= 0.3.1) | |
| 93 | - debugger-linecache (~> 1.2.0) | |
| 94 | - debugger-ruby_core_source (~> 1.2.4) | |
| 95 | - debugger-linecache (1.2.0) | |
| 96 | - debugger-ruby_core_source (1.2.4) | |
| 97 | 89 | decent_exposure (2.3.0) |
| 98 | 90 | devise (3.1.1) |
| 99 | 91 | bcrypt-ruby (~> 3.0) |
| ... | ... | @@ -103,11 +95,6 @@ GEM |
| 103 | 95 | warden (~> 1.2.3) |
| 104 | 96 | diff-lcs (1.2.4) |
| 105 | 97 | dotenv (0.9.0) |
| 106 | - draper (1.3.0) | |
| 107 | - actionpack (>= 3.0) | |
| 108 | - activemodel (>= 3.0) | |
| 109 | - activesupport (>= 3.0) | |
| 110 | - request_store (~> 1.0.3) | |
| 111 | 98 | email_spec (1.5.0) |
| 112 | 99 | launchy (~> 2.1) |
| 113 | 100 | mail (~> 2.2) |
| ... | ... | @@ -164,6 +151,7 @@ GEM |
| 164 | 151 | railties |
| 165 | 152 | method_source (0.8.2) |
| 166 | 153 | mime-types (1.25.1) |
| 154 | + mini_portile (0.5.3) | |
| 167 | 155 | mongoid (3.1.5) |
| 168 | 156 | activemodel (~> 3.2) |
| 169 | 157 | moped (~> 1.4) |
| ... | ... | @@ -179,7 +167,7 @@ GEM |
| 179 | 167 | rails (>= 3.2.0) |
| 180 | 168 | railties (>= 3.2.0) |
| 181 | 169 | moped (1.5.1) |
| 182 | - multi_json (1.8.2) | |
| 170 | + multi_json (1.10.0) | |
| 183 | 171 | multi_xml (0.5.5) |
| 184 | 172 | multipart-post (1.2.0) |
| 185 | 173 | net-scp (1.1.2) |
| ... | ... | @@ -189,7 +177,8 @@ GEM |
| 189 | 177 | net-ssh (2.7.0) |
| 190 | 178 | net-ssh-gateway (1.2.0) |
| 191 | 179 | net-ssh (>= 2.6.5) |
| 192 | - nokogiri (1.5.10) | |
| 180 | + nokogiri (1.6.1) | |
| 181 | + mini_portile (~> 0.5.0) | |
| 193 | 182 | oauth2 (0.8.1) |
| 194 | 183 | faraday (~> 0.8) |
| 195 | 184 | httpauth (~> 0.1) |
| ... | ... | @@ -211,13 +200,8 @@ GEM |
| 211 | 200 | orm_adapter (0.4.0) |
| 212 | 201 | pjax_rails (0.3.4) |
| 213 | 202 | jquery-rails |
| 214 | - poltergeist (1.4.1) | |
| 215 | - capybara (~> 2.1.0) | |
| 216 | - cliver (~> 0.2.1) | |
| 217 | - multi_json (~> 1.0) | |
| 218 | - websocket-driver (>= 0.2.0) | |
| 219 | - polyglot (0.3.3) | |
| 220 | - premailer (1.7.9) | |
| 203 | + polyglot (0.3.4) | |
| 204 | + premailer (1.7.3) | |
| 221 | 205 | css_parser (>= 1.1.9) |
| 222 | 206 | htmlentities (>= 4.0.0) |
| 223 | 207 | pry (0.9.12.2) |
| ... | ... | @@ -235,33 +219,33 @@ GEM |
| 235 | 219 | rack (>= 0.4) |
| 236 | 220 | rack-contrib (1.1.0) |
| 237 | 221 | rack (>= 0.9.1) |
| 238 | - rack-ssl (1.3.3) | |
| 222 | + rack-ssl (1.3.4) | |
| 239 | 223 | rack |
| 240 | 224 | rack-ssl-enforcer (0.2.6) |
| 241 | 225 | rack-test (0.6.2) |
| 242 | 226 | rack (>= 1.0) |
| 243 | - rails (3.2.16) | |
| 244 | - actionmailer (= 3.2.16) | |
| 245 | - actionpack (= 3.2.16) | |
| 246 | - activerecord (= 3.2.16) | |
| 247 | - activeresource (= 3.2.16) | |
| 248 | - activesupport (= 3.2.16) | |
| 227 | + rails (3.2.18) | |
| 228 | + actionmailer (= 3.2.18) | |
| 229 | + actionpack (= 3.2.18) | |
| 230 | + activerecord (= 3.2.18) | |
| 231 | + activeresource (= 3.2.18) | |
| 232 | + activesupport (= 3.2.18) | |
| 249 | 233 | bundler (~> 1.0) |
| 250 | - rails_autolink (1.1.5) | |
| 234 | + railties (= 3.2.18) | |
| 235 | + rails_autolink (1.1.4) | |
| 251 | 236 | rails (> 3.1) |
| 252 | - railties (3.2.16) | |
| 253 | - actionpack (= 3.2.16) | |
| 254 | - activesupport (= 3.2.16) | |
| 237 | + railties (3.2.18) | |
| 238 | + actionpack (= 3.2.18) | |
| 239 | + activesupport (= 3.2.18) | |
| 255 | 240 | rack-ssl (~> 1.3.2) |
| 256 | 241 | rake (>= 0.8.7) |
| 257 | 242 | rdoc (~> 3.4) |
| 258 | 243 | thor (>= 0.14.6, < 2.0) |
| 259 | 244 | raindrops (0.12.0) |
| 260 | - rake (10.1.0) | |
| 245 | + rake (10.3.1) | |
| 261 | 246 | rdoc (3.12.2) |
| 262 | 247 | json (~> 1.4) |
| 263 | 248 | ref (1.0.5) |
| 264 | - request_store (1.0.5) | |
| 265 | 249 | rest-client (1.6.7) |
| 266 | 250 | mime-types (>= 1.16) |
| 267 | 251 | ri_cal (0.8.8) |
| ... | ... | @@ -306,7 +290,7 @@ GEM |
| 306 | 290 | therubyracer (0.12.0) |
| 307 | 291 | libv8 (~> 3.16.14.0) |
| 308 | 292 | ref |
| 309 | - thor (0.18.1) | |
| 293 | + thor (0.19.1) | |
| 310 | 294 | thread_safe (0.1.3) |
| 311 | 295 | atomic |
| 312 | 296 | tilt (1.4.1) |
| ... | ... | @@ -318,8 +302,8 @@ GEM |
| 318 | 302 | turbo-sprockets-rails3 (0.3.10) |
| 319 | 303 | railties (> 3.2.8, < 4.0.0) |
| 320 | 304 | sprockets (>= 2.0.0) |
| 321 | - tzinfo (0.3.38) | |
| 322 | - uglifier (2.3.0) | |
| 305 | + tzinfo (0.3.39) | |
| 306 | + uglifier (2.2.1) | |
| 323 | 307 | execjs (>= 0.3.0) |
| 324 | 308 | json (>= 1.8.0) |
| 325 | 309 | underscore-rails (1.5.2) |
| ... | ... | @@ -333,7 +317,6 @@ GEM |
| 333 | 317 | webmock (1.15.0) |
| 334 | 318 | addressable (>= 2.2.7) |
| 335 | 319 | crack (>= 0.3.2) |
| 336 | - websocket-driver (0.3.0) | |
| 337 | 320 | xmpp4r (0.5.5) |
| 338 | 321 | xpath (2.0.0) |
| 339 | 322 | nokogiri (~> 1.3) |
| ... | ... | @@ -343,9 +326,9 @@ PLATFORMS |
| 343 | 326 | ruby |
| 344 | 327 | |
| 345 | 328 | DEPENDENCIES |
| 346 | - actionmailer (~> 3.2.15) | |
| 329 | + actionmailer (~> 3.2.18) | |
| 347 | 330 | actionmailer_inline_css |
| 348 | - actionpack (~> 3.2.15) | |
| 331 | + actionpack (~> 3.2.18) | |
| 349 | 332 | airbrake |
| 350 | 333 | better_errors |
| 351 | 334 | binding_of_caller |
| ... | ... | @@ -354,10 +337,8 @@ DEPENDENCIES |
| 354 | 337 | capybara |
| 355 | 338 | coveralls |
| 356 | 339 | database_cleaner |
| 357 | - debugger | |
| 358 | 340 | decent_exposure |
| 359 | 341 | devise |
| 360 | - draper | |
| 361 | 342 | email_spec |
| 362 | 343 | errbit_github_plugin! |
| 363 | 344 | errbit_plugin! |
| ... | ... | @@ -380,14 +361,13 @@ DEPENDENCIES |
| 380 | 361 | mongoid_rails_migrations |
| 381 | 362 | omniauth-github |
| 382 | 363 | pjax_rails |
| 383 | - poltergeist | |
| 384 | 364 | pry-rails |
| 385 | 365 | puma |
| 386 | 366 | quiet_assets |
| 387 | 367 | rack-ssl |
| 388 | 368 | rack-ssl-enforcer |
| 389 | 369 | rails_autolink |
| 390 | - railties (~> 3.2.15) | |
| 370 | + railties (~> 3.2.18) | |
| 391 | 371 | ri_cal |
| 392 | 372 | rspec-rails |
| 393 | 373 | rushover | ... | ... |
README.md
| 1 | 1 | # Errbit [![TravisCI][travis-img-url]][travis-ci-url] [![Code Climate][codeclimate-img-url]][codeclimate-url] [![Coveralls][coveralls-img-url]][coveralls-url] [![Dependency Status][gemnasium-img-url]][gemnasium-url] |
| 2 | 2 | |
| 3 | -[travis-img-url]: https://secure.travis-ci.org/errbit/errbit.png?branch=master | |
| 3 | +[travis-img-url]: https://travis-ci.org/errbit/errbit.svg?branch=master | |
| 4 | 4 | [travis-ci-url]: http://travis-ci.org/errbit/errbit |
| 5 | 5 | [codeclimate-img-url]: https://codeclimate.com/github/errbit/errbit.png |
| 6 | 6 | [codeclimate-url]: https://codeclimate.com/github/errbit/errbit |
| ... | ... | @@ -409,6 +409,19 @@ end |
| 409 | 409 | |
| 410 | 410 | Then get the `notifier.js` from `errbit/public/javascript/notifier.js` and add to `application.js` on your rails app or include `http://YOUR-ERRBIT-HOST/javascripts/notifier.js` on your `application.html.erb.` |
| 411 | 411 | |
| 412 | +Using custom fingerprinting methods | |
| 413 | +----------------------------------- | |
| 414 | + | |
| 415 | +Errbit now allows you to easily use your own Fingerprint Strategy if that's what you'd like to do. If you are upgrading from a very old version of errbit, you can use the `LegacyFingerprint` to provide yourself | |
| 416 | +with compatibility. The fingerprint strategy can be changed by adding an initializer to errbit: | |
| 417 | + | |
| 418 | +```ruby | |
| 419 | +# config/fingerprint.rb | |
| 420 | +ErrorReport.fingerprint_strategy = LegacyFingerprint | |
| 421 | +``` | |
| 422 | + | |
| 423 | +The easiest way to add custom fingerprint methods is to simply subclass `Fingerprint` | |
| 424 | + | |
| 412 | 425 | Issue Trackers |
| 413 | 426 | -------------- |
| 414 | 427 | |
| ... | ... | @@ -597,7 +610,3 @@ Copyright |
| 597 | 610 | |
| 598 | 611 | Copyright (c) 2010-2013 Errbit Team. See LICENSE for details. |
| 599 | 612 | |
| 600 | - | |
| 601 | - | |
| 602 | -[](https://bitdeli.com/free "Bitdeli Badge") | |
| 603 | - | ... | ... |
5.89 KB
5.89 KB
5.68 KB
app/controllers/api/v1/notices_controller.rb
| ... | ... | @@ -16,8 +16,7 @@ class Api::V1::NoticesController < ApplicationController |
| 16 | 16 | end |
| 17 | 17 | |
| 18 | 18 | respond_to do |format| |
| 19 | - format.html { render :json => Yajl.dump(results) } # render JSON if no extension specified on path | |
| 20 | - format.json { render :json => Yajl.dump(results) } | |
| 19 | + format.any(:html, :json) { render :json => Yajl.dump(results) } # render JSON if no extension specified on path | |
| 21 | 20 | format.xml { render :xml => results } |
| 22 | 21 | end |
| 23 | 22 | end | ... | ... |
app/controllers/api/v1/problems_controller.rb
| ... | ... | @@ -16,8 +16,7 @@ class Api::V1::ProblemsController < ApplicationController |
| 16 | 16 | end |
| 17 | 17 | |
| 18 | 18 | respond_to do |format| |
| 19 | - format.html { render :json => Yajl.dump(results) } # render JSON if no extension specified on path | |
| 20 | - format.json { render :json => Yajl.dump(results) } | |
| 19 | + format.any(:html, :json) { render :json => Yajl.dump(results) } # render JSON if no extension specified on path | |
| 21 | 20 | format.xml { render :xml => results } |
| 22 | 21 | end |
| 23 | 22 | end | ... | ... |
app/controllers/api/v1/stats_controller.rb
| ... | ... | @@ -12,13 +12,13 @@ class Api::V1::StatsController < ApplicationController |
| 12 | 12 | |
| 13 | 13 | stats = { |
| 14 | 14 | :name => @app.name, |
| 15 | + :id => @app.id, | |
| 15 | 16 | :last_error_time => @last_error_time, |
| 16 | 17 | :unresolved_errors => @app.unresolved_count |
| 17 | 18 | } |
| 18 | 19 | |
| 19 | 20 | respond_to do |format| |
| 20 | - format.html { render :json => Yajl.dump(stats) } # render JSON if no extension specified on path | |
| 21 | - format.json { render :json => Yajl.dump(stats) } | |
| 21 | + format.any(:html, :json) { render :json => Yajl.dump(stats) } # render JSON if no extension specified on path | |
| 22 | 22 | format.xml { render :xml => stats } |
| 23 | 23 | end |
| 24 | 24 | end |
| ... | ... | @@ -37,5 +37,3 @@ class Api::V1::StatsController < ApplicationController |
| 37 | 37 | end |
| 38 | 38 | |
| 39 | 39 | end |
| 40 | - | |
| 41 | - | ... | ... |
app/controllers/apps_controller.rb
| ... | ... | @@ -39,6 +39,10 @@ class AppsController < ApplicationController |
| 39 | 39 | app.deploys.order_by(:created_at.desc).limit(5) |
| 40 | 40 | } |
| 41 | 41 | |
| 42 | + expose(:users) { | |
| 43 | + User.all.sort_by {|u| u.name.downcase } | |
| 44 | + } | |
| 45 | + | |
| 42 | 46 | def index; end |
| 43 | 47 | def show |
| 44 | 48 | app |
| ... | ... | @@ -91,8 +95,10 @@ class AppsController < ApplicationController |
| 91 | 95 | def initialize_subclassed_notification_service |
| 92 | 96 | # set the app's notification service |
| 93 | 97 | if params[:app][:notification_service_attributes] && notification_type = params[:app][:notification_service_attributes][:type] |
| 94 | - if NotificationService.subclasses.map(&:name).concat(["NotificationService"]).include?(notification_type) | |
| 95 | - app.notification_service = notification_type.constantize.new(params[:app][:notification_service_attributes]) | |
| 98 | + available_notification_classes = [NotificationService] + NotificationService.subclasses | |
| 99 | + notification_class = available_notification_classes.detect{|c| c.name == notification_type} | |
| 100 | + if !notification_class.nil? | |
| 101 | + app.notification_service = notification_class.new(params[:app][:notification_service_attributes]) | |
| 96 | 102 | end |
| 97 | 103 | end |
| 98 | 104 | end | ... | ... |
app/controllers/problems_controller.rb
| ... | ... | @@ -123,6 +123,14 @@ class ProblemsController < ApplicationController |
| 123 | 123 | redirect_to :back |
| 124 | 124 | end |
| 125 | 125 | |
| 126 | + def destroy_all | |
| 127 | + nb_problem_destroy = ProblemDestroy.execute(app.problems) | |
| 128 | + flash[:success] = "#{I18n.t(:n_errs_have, :count => nb_problem_destroy)} been deleted." | |
| 129 | + redirect_to :back | |
| 130 | + rescue ActionController::RedirectBackError | |
| 131 | + redirect_to app_path(app) | |
| 132 | + end | |
| 133 | + | |
| 126 | 134 | def search |
| 127 | 135 | ps = Problem.search(params[:search]).for_apps(app_scope).in_env(params[:environment]).all_else_unresolved(params[:all_errs]).ordered_by(params_sort, params_order) |
| 128 | 136 | selected_problems = params[:problems] || [] | ... | ... |
app/models/error_report.rb
| ... | ... | @@ -17,6 +17,10 @@ require 'hoptoad_notifier' |
| 17 | 17 | class ErrorReport |
| 18 | 18 | attr_reader :error_class, :message, :request, :server_environment, :api_key, :notifier, :user_attributes, :framework |
| 19 | 19 | |
| 20 | + cattr_accessor :fingerprint_strategy do | |
| 21 | + Fingerprint | |
| 22 | + end | |
| 23 | + | |
| 20 | 24 | def initialize(xml_or_attributes) |
| 21 | 25 | @attributes = (xml_or_attributes.is_a?(String) ? Hoptoad.parse_xml!(xml_or_attributes) : xml_or_attributes).with_indifferent_access |
| 22 | 26 | @attributes.each{|k, v| instance_variable_set(:"@#{k}", v) } |
| ... | ... | @@ -84,7 +88,7 @@ class ErrorReport |
| 84 | 88 | private |
| 85 | 89 | |
| 86 | 90 | def fingerprint |
| 87 | - @fingerprint ||= Fingerprint.generate(notice, api_key) | |
| 91 | + @fingerprint ||= fingerprint_strategy.generate(notice, api_key) | |
| 88 | 92 | end |
| 89 | 93 | |
| 90 | 94 | end | ... | ... |
| ... | ... | @@ -0,0 +1,20 @@ |
| 1 | +require 'digest/md5' | |
| 2 | + | |
| 3 | +class LegacyFingerprint < Fingerprint | |
| 4 | + def to_s | |
| 5 | + Digest::MD5.hexdigest(fingerprint_source) | |
| 6 | + end | |
| 7 | + | |
| 8 | + def fingerprint_source | |
| 9 | + location['method'] &&= sanitized_method_signature | |
| 10 | + end | |
| 11 | + | |
| 12 | + private | |
| 13 | + def sanitized_method_signature | |
| 14 | + location['method'].gsub(/[0-9]+|FRAGMENT/, '#').gsub(/_+#/, '_#') | |
| 15 | + end | |
| 16 | + | |
| 17 | + def location | |
| 18 | + notice.backtrace.lines.first | |
| 19 | + end | |
| 20 | +end | ... | ... |
app/models/notice.rb
| ... | ... | @@ -78,6 +78,17 @@ class Notice |
| 78 | 78 | "N/A" |
| 79 | 79 | end |
| 80 | 80 | |
| 81 | + def to_curl | |
| 82 | + return "N/A" if url.blank? | |
| 83 | + headers = %w(Accept Accept-Encoding Accept-Language Cookie Referer User-Agent).each_with_object([]) do |name, h| | |
| 84 | + if value = env_vars["HTTP_#{name.underscore.upcase}"] | |
| 85 | + h << "-H '#{name}: #{value}'" | |
| 86 | + end | |
| 87 | + end | |
| 88 | + | |
| 89 | + "curl -X #{env_vars['REQUEST_METHOD'] || 'GET'} #{headers.join(' ')} #{url}" | |
| 90 | + end | |
| 91 | + | |
| 81 | 92 | def env_vars |
| 82 | 93 | request['cgi-data'] || {} |
| 83 | 94 | end | ... | ... |
| ... | ... | @@ -0,0 +1,41 @@ |
| 1 | +class NotificationServices::SlackService < NotificationService | |
| 2 | + Label = "slack" | |
| 3 | + Fields += [ | |
| 4 | + [:subdomain, { | |
| 5 | + :placeholder => 'subdomain', | |
| 6 | + :label => 'Subdomain portion for Slack service' | |
| 7 | + }], | |
| 8 | + [:api_token, { | |
| 9 | + :placeholder => 'Slack Integration Token', | |
| 10 | + :label => 'Token' | |
| 11 | + }], | |
| 12 | + [:room_id, { | |
| 13 | + :placeholder => '#general', | |
| 14 | + :label => 'Room where Slack should notify' | |
| 15 | + }] | |
| 16 | + ] | |
| 17 | + | |
| 18 | + def check_params | |
| 19 | + if Fields.detect {|f| self[f[0]].blank? unless f[0] == :room_id } | |
| 20 | + errors.add :base, "You must specify your Slack subdomain and token." | |
| 21 | + end | |
| 22 | + end | |
| 23 | + | |
| 24 | + def url | |
| 25 | + "https://#{subdomain}.slack.com/services/hooks/incoming-webhook?token=#{api_token}" | |
| 26 | + end | |
| 27 | + | |
| 28 | + def message_for_slack(problem) | |
| 29 | + "[#{problem.app.name}][#{problem.environment}][#{problem.where}]: #{problem.error_class} #{problem_url(problem)}" | |
| 30 | + end | |
| 31 | + | |
| 32 | + def post_payload(problem) | |
| 33 | + payload = {:text => message_for_slack(problem) } | |
| 34 | + payload[:channel] = room_id unless room_id.empty? | |
| 35 | + payload.to_json | |
| 36 | + end | |
| 37 | + | |
| 38 | + def create_notification(problem) | |
| 39 | + HTTParty.post(url, :body => post_payload(problem), :headers => { 'Content-Type' => 'application/json' }) | |
| 40 | + end | |
| 41 | +end | ... | ... |
app/views/apps/_fields.html.haml
| ... | ... | @@ -53,9 +53,9 @@ |
| 53 | 53 | = w.radio_button :watcher_type, :user |
| 54 | 54 | = w.label :watcher_type_user, 'User' |
| 55 | 55 | = w.radio_button :watcher_type, :email |
| 56 | - = w.label :watcher_type_email, 'Email Address' | |
| 57 | - %div.watcher_params.user{:class => w.object.email_choosen} | |
| 58 | - = w.select :user_id, User.all.map{|u| [u.name,u.id.to_s]}, :include_blank => '-- Select a User --' | |
| 56 | + = label_tag :watcher_type_email, 'Email Address', :for => label_for_attr(w, 'watcher_type_email') | |
| 57 | + %div.watcher_params.user{:class => w.object.email.blank? ? 'chosen' : nil} | |
| 58 | + = w.select :user_id, users.map{|u| [u.name,u.id.to_s]}, :include_blank => '-- Select a User --' | |
| 59 | 59 | %div.watcher_params.email{:class => w.object.email.present? ? 'chosen' : nil} |
| 60 | 60 | = w.text_field :email |
| 61 | 61 | |
| ... | ... | @@ -65,4 +65,3 @@ |
| 65 | 65 | |
| 66 | 66 | = render "issue_tracker_fields", :f => f |
| 67 | 67 | = render "service_notification_fields", :f => f |
| 68 | - | ... | ... |
app/views/apps/edit.html.haml
| 1 | 1 | - content_for :title, t('.title') |
| 2 | 2 | - content_for :action_bar do |
| 3 | 3 | = link_to_copy_attributes_from_other_app |
| 4 | - = link_to t('.destroy'), app_path(app), :method => :delete, | |
| 4 | + = link_to 'delete all errs', destroy_all_app_problems_path(app), :method => :post, | |
| 5 | + :data => { :confirm => t('apps.confirm_destroy_all_problems') }, :class => 'button' | |
| 6 | + = link_to 'delete application', app_path(app), :method => :delete, | |
| 5 | 7 | :data => { :confirm => t('apps.confirm_delete') }, :class => 'button' |
| 6 | 8 | = link_to(t('.cancel'), app_path(app), :class => 'button') |
| 7 | 9 | ... | ... |
app/views/devise/mailer/reset_password_instructions.html.haml
| ... | ... | @@ -10,7 +10,7 @@ |
| 10 | 10 | %p |
| 11 | 11 | We hear you'd like to change your password. You can do that by visiting the link below: |
| 12 | 12 | %p |
| 13 | - = edit_password_url @resource, :reset_password_token => @resource.reset_password_token | |
| 13 | + = edit_password_url @resource, :reset_password_token => @token | |
| 14 | 14 | %tr |
| 15 | 15 | %td.content(valign="top") |
| 16 | 16 | %div | ... | ... |
app/views/devise/mailer/reset_password_instructions.text.erb
| ... | ... | @@ -2,7 +2,7 @@ Hello, |
| 2 | 2 | |
| 3 | 3 | We hear you'd like to change your password. You can do that by visiting the link below: |
| 4 | 4 | |
| 5 | - <%= edit_password_url(@resource, :reset_password_token => @resource.reset_password_token) %> | |
| 5 | + <%= edit_password_url(@resource, :reset_password_token => @token) %> | |
| 6 | 6 | |
| 7 | 7 | If you didn't request this, please ignore this email. Your password won't change unless you access the link above and create a new one. |
| 8 | 8 | ... | ... |
app/views/notices/_summary.html.haml
config/locales/en.yml
| ... | ... | @@ -79,11 +79,12 @@ en: |
| 79 | 79 | unresolve: "Unresolve selected issues? They can be resolved again later." |
| 80 | 80 | |
| 81 | 81 | comments: |
| 82 | - confirm_delete: "Premanently delete this comment?" | |
| 82 | + confirm_delete: "Permanently delete this comment?" | |
| 83 | 83 | users: |
| 84 | 84 | confirm_delete: "Permanently delete this user?" |
| 85 | 85 | apps: |
| 86 | 86 | confirm_delete: "Permanently delete this app?" |
| 87 | + confirm_destroy_all_problems: "Permanently delete all of this app's errors?" | |
| 87 | 88 | index: |
| 88 | 89 | notify: Notification Service |
| 89 | 90 | tracker: Tracker | ... | ... |
config/routes.rb
db/migrate/20121003223358_extract_backtraces.rb
| 1 | 1 | class ExtractBacktraces < Mongoid::Migration |
| 2 | 2 | def self.up |
| 3 | 3 | say "It could take long time (hours if you have many Notices)" |
| 4 | - Notice.unscoped.all.each do |notice| | |
| 4 | + Notice.unscoped.where(backtrace_id: nil).each do |notice| | |
| 5 | 5 | next if notice.backtrace.present? || notice['backtrace'].nil? |
| 6 | 6 | backtrace = Backtrace.find_or_create(:raw => notice['backtrace'] || []) |
| 7 | + notice.unset(:backtrace) | |
| 7 | 8 | notice.backtrace = backtrace |
| 8 | - notice['backtrace'] = nil | |
| 9 | 9 | notice.save! |
| 10 | 10 | end |
| 11 | 11 | say "run `db.repairDatabase()` (in mongodb console) to recover deleted space" |
| ... | ... | @@ -13,4 +13,4 @@ class ExtractBacktraces < Mongoid::Migration |
| 13 | 13 | |
| 14 | 14 | def self.down |
| 15 | 15 | end |
| 16 | -end | |
| 17 | 16 | \ No newline at end of file |
| 17 | +end | ... | ... |
db/migrate/20121005142110_regenerate_err_fingerprints.rb
| 1 | 1 | class RegenerateErrFingerprints < Mongoid::Migration |
| 2 | 2 | def self.up |
| 3 | 3 | Err.all.each do |err| |
| 4 | - if err.notices.any? | |
| 5 | - fingerprint_source = { | |
| 6 | - :backtrace => err.notices.first.backtrace_id, | |
| 7 | - :error_class => err.error_class, | |
| 8 | - :component => err.component, | |
| 9 | - :action => err.action, | |
| 10 | - :environment => err.environment, | |
| 11 | - :api_key => err.app.api_key | |
| 12 | - } | |
| 4 | + if err.notices.any? && err.problem | |
| 13 | 5 | err.update_attribute( |
| 14 | 6 | :fingerprint, |
| 15 | 7 | Fingerprint.generate(err.notices.first, err.app.api_key) | ... | ... |
lib/tasks/errbit/demo.rake
| ... | ... | @@ -49,7 +49,8 @@ namespace :errbit do |
| 49 | 49 | :backtrace => random_backtrace, |
| 50 | 50 | :request => { |
| 51 | 51 | 'component' => 'main', |
| 52 | - 'action' => 'error' | |
| 52 | + 'action' => 'error', | |
| 53 | + 'url' => "http://example.com/post/#{[111, 222, 333].sample}", | |
| 53 | 54 | }, |
| 54 | 55 | :server_environment => {'environment-name' => Rails.env.to_s}, |
| 55 | 56 | :notifier => {:name => "seeds.rb"}, | ... | ... |
public/javascripts/notifier.js
| ... | ... | @@ -870,6 +870,12 @@ printStackTrace.implementation.prototype = { |
| 870 | 870 | // Share to global scope as Airbrake ("window.Hoptoad" for backward compatibility) |
| 871 | 871 | Global = window.Airbrake = window.Hoptoad = Util.generatePublicAPI(_publicAPI, Config); |
| 872 | 872 | |
| 873 | + Global._filters = []; | |
| 874 | + | |
| 875 | + Global.addFilter = function (cb) { | |
| 876 | + Global._filters.push(cb); | |
| 877 | + }; | |
| 878 | + | |
| 873 | 879 | function Notifier() { |
| 874 | 880 | this.options = Util.merge({}, Config.options); |
| 875 | 881 | this.xmlData = Util.merge(this.DEF_XML_DATA, Config.xmlData); |
| ... | ... | @@ -921,7 +927,7 @@ printStackTrace.implementation.prototype = { |
| 921 | 927 | } |
| 922 | 928 | |
| 923 | 929 | return function (error) { |
| 924 | - var outputData = '', | |
| 930 | + var outputData = '', jsonData, | |
| 925 | 931 | url = ''; |
| 926 | 932 | // |
| 927 | 933 | |
| ... | ... | @@ -934,9 +940,12 @@ printStackTrace.implementation.prototype = { |
| 934 | 940 | |
| 935 | 941 | switch (this.options['outputFormat']) { |
| 936 | 942 | case 'XML': |
| 937 | - outputData = encodeURIComponent(this.generateXML(this.generateDataJSON(error))); | |
| 938 | - url = ('https:' == document.location.protocol ? 'https://' : 'http://') + this.options.host + '/notifier_api/v2/notices'; | |
| 939 | - _sendGETRequest(url, outputData); | |
| 943 | + jsonData = this.generateDataJSON(error); | |
| 944 | + if (this.shouldSendData(jsonData)){ | |
| 945 | + outputData = encodeURIComponent(this.generateXML(jsonData)); | |
| 946 | + url = ('https:' == document.location.protocol ? 'https://' : 'http://') + this.options.host + '/notifier_api/v2/notices'; | |
| 947 | + _sendGETRequest(url, outputData); | |
| 948 | + } | |
| 940 | 949 | break; |
| 941 | 950 | |
| 942 | 951 | case 'JSON': |
| ... | ... | @@ -945,9 +954,12 @@ printStackTrace.implementation.prototype = { |
| 945 | 954 | * http://collect.airbrake.io/api/v3/projects/[PROJECT_ID]/notices?key=[API_KEY] |
| 946 | 955 | * url = window.location.protocol + '://' + this.options.host + '/api/v3/projects' + this.options.projectId + '/notices?key=' + this.options.key; |
| 947 | 956 | */ |
| 948 | - outputData = JSON.stringify(this.generateJSON(this.generateDataJSON(error))); | |
| 949 | - url = ('https:' == document.location.protocol ? 'https://' : 'http://') + this.options.host + '/api/v3/projects/' + this.options.projectId + '/notices?key=' + this.xmlData.key; | |
| 950 | - _sendPOSTRequest(url, outputData); | |
| 957 | + jsonData = this.generateDataJSON(error); | |
| 958 | + if (this.shouldSendData(jsonData)){ | |
| 959 | + outputData = JSON.stringify(this.generateJSON(jsonData)); | |
| 960 | + url = ('https:' == document.location.protocol ? 'https://' : 'http://') + this.options.host + '/api/v3/projects/' + this.options.projectId + '/notices?key=' + this.xmlData.key; | |
| 961 | + _sendPOSTRequest(url, outputData); | |
| 962 | + } | |
| 951 | 963 | break; |
| 952 | 964 | |
| 953 | 965 | default: |
| ... | ... | @@ -1150,6 +1162,13 @@ printStackTrace.implementation.prototype = { |
| 1150 | 1162 | continue; |
| 1151 | 1163 | } |
| 1152 | 1164 | |
| 1165 | + // Special case for sprocket coffee stacktrace: | |
| 1166 | + // "Function.foo (http://host/file.js?body=1:666:42)" becomes "Function.foo @http://host/file.js?body=1:666" | |
| 1167 | + if (stacktrace[i].match(/\([^\s]+:(\d+):(\d+)\)$/)) { | |
| 1168 | + stacktrace[i] = stacktrace[i].replace(/\((.+):(\d+):(\d+)\)$/, '@$1:$2') | |
| 1169 | + continue; | |
| 1170 | + } | |
| 1171 | + | |
| 1153 | 1172 | if (stacktrace[i].indexOf('@') === -1) { |
| 1154 | 1173 | stacktrace[i] += '@unsupported.js'; |
| 1155 | 1174 | } |
| ... | ... | @@ -1168,16 +1187,31 @@ printStackTrace.implementation.prototype = { |
| 1168 | 1187 | } |
| 1169 | 1188 | |
| 1170 | 1189 | return true; |
| 1190 | + }, | |
| 1191 | + | |
| 1192 | + shouldSendData: function (jsonData) { | |
| 1193 | + var shouldSend = true, i; | |
| 1194 | + | |
| 1195 | + for ( i = 0; i < Global._filters.length; i++ ) { | |
| 1196 | + if ( ! Global._filters[i](jsonData) ){ | |
| 1197 | + shouldSend = false; | |
| 1198 | + } | |
| 1199 | + } | |
| 1200 | + | |
| 1201 | + return shouldSend; | |
| 1171 | 1202 | } |
| 1172 | 1203 | }; |
| 1173 | 1204 | |
| 1205 | + var oldOnerror = window.onerror; | |
| 1174 | 1206 | window.onerror = function (message, file, line, code, error) { |
| 1175 | 1207 | setTimeout(function () { |
| 1176 | 1208 | var e = error || {stack: '()@' + file + ':' + line} |
| 1177 | 1209 | e.message = message |
| 1178 | 1210 | new Notifier().notify(e); |
| 1179 | 1211 | }, 0); |
| 1180 | - | |
| 1212 | + if (oldOnerror) { | |
| 1213 | + return oldOnerror(message, file, line, code, error); | |
| 1214 | + } | |
| 1181 | 1215 | return true; |
| 1182 | 1216 | }; |
| 1183 | 1217 | })(); | ... | ... |
| ... | ... | @@ -0,0 +1,31 @@ |
| 1 | +require 'acceptance/acceptance_helper' | |
| 2 | + | |
| 3 | +feature 'password reset token' do | |
| 4 | + let(:user) { Fabricate :user } | |
| 5 | + | |
| 6 | + scenario 'receives correct password reset token' do | |
| 7 | + host = ActionMailer::Base.default_url_options.values_at(:host).first | |
| 8 | + port = ActionMailer::Base.default_url_options.values_at(:port).first | |
| 9 | + port = port.blank? ? '' : ':' + port | |
| 10 | + regex = %r{http://#{host}#{port}/users/password/edit\?reset_password_token=([A-Za-z0-9\-_]+)} | |
| 11 | + | |
| 12 | + visit 'https://brighterr.herokuapp.com/users/password/new' | |
| 13 | + fill_in 'Email', with: user.email | |
| 14 | + click_button 'Send me reset password instructions' | |
| 15 | + expect(page).to have_content I18n.t('devise.passwords.send_instructions') | |
| 16 | + | |
| 17 | + mail = ActionMailer::Base.deliveries.last | |
| 18 | + expect(mail.subject).to match(/Reset password instructions/) | |
| 19 | + expect(mail.body.encoded).to_not be_empty | |
| 20 | + expect(mail.body.encoded).to match(/change your password/) | |
| 21 | + expect(mail.body.encoded).to match(regex) | |
| 22 | + if mail.body.encoded =~ regex | |
| 23 | + visit "/users/password/edit?reset_password_token=#{$1}" | |
| 24 | + expect(page).to have_content 'Change your password' | |
| 25 | + fill_in 'New password', with: 'test12345' | |
| 26 | + fill_in 'Type your new password again', with: 'test12345' | |
| 27 | + click_button 'Change my password' | |
| 28 | + expect(page).to_not have_content 'Reset password token is invalid' | |
| 29 | + end | |
| 30 | + end | |
| 31 | +end | ... | ... |
spec/controllers/apps_controller_spec.rb
| ... | ... | @@ -73,6 +73,16 @@ describe AppsController do |
| 73 | 73 | expect(response).to be_success |
| 74 | 74 | end |
| 75 | 75 | |
| 76 | + it "should list available watchers by name" do | |
| 77 | + Fabricate(:user, :name => "Carol") | |
| 78 | + Fabricate(:user, :name => "Alice") | |
| 79 | + Fabricate(:user, :name => "Betty") | |
| 80 | + | |
| 81 | + get :show, :id => app.id | |
| 82 | + | |
| 83 | + expect(controller.users.to_a).to eq(User.all.to_a.sort_by(&:name)) | |
| 84 | + end | |
| 85 | + | |
| 76 | 86 | context "pagination" do |
| 77 | 87 | before(:each) do |
| 78 | 88 | 35.times { Fabricate(:err, :problem => Fabricate(:problem, :app => app)) } |
| ... | ... | @@ -395,4 +405,3 @@ describe AppsController do |
| 395 | 405 | end |
| 396 | 406 | |
| 397 | 407 | end |
| 398 | - | ... | ... |
spec/controllers/problems_controller_spec.rb
| ... | ... | @@ -390,6 +390,34 @@ describe ProblemsController do |
| 390 | 390 | }.to change(Problem, :count).by(-1) |
| 391 | 391 | end |
| 392 | 392 | end |
| 393 | + | |
| 394 | + describe "POST /apps/:app_id/problems/destroy_all" do | |
| 395 | + before do | |
| 396 | + sign_in Fabricate(:admin) | |
| 397 | + @app = Fabricate(:app) | |
| 398 | + @problem1 = Fabricate(:problem, :app=>@app) | |
| 399 | + @problem2 = Fabricate(:problem, :app=>@app) | |
| 400 | + end | |
| 401 | + | |
| 402 | + it "destroys all problems" do | |
| 403 | + expect { | |
| 404 | + post :destroy_all, :app_id => @app.id | |
| 405 | + }.to change(Problem, :count).by(-2) | |
| 406 | + expect(controller.app).to eq @app | |
| 407 | + end | |
| 408 | + | |
| 409 | + it "should display a message" do | |
| 410 | + put :destroy_all, :app_id => @app.id | |
| 411 | + expect(request.flash[:success]).to match(/been deleted/) | |
| 412 | + end | |
| 413 | + | |
| 414 | + it "should redirect back to the app page" do | |
| 415 | + request.env["Referer"] = edit_app_path(@app) | |
| 416 | + put :destroy_all, :app_id => @app.id | |
| 417 | + expect(response).to redirect_to(edit_app_path(@app)) | |
| 418 | + end | |
| 419 | + end | |
| 420 | + | |
| 393 | 421 | end |
| 394 | 422 | |
| 395 | 423 | end | ... | ... |
spec/fabricators/notification_service_fabricator.rb
| ... | ... | @@ -12,6 +12,6 @@ Fabricator :gtalk_notification_service, :from => :notification_service, :class_n |
| 12 | 12 | service { sequence :word } |
| 13 | 13 | end |
| 14 | 14 | |
| 15 | -%w(campfire flowdock hipchat hoiio hubot pushover webhook).each do |t| | |
| 15 | +%w(campfire flowdock hipchat hoiio hubot pushover slack webhook).each do |t| | |
| 16 | 16 | Fabricator "#{t}_notification_service".to_sym, :from => :notification_service, :class_name => "NotificationService::#{t.camelcase}Service" |
| 17 | 17 | end | ... | ... |
spec/models/error_report_spec.rb
| ... | ... | @@ -47,6 +47,19 @@ describe ErrorReport do |
| 47 | 47 | end |
| 48 | 48 | end |
| 49 | 49 | |
| 50 | + describe "#fingerprint_strategy" do | |
| 51 | + after(:all) { | |
| 52 | + error_report.fingerprint_strategy = Fingerprint | |
| 53 | + } | |
| 54 | + | |
| 55 | + it "should be possible to change how fingerprints are generated" do | |
| 56 | + strategy = double() | |
| 57 | + strategy.should_receive(:generate){ 'fingerprints' } | |
| 58 | + error_report.fingerprint_strategy = strategy | |
| 59 | + error_report.generate_notice! | |
| 60 | + end | |
| 61 | + end | |
| 62 | + | |
| 50 | 63 | describe "#generate_notice!" do |
| 51 | 64 | it "save a notice" do |
| 52 | 65 | expect { | ... | ... |
| ... | ... | @@ -0,0 +1,43 @@ |
| 1 | +require 'spec_helper' | |
| 2 | + | |
| 3 | +describe LegacyFingerprint do | |
| 4 | + context 'being created' do | |
| 5 | + let(:backtrace) do | |
| 6 | + Backtrace.create(:raw => [ | |
| 7 | + { | |
| 8 | + "number"=>"17", | |
| 9 | + "file"=>"[GEM_ROOT]/gems/activesupport/lib/active_support/callbacks.rb", | |
| 10 | + "method"=>"_run__2497084960985961383__process_action__2062871603614456254__callbacks" | |
| 11 | + } | |
| 12 | + ]) | |
| 13 | + end | |
| 14 | + let(:notice1) { Fabricate.build(:notice, :backtrace => backtrace) } | |
| 15 | + let(:notice2) { Fabricate.build(:notice, :backtrace => backtrace_2) } | |
| 16 | + | |
| 17 | + context "with same backtrace" do | |
| 18 | + let(:backtrace_2) do | |
| 19 | + backtrace | |
| 20 | + backtrace.lines.last.method = '_run__FRAGMENT__process_action__FRAGMENT__callbacks' | |
| 21 | + backtrace.save | |
| 22 | + backtrace | |
| 23 | + end | |
| 24 | + | |
| 25 | + it "normalizes the fingerprint of generated methods" do | |
| 26 | + expect(LegacyFingerprint.generate(notice1, "api key")).to eql LegacyFingerprint.generate(notice2, "api key") | |
| 27 | + end | |
| 28 | + end | |
| 29 | + | |
| 30 | + context "with same backtrace where FRAGMENT has not been extracted" do | |
| 31 | + let(:backtrace_2) do | |
| 32 | + backtrace | |
| 33 | + backtrace.lines.last.method = '_run__998857585768765__process_action__1231231312321313__callbacks' | |
| 34 | + backtrace.save | |
| 35 | + backtrace | |
| 36 | + end | |
| 37 | + | |
| 38 | + it "normalizes the fingerprint of generated methods" do | |
| 39 | + expect(LegacyFingerprint.generate(notice1, "api key")).to eql LegacyFingerprint.generate(notice2, "api key") | |
| 40 | + end | |
| 41 | + end | |
| 42 | + end | |
| 43 | +end | ... | ... |
spec/models/notice_spec.rb
| ... | ... | @@ -35,6 +35,28 @@ describe Notice do |
| 35 | 35 | end |
| 36 | 36 | end |
| 37 | 37 | |
| 38 | + describe "to_curl" do | |
| 39 | + let(:notice) { Fabricate.build(:notice, request: request) } | |
| 40 | + | |
| 41 | + context "when it has a request url" do | |
| 42 | + let(:request) { {'url' => "http://example.com/resource/12", 'cgi-data' => {'HTTP_USER_AGENT' => 'Mozilla/5.0'}} } | |
| 43 | + | |
| 44 | + it 'has a curl representation' do | |
| 45 | + cmd = notice.to_curl | |
| 46 | + expect(cmd).to eq(%q[curl -X GET -H 'User-Agent: Mozilla/5.0' http://example.com/resource/12]) | |
| 47 | + end | |
| 48 | + end | |
| 49 | + | |
| 50 | + context "when it has not a request url" do | |
| 51 | + let(:request) { {'cgi-data' => {'HTTP_USER_AGENT' => 'Mozilla/5.0'}} } | |
| 52 | + | |
| 53 | + it 'has a curl representation' do | |
| 54 | + cmd = notice.to_curl | |
| 55 | + expect(cmd).to eq "N/A" | |
| 56 | + end | |
| 57 | + end | |
| 58 | + end | |
| 59 | + | |
| 38 | 60 | describe "user agent" do |
| 39 | 61 | it "should be parsed and human-readable" do |
| 40 | 62 | notice = Fabricate.build(:notice, :request => {'cgi-data' => { | ... | ... |
| ... | ... | @@ -0,0 +1,29 @@ |
| 1 | +require 'spec_helper' | |
| 2 | + | |
| 3 | +describe NotificationService::SlackService do | |
| 4 | + it "it should send a notification to Slack with channel" do | |
| 5 | + # setup | |
| 6 | + notice = Fabricate :notice | |
| 7 | + notification_service = Fabricate :slack_notification_service, :app => notice.app | |
| 8 | + problem = notice.problem | |
| 9 | + | |
| 10 | + # faraday stubbing | |
| 11 | + payload = {:text => notification_service.message_for_slack(problem), :channel => notification_service.room_id}.to_json | |
| 12 | + expect(HTTParty).to receive(:post).with(notification_service.url, :body => payload, :headers => {"Content-Type" => "application/json"}).and_return(true) | |
| 13 | + | |
| 14 | + notification_service.create_notification(problem) | |
| 15 | + end | |
| 16 | + | |
| 17 | + it "it should send a notification to Slack without a channel" do | |
| 18 | + # setup | |
| 19 | + notice = Fabricate :notice | |
| 20 | + notification_service = Fabricate :slack_notification_service, :app => notice.app, :room_id => "" | |
| 21 | + problem = notice.problem | |
| 22 | + | |
| 23 | + # faraday stubbing | |
| 24 | + payload = {:text => notification_service.message_for_slack(problem)}.to_json | |
| 25 | + expect(HTTParty).to receive(:post).with(notification_service.url, :body => payload, :headers => {"Content-Type" => "application/json"}).and_return(true) | |
| 26 | + | |
| 27 | + notification_service.create_notification(problem) | |
| 28 | + end | |
| 29 | +end | ... | ... |
spec/views/apps/edit.html.haml_spec.rb
| ... | ... | @@ -14,6 +14,11 @@ describe "apps/edit.html.haml" do |
| 14 | 14 | view.content_for(:action_bar) |
| 15 | 15 | end |
| 16 | 16 | |
| 17 | + it "should confirm the 'reset' link" do | |
| 18 | + render | |
| 19 | + expect(action_bar).to have_selector('a.button[data-confirm="%s"]' % I18n.t('apps.confirm_destroy_all_problems')) | |
| 20 | + end | |
| 21 | + | |
| 17 | 22 | it "should confirm the 'destroy' link" do |
| 18 | 23 | render |
| 19 | 24 | expect(action_bar).to have_selector('a.button[data-confirm="%s"]' % I18n.t('apps.confirm_delete')) | ... | ... |