Commit 72e373804c57903d195f8b5e1957f0bc5a11f1b5
Exists in
master
and in
1 other branch
Merge branch 'master' of github:errbit/errbit
Showing
66 changed files
with
1136 additions
and
563 deletions
Show diff stats
.gitignore
CHANGELOG.md
| ... | ... | @@ -4,12 +4,17 @@ |
| 4 | 4 | |
| 5 | 5 | - Update some gems ([@shingara][]) |
| 6 | 6 | - [#492][] Improve some Pjax call ([@nfedyashev][]) |
| 7 | -- [#428][] Add the support of Unfuddle Tracker ([@parallel588][]) | |
| 7 | +- [#428][] Add the support of Unfuddle Issue Tracker ([@parallel588][]) | |
| 8 | 8 | - Avoid to delete his own user ([@shingara][]) |
| 9 | 9 | - [#456][] Avoid to delete admin access of current user logged ([@shingara][]) |
| 10 | 10 | - [#253][] Refactor the Fingerprint generation ([@boblail][]) |
| 11 | 11 | - [#508][] Merge comments to when you merge problems ([@shingara][]) |
| 12 | 12 | - Update the Devise Gem to the last one ([@shingara][]) |
| 13 | +- [#524][] Add current user information on the notifer.js ([@roryf][]) | |
| 14 | +- [#523][] Update javascript-stacktrace ([@aliscott][]) | |
| 15 | +- [#516][] Add Jira Issue tracker ([@xenji][]) | |
| 16 | +- [#512][] Add capabilities to configure the use of sendmail to send | |
| 17 | + email from Errbit ([@shingara][]) | |
| 13 | 18 | |
| 14 | 19 | ### Bug Fixes |
| 15 | 20 | |
| ... | ... | @@ -76,24 +81,28 @@ |
| 76 | 81 | [#506]: https://github.com/errbit/errbit/issues/506 |
| 77 | 82 | [#508]: https://github.com/errbit/errbit/issues/508 |
| 78 | 83 | [#514]: https://github.com/errbit/errbit/issues/514 |
| 84 | +[#516]: https://github.com/errbit/errbit/issues/516 | |
| 79 | 85 | [#517]: https://github.com/errbit/errbit/issues/517 |
| 86 | +[#524]: https://github.com/errbit/errbit/issues/524 | |
| 80 | 87 | |
| 81 | 88 | <!-- Contributor on Errbit Thanks to all of them --> |
| 82 | 89 | |
| 90 | +[@Gonzih]: https://github.com/Gonzih | |
| 83 | 91 | [@SamSaffron]: https://github.com/SamSaffron |
| 84 | 92 | [@adamjt]: https://github.com/adamjt |
| 93 | +[@aliscott]: http://github.com/aliscott | |
| 85 | 94 | [@alvarobp]: https://github.com/alvarobp |
| 95 | +[@boblail]: https://github.com/boblail | |
| 86 | 96 | [@chadcf]: https://github.com/chadcf |
| 87 | -[@mildavw]: https://github.com/mildavw | |
| 88 | -[@sdepold]: https://github.com/sdepold | |
| 89 | -[@shingara]: https://github.com/shingara | |
| 90 | -[@tamaloa]: https://github.com/tamaloa | |
| 91 | 97 | [@ivanyv]: https://github.com/ivanyv |
| 92 | 98 | [@manuelmeurer]: https://github.com/manuelmeurer |
| 93 | -[@tvdeyen]: https://github.com/tvdeyen | |
| 99 | +[@mildavw]: https://github.com/mildavw | |
| 100 | +[@mildavw]: https://github.com/mildavw | |
| 94 | 101 | [@nfedyashev]: https://github.com/nfedyashev |
| 95 | 102 | [@parallel588]: https://github.com/parallel588 |
| 96 | -[@Gonzih]: https://github.com/Gonzih | |
| 97 | -[@boblail]: https://github.com/boblail | |
| 98 | 103 | [@roryf]: https://github.com/roryf |
| 99 | -[@mildavw]: https://github.com/mildavw | |
| 104 | +[@sdepold]: https://github.com/sdepold | |
| 105 | +[@shingara]: https://github.com/shingara | |
| 106 | +[@tamaloa]: https://github.com/tamaloa | |
| 107 | +[@tvdeyen]: https://github.com/tvdeyen | |
| 108 | +[@xenji]: https://github.com/xenji | ... | ... |
Gemfile
| ... | ... | @@ -11,7 +11,6 @@ gem 'htmlentities' |
| 11 | 11 | gem 'rack-ssl', :require => 'rack/ssl' # force SSL |
| 12 | 12 | |
| 13 | 13 | gem 'useragent' |
| 14 | -gem 'inherited_resources' | |
| 15 | 14 | gem 'decent_exposure' |
| 16 | 15 | gem 'strong_parameters' |
| 17 | 16 | gem 'SystemTimer', :platform => :ruby_18 |
| ... | ... | @@ -45,12 +44,15 @@ gem 'octokit' |
| 45 | 44 | gem 'gitlab', :git => 'https://github.com/NARKOZ/gitlab.git' |
| 46 | 45 | |
| 47 | 46 | # Bitbucket Issues |
| 48 | -gem 'bitbucket_rest_api' | |
| 47 | +gem 'bitbucket_rest_api', :require => false | |
| 49 | 48 | |
| 50 | 49 | # Unfuddle |
| 51 | 50 | gem "taskmapper", "~> 0.8.0" |
| 52 | 51 | gem "taskmapper-unfuddle", "~> 0.7.0" |
| 53 | 52 | |
| 53 | +# Jira | |
| 54 | +gem 'jira-ruby', :require => 'jira' | |
| 55 | + | |
| 54 | 56 | # Notification services |
| 55 | 57 | # --------------------------------------- |
| 56 | 58 | # Campfire ( We can't upgrade to 1.0 because drop support of ruby 1.8 |
| ... | ... | @@ -87,11 +89,9 @@ group :development, :test do |
| 87 | 89 | gem 'rspec-rails', '~> 2.6' |
| 88 | 90 | gem 'webmock', :require => false |
| 89 | 91 | gem 'airbrake', :require => false |
| 90 | - unless ENV["CI"] | |
| 91 | - gem 'ruby-debug', :platform => :mri_18 | |
| 92 | - gem 'debugger', :platform => :mri_19 | |
| 93 | - gem 'pry-rails' | |
| 94 | - end | |
| 92 | + gem 'ruby-debug', :platform => :mri_18 | |
| 93 | + gem 'debugger', :platform => :mri_19 | |
| 94 | + gem 'pry-rails' | |
| 95 | 95 | # gem 'rpm_contrib' |
| 96 | 96 | # gem 'newrelic_rpm' |
| 97 | 97 | gem 'quiet_assets' |
| ... | ... | @@ -118,7 +118,7 @@ group :test do |
| 118 | 118 | # DatabaseCleaner 1.0.0 drop the support of ruby 1.8.7 |
| 119 | 119 | gem 'database_cleaner', '~> 0.9.0' |
| 120 | 120 | gem 'email_spec' |
| 121 | - gem 'timecop' | |
| 121 | + gem 'timecop', '0.6.1' # last version compatible to ruby 1.8 | |
| 122 | 122 | gem 'coveralls', :require => false |
| 123 | 123 | end |
| 124 | 124 | ... | ... |
Gemfile.lock
| ... | ... | @@ -40,13 +40,13 @@ GEM |
| 40 | 40 | activesupport (3.2.13) |
| 41 | 41 | i18n (= 0.6.1) |
| 42 | 42 | multi_json (~> 1.0) |
| 43 | - addressable (2.3.4) | |
| 43 | + addressable (2.3.5) | |
| 44 | 44 | airbrake (3.1.12) |
| 45 | 45 | activesupport |
| 46 | 46 | builder |
| 47 | 47 | json |
| 48 | 48 | arel (3.0.2) |
| 49 | - bcrypt-ruby (3.0.1) | |
| 49 | + bcrypt-ruby (3.1.1) | |
| 50 | 50 | better_errors (0.9.0) |
| 51 | 51 | coderay (>= 1.0.0) |
| 52 | 52 | erubis (>= 2.6.6) |
| ... | ... | @@ -66,7 +66,7 @@ GEM |
| 66 | 66 | callsite (0.0.11) |
| 67 | 67 | campy (0.1.3) |
| 68 | 68 | multi_json (~> 1.0) |
| 69 | - capistrano (2.15.4) | |
| 69 | + capistrano (2.15.5) | |
| 70 | 70 | highline |
| 71 | 71 | net-scp (>= 1.0.0) |
| 72 | 72 | net-sftp (>= 2.0.0) |
| ... | ... | @@ -90,19 +90,19 @@ GEM |
| 90 | 90 | rest-client |
| 91 | 91 | simplecov (>= 0.7) |
| 92 | 92 | thor |
| 93 | - crack (0.4.0) | |
| 93 | + crack (0.4.1) | |
| 94 | 94 | safe_yaml (~> 0.9.0) |
| 95 | 95 | css_parser (1.3.4) |
| 96 | 96 | addressable |
| 97 | 97 | daemons (1.1.9) |
| 98 | 98 | database_cleaner (0.9.1) |
| 99 | 99 | debug_inspector (0.0.2) |
| 100 | - debugger (1.6.0) | |
| 100 | + debugger (1.6.1) | |
| 101 | 101 | columnize (>= 0.3.1) |
| 102 | 102 | debugger-linecache (~> 1.2.0) |
| 103 | - debugger-ruby_core_source (~> 1.2.1) | |
| 103 | + debugger-ruby_core_source (~> 1.2.3) | |
| 104 | 104 | debugger-linecache (1.2.0) |
| 105 | - debugger-ruby_core_source (1.2.2) | |
| 105 | + debugger-ruby_core_source (1.2.3) | |
| 106 | 106 | decent_exposure (2.2.0) |
| 107 | 107 | devise (2.2.4) |
| 108 | 108 | bcrypt-ruby (~> 3.0) |
| ... | ... | @@ -111,7 +111,7 @@ GEM |
| 111 | 111 | warden (~> 1.2.1) |
| 112 | 112 | diff-lcs (1.2.4) |
| 113 | 113 | dotenv (0.8.0) |
| 114 | - email_spec (1.4.0) | |
| 114 | + email_spec (1.5.0) | |
| 115 | 115 | launchy (~> 2.1) |
| 116 | 116 | mail (~> 2.2) |
| 117 | 117 | erubis (2.7.0) |
| ... | ... | @@ -119,10 +119,11 @@ GEM |
| 119 | 119 | execjs (1.4.0) |
| 120 | 120 | multi_json (~> 1.0) |
| 121 | 121 | fabrication (1.3.2) |
| 122 | - faraday (0.8.7) | |
| 123 | - multipart-post (~> 1.1) | |
| 122 | + faraday (0.8.8) | |
| 123 | + multipart-post (~> 1.2.0) | |
| 124 | 124 | faraday_middleware (0.8.8) |
| 125 | 125 | faraday (>= 0.7.4, < 0.9) |
| 126 | + ffi (1.9.0) | |
| 126 | 127 | flowdock (0.3.1) |
| 127 | 128 | httparty (~> 0.7) |
| 128 | 129 | multi_json |
| ... | ... | @@ -133,7 +134,6 @@ GEM |
| 133 | 134 | tilt |
| 134 | 135 | happymapper (0.4.0) |
| 135 | 136 | libxml-ruby (~> 2.0) |
| 136 | - has_scope (0.5.1) | |
| 137 | 137 | hashie (1.2.0) |
| 138 | 138 | highline (1.6.19) |
| 139 | 139 | hike (1.2.3) |
| ... | ... | @@ -151,9 +151,10 @@ GEM |
| 151 | 151 | multi_xml (>= 0.5.2) |
| 152 | 152 | httpauth (0.2.0) |
| 153 | 153 | i18n (0.6.1) |
| 154 | - inherited_resources (1.4.0) | |
| 155 | - has_scope (~> 0.5.0) | |
| 156 | - responders (~> 0.9) | |
| 154 | + jira-ruby (0.1.2) | |
| 155 | + activesupport | |
| 156 | + oauth | |
| 157 | + railties | |
| 157 | 158 | journey (1.0.4) |
| 158 | 159 | jquery-rails (2.1.4) |
| 159 | 160 | railties (>= 3.0, < 5.0) |
| ... | ... | @@ -181,7 +182,7 @@ GEM |
| 181 | 182 | callsite |
| 182 | 183 | rack-contrib |
| 183 | 184 | railties |
| 184 | - method_source (0.8.1) | |
| 185 | + method_source (0.8.2) | |
| 185 | 186 | mime-types (1.23) |
| 186 | 187 | mongo (1.8.6) |
| 187 | 188 | bson (~> 1.8.6) |
| ... | ... | @@ -197,16 +198,17 @@ GEM |
| 197 | 198 | multi_json (1.7.7) |
| 198 | 199 | multi_xml (0.5.4) |
| 199 | 200 | multipart-post (1.2.0) |
| 200 | - net-scp (1.1.1) | |
| 201 | + net-scp (1.1.2) | |
| 201 | 202 | net-ssh (>= 2.6.5) |
| 202 | 203 | net-sftp (2.1.2) |
| 203 | 204 | net-ssh (>= 2.6.5) |
| 204 | - net-ssh (2.6.7) | |
| 205 | + net-ssh (2.6.8) | |
| 205 | 206 | net-ssh-gateway (1.2.0) |
| 206 | 207 | net-ssh (>= 2.6.5) |
| 207 | 208 | nokogiri (1.5.10) |
| 208 | 209 | nokogiri-happymapper (0.5.7) |
| 209 | 210 | nokogiri (~> 1.5) |
| 211 | + oauth (0.4.7) | |
| 210 | 212 | oauth2 (0.8.1) |
| 211 | 213 | faraday (~> 0.8) |
| 212 | 214 | httpauth (~> 0.1) |
| ... | ... | @@ -222,7 +224,7 @@ GEM |
| 222 | 224 | omniauth (1.1.4) |
| 223 | 225 | hashie (>= 1.2, < 3) |
| 224 | 226 | rack |
| 225 | - omniauth-github (1.1.0) | |
| 227 | + omniauth-github (1.1.1) | |
| 226 | 228 | omniauth (~> 1.0) |
| 227 | 229 | omniauth-oauth2 (~> 1.1) |
| 228 | 230 | omniauth-oauth2 (1.1.1) |
| ... | ... | @@ -251,7 +253,7 @@ GEM |
| 251 | 253 | coderay (~> 1.0.5) |
| 252 | 254 | method_source (~> 0.8) |
| 253 | 255 | slop (~> 3.4) |
| 254 | - pry-rails (0.3.1) | |
| 256 | + pry-rails (0.3.2) | |
| 255 | 257 | pry (>= 0.9.10) |
| 256 | 258 | quiet_assets (1.0.2) |
| 257 | 259 | railties (>= 3.1, < 5.0) |
| ... | ... | @@ -288,22 +290,20 @@ GEM |
| 288 | 290 | rdoc (3.12.2) |
| 289 | 291 | json (~> 1.4) |
| 290 | 292 | ref (1.0.5) |
| 291 | - responders (0.9.3) | |
| 292 | - railties (~> 3.1) | |
| 293 | 293 | rest-client (1.6.7) |
| 294 | 294 | mime-types (>= 1.16) |
| 295 | 295 | ri_cal (0.8.8) |
| 296 | - rspec-core (2.13.1) | |
| 297 | - rspec-expectations (2.13.0) | |
| 296 | + rspec-core (2.14.4) | |
| 297 | + rspec-expectations (2.14.0) | |
| 298 | 298 | diff-lcs (>= 1.1.3, < 2.0) |
| 299 | - rspec-mocks (2.13.1) | |
| 300 | - rspec-rails (2.13.2) | |
| 299 | + rspec-mocks (2.14.2) | |
| 300 | + rspec-rails (2.14.0) | |
| 301 | 301 | actionpack (>= 3.0) |
| 302 | 302 | activesupport (>= 3.0) |
| 303 | 303 | railties (>= 3.0) |
| 304 | - rspec-core (~> 2.13.0) | |
| 305 | - rspec-expectations (~> 2.13.0) | |
| 306 | - rspec-mocks (~> 2.13.0) | |
| 304 | + rspec-core (~> 2.14.0) | |
| 305 | + rspec-expectations (~> 2.14.0) | |
| 306 | + rspec-mocks (~> 2.14.0) | |
| 307 | 307 | ruby-debug (0.10.4) |
| 308 | 308 | columnize (>= 0.1) |
| 309 | 309 | ruby-debug-base (~> 0.10.4.0) |
| ... | ... | @@ -315,7 +315,7 @@ GEM |
| 315 | 315 | rushover (0.3.0) |
| 316 | 316 | json |
| 317 | 317 | rest-client |
| 318 | - safe_yaml (0.9.3) | |
| 318 | + safe_yaml (0.9.5) | |
| 319 | 319 | selenium-webdriver (2.33.0) |
| 320 | 320 | childprocess (>= 0.2.5) |
| 321 | 321 | multi_json (~> 1.0) |
| ... | ... | @@ -326,7 +326,7 @@ GEM |
| 326 | 326 | multi_json (~> 1.0) |
| 327 | 327 | simplecov-html (~> 0.7.1) |
| 328 | 328 | simplecov-html (0.7.1) |
| 329 | - slop (3.4.5) | |
| 329 | + slop (3.4.6) | |
| 330 | 330 | sprockets (2.2.2) |
| 331 | 331 | hike (~> 1.2) |
| 332 | 332 | multi_json (~> 1.0) |
| ... | ... | @@ -360,18 +360,18 @@ GEM |
| 360 | 360 | railties (> 3.2.8, < 4.0.0) |
| 361 | 361 | sprockets (>= 2.0.0) |
| 362 | 362 | tzinfo (0.3.37) |
| 363 | - uglifier (2.1.1) | |
| 363 | + uglifier (2.1.2) | |
| 364 | 364 | execjs (>= 0.3.0) |
| 365 | 365 | multi_json (~> 1.0, >= 1.0.2) |
| 366 | - underscore-rails (1.4.4) | |
| 366 | + underscore-rails (1.5.1) | |
| 367 | 367 | unicorn (4.6.3) |
| 368 | 368 | kgio (~> 2.6) |
| 369 | 369 | rack |
| 370 | 370 | raindrops (~> 0.7) |
| 371 | 371 | useragent (0.6.0) |
| 372 | - warden (1.2.1) | |
| 372 | + warden (1.2.3) | |
| 373 | 373 | rack (>= 1.0) |
| 374 | - webmock (1.12.0) | |
| 374 | + webmock (1.13.0) | |
| 375 | 375 | addressable (>= 2.2.7) |
| 376 | 376 | crack (>= 0.3.2) |
| 377 | 377 | websocket (1.0.7) |
| ... | ... | @@ -412,7 +412,7 @@ DEPENDENCIES |
| 412 | 412 | hoptoad_notifier (~> 2.4) |
| 413 | 413 | htmlentities |
| 414 | 414 | httparty |
| 415 | - inherited_resources | |
| 415 | + jira-ruby | |
| 416 | 416 | jquery-rails (~> 2.1.4) |
| 417 | 417 | kaminari (>= 0.14.1) |
| 418 | 418 | launchy |
| ... | ... | @@ -442,7 +442,7 @@ DEPENDENCIES |
| 442 | 442 | taskmapper-unfuddle (~> 0.7.0) |
| 443 | 443 | therubyracer |
| 444 | 444 | thin |
| 445 | - timecop | |
| 445 | + timecop (= 0.6.1) | |
| 446 | 446 | turbo-sprockets-rails3 |
| 447 | 447 | uglifier (>= 1.0.3) |
| 448 | 448 | underscore-rails | ... | ... |
README.md
| ... | ... | @@ -423,6 +423,18 @@ card_type = Defect, status = Open, priority = Essential |
| 423 | 423 | * Project id the id of your project where your ticket is create |
| 424 | 424 | * Milestone id the id of your milestone where your ticket is create |
| 425 | 425 | |
| 426 | +** Jira Issue Integration ** | |
| 427 | + | |
| 428 | +* base_url the jira URL | |
| 429 | +* context_path Context Path (Just "/" if empty otherwise with leading slash) | |
| 430 | +* username HTTP Basic Auth User | |
| 431 | +* password HTTP Basic Auth Password | |
| 432 | +* project_id The project Key where the issue will be created | |
| 433 | +* account Assign to this user. If empty, Jira takes the project default. | |
| 434 | +* issue_component Website - Other | |
| 435 | +* issue_type Issue type | |
| 436 | +* issue_priority Priority | |
| 437 | + | |
| 426 | 438 | |
| 427 | 439 | What if Errbit has an error? |
| 428 | 440 | ---------------------------- | ... | ... |
1.97 KB
1.97 KB
1.97 KB
1.91 KB
app/controllers/apps_controller.rb
| 1 | -class AppsController < InheritedResources::Base | |
| 1 | +class AppsController < ApplicationController | |
| 2 | + | |
| 3 | + include ProblemsSearcher | |
| 4 | + | |
| 2 | 5 | before_filter :require_admin!, :except => [:index, :show] |
| 3 | 6 | before_filter :parse_email_at_notices_or_set_default, :only => [:create, :update] |
| 4 | 7 | before_filter :parse_notice_at_notices_or_set_default, :only => [:create, :update] |
| 5 | 8 | respond_to :html |
| 6 | 9 | |
| 7 | - def show | |
| 8 | - respond_to do |format| | |
| 9 | - format.html do | |
| 10 | - @all_errs = !!params[:all_errs] | |
| 10 | + expose(:app_scope) { | |
| 11 | + (current_user.admin? ? App : current_user.apps) | |
| 12 | + } | |
| 13 | + | |
| 14 | + expose(:apps) { | |
| 15 | + app_scope.all.sort | |
| 16 | + } | |
| 17 | + | |
| 18 | + expose(:app, :ancestor => :app_scope) | |
| 19 | + | |
| 20 | + expose(:all_errs) { | |
| 21 | + !!params[:all_errs] | |
| 22 | + } | |
| 23 | + expose(:problems) { | |
| 24 | + if request.format == :atom | |
| 25 | + app.problems.unresolved.ordered | |
| 26 | + else | |
| 27 | + pr = app.problems | |
| 28 | + pr = pr.unresolved unless all_errs | |
| 29 | + pr.in_env( | |
| 30 | + params[:environment] | |
| 31 | + ).ordered_by(params_sort, params_order).page(params[:page]).per(current_user.per_page) | |
| 32 | + end | |
| 33 | + } | |
| 11 | 34 | |
| 12 | - @sort = params[:sort] | |
| 13 | - @order = params[:order] | |
| 14 | - @sort = "last_notice_at" unless %w{message app last_deploy_at last_notice_at count}.member?(@sort) | |
| 15 | - @order = "desc" unless %w{asc desc}.member?(@order) | |
| 35 | + expose(:deploys) { | |
| 36 | + app.deploys.order_by(:created_at.desc).limit(5) | |
| 37 | + } | |
| 16 | 38 | |
| 17 | - @problems = resource.problems | |
| 18 | - @problems = @problems.unresolved unless @all_errs | |
| 19 | - @problems = @problems.in_env(params[:environment]).ordered_by(@sort, @order).page(params[:page]).per(current_user.per_page) | |
| 39 | + def index; end | |
| 40 | + def show | |
| 41 | + app | |
| 42 | + end | |
| 20 | 43 | |
| 21 | - @selected_problems = params[:problems] || [] | |
| 22 | - @deploys = @app.deploys.order_by(:created_at.desc).limit(5) | |
| 23 | - end | |
| 24 | - format.atom do | |
| 25 | - @problems = resource.problems.unresolved.ordered | |
| 26 | - end | |
| 27 | - end | |
| 44 | + def new | |
| 45 | + plug_params(app) | |
| 28 | 46 | end |
| 29 | 47 | |
| 30 | 48 | def create |
| 31 | - @app = App.new(params[:app]) | |
| 32 | 49 | initialize_subclassed_issue_tracker |
| 33 | 50 | initialize_subclassed_notification_service |
| 34 | - create! | |
| 51 | + if app.save | |
| 52 | + redirect_to app_url(app), :flash => { :success => I18n.t('controllers.apps.flash.create.success') } | |
| 53 | + else | |
| 54 | + flash[:error] = I18n.t('controllers.apps.flash.create.error') | |
| 55 | + render :new | |
| 56 | + end | |
| 35 | 57 | end |
| 36 | 58 | |
| 37 | 59 | def update |
| 38 | - @app = resource | |
| 39 | 60 | initialize_subclassed_issue_tracker |
| 40 | 61 | initialize_subclassed_notification_service |
| 41 | - update! | |
| 62 | + if app.save | |
| 63 | + redirect_to app_url(app), :flash => { :success => I18n.t('controllers.apps.flash.update.success') } | |
| 64 | + else | |
| 65 | + flash[:error] = I18n.t('controllers.apps.flash.update.error') | |
| 66 | + render :edit | |
| 67 | + end | |
| 42 | 68 | end |
| 43 | 69 | |
| 44 | - def new | |
| 45 | - plug_params(build_resource) | |
| 46 | - new! | |
| 70 | + def edit | |
| 71 | + plug_params(app) | |
| 47 | 72 | end |
| 48 | 73 | |
| 49 | - def edit | |
| 50 | - plug_params(resource) | |
| 51 | - edit! | |
| 74 | + def destroy | |
| 75 | + if app.destroy | |
| 76 | + redirect_to apps_url, :flash => { :success => I18n.t('controllers.apps.flash.destroy.success') } | |
| 77 | + else | |
| 78 | + flash[:error] = I18n.t('controllers.apps.flash.destroy.error') | |
| 79 | + render :show | |
| 80 | + end | |
| 52 | 81 | end |
| 53 | 82 | |
| 54 | 83 | protected |
| 55 | - def collection | |
| 56 | - @apps ||= end_of_association_chain.all.sort | |
| 57 | - end | |
| 58 | 84 | |
| 59 | 85 | def initialize_subclassed_issue_tracker |
| 60 | 86 | # set the app's issue tracker |
| 61 | 87 | if params[:app][:issue_tracker_attributes] && tracker_type = params[:app][:issue_tracker_attributes][:type] |
| 62 | 88 | if IssueTracker.subclasses.map(&:name).concat(["IssueTracker"]).include?(tracker_type) |
| 63 | - @app.issue_tracker = tracker_type.constantize.new(params[:app][:issue_tracker_attributes]) | |
| 89 | + app.issue_tracker = tracker_type.constantize.new(params[:app][:issue_tracker_attributes]) | |
| 64 | 90 | end |
| 65 | 91 | end |
| 66 | 92 | end |
| ... | ... | @@ -69,21 +95,11 @@ class AppsController < InheritedResources::Base |
| 69 | 95 | # set the app's notification service |
| 70 | 96 | if params[:app][:notification_service_attributes] && notification_type = params[:app][:notification_service_attributes][:type] |
| 71 | 97 | if NotificationService.subclasses.map(&:name).concat(["NotificationService"]).include?(notification_type) |
| 72 | - @app.notification_service = notification_type.constantize.new(params[:app][:notification_service_attributes]) | |
| 98 | + app.notification_service = notification_type.constantize.new(params[:app][:notification_service_attributes]) | |
| 73 | 99 | end |
| 74 | 100 | end |
| 75 | 101 | end |
| 76 | 102 | |
| 77 | - def begin_of_association_chain | |
| 78 | - # Filter the @apps collection to apps watched by the current user, unless user is an admin. | |
| 79 | - # If user is an admin, then no filter is applied, and all apps are shown. | |
| 80 | - current_user unless current_user.admin? | |
| 81 | - end | |
| 82 | - | |
| 83 | - def interpolation_options | |
| 84 | - {:app_name => resource.name} | |
| 85 | - end | |
| 86 | - | |
| 87 | 103 | def plug_params app |
| 88 | 104 | app.watchers.build if app.watchers.none? |
| 89 | 105 | app.issue_tracker = IssueTracker.new unless app.issue_tracker_configured? | ... | ... |
app/controllers/problems_controller.rb
| 1 | +## | |
| 2 | +# Manage problems | |
| 3 | +# | |
| 4 | +# List of actions available : | |
| 5 | +# MEMBER => :show, :edit, :update, :create, :destroy, :resolve, :unresolve, :create_issue, :unlink_issue | |
| 6 | +# COLLECTION => :index, :all, :destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several, :search | |
| 1 | 7 | class ProblemsController < ApplicationController |
| 2 | - before_filter :find_app, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several, :search] | |
| 3 | - before_filter :find_problem, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several, :search] | |
| 4 | - before_filter :find_selected_problems, :only => [:destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several] | |
| 5 | - before_filter :set_sorting_params, :only => [:index, :all, :search] | |
| 6 | - before_filter :set_tracker_params, :only => [:create_issue] | |
| 7 | - | |
| 8 | - def index | |
| 9 | - app_scope = current_user.admin? ? App.all : current_user.apps | |
| 10 | - @all_errs = params[:all_errs] | |
| 11 | - @problems = Problem.for_apps(app_scope).in_env(params[:environment]).all_else_unresolved(@all_errs).ordered_by(@sort, @order) | |
| 12 | - @selected_problems = params[:problems] || [] | |
| 13 | - respond_to do |format| | |
| 14 | - format.html do | |
| 15 | - @problems = @problems.page(params[:page]).per(current_user.per_page) | |
| 16 | - end | |
| 17 | - format.atom | |
| 8 | + | |
| 9 | + | |
| 10 | + include ProblemsSearcher | |
| 11 | + | |
| 12 | + before_filter :need_selected_problem, :only => [ | |
| 13 | + :resolve_several, :unresolve_several, :unmerge_several | |
| 14 | + ] | |
| 15 | + | |
| 16 | + expose(:app) { | |
| 17 | + if current_user.admin? | |
| 18 | + App.find(params[:app_id]) | |
| 19 | + else | |
| 20 | + current_user.apps.find(params[:app_id]) | |
| 18 | 21 | end |
| 19 | - end | |
| 22 | + } | |
| 23 | + | |
| 24 | + expose(:problem) { | |
| 25 | + app.problems.find(params[:id]) | |
| 26 | + } | |
| 27 | + | |
| 28 | + | |
| 29 | + expose(:all_errs) { | |
| 30 | + params[:all_errs] | |
| 31 | + } | |
| 32 | + | |
| 33 | + expose(:app_scope) { | |
| 34 | + apps = current_user.admin? ? App.all : current_user.apps | |
| 35 | + params[:app_id] ? apps.where(:_id => params[:app_id]) : apps | |
| 36 | + } | |
| 37 | + | |
| 38 | + expose(:params_environement) { | |
| 39 | + params[:environment] | |
| 40 | + } | |
| 41 | + | |
| 42 | + expose(:problems) { | |
| 43 | + pro = Problem.for_apps( | |
| 44 | + app_scope | |
| 45 | + ).in_env( | |
| 46 | + params_environement | |
| 47 | + ).all_else_unresolved(all_errs).ordered_by(params_sort, params_order) | |
| 48 | + | |
| 49 | + if request.format == :html | |
| 50 | + pro.page(params[:page]).per(current_user.per_page) | |
| 51 | + else | |
| 52 | + pro | |
| 53 | + end | |
| 54 | + } | |
| 55 | + | |
| 56 | + def index; end | |
| 20 | 57 | |
| 21 | 58 | def show |
| 22 | - @notices = @problem.notices.reverse_ordered.page(params[:notice]).per(1) | |
| 59 | + @notices = problem.notices.reverse_ordered.page(params[:notice]).per(1) | |
| 23 | 60 | @notice = @notices.first |
| 24 | 61 | @comment = Comment.new |
| 25 | 62 | end |
| 26 | 63 | |
| 27 | 64 | def create_issue |
| 28 | - issue_creation = IssueCreation.new(@problem, current_user, params[:tracker]) | |
| 65 | + IssueTracker.update_url_options(request) | |
| 66 | + issue_creation = IssueCreation.new(problem, current_user, params[:tracker]) | |
| 29 | 67 | |
| 30 | 68 | unless issue_creation.execute |
| 31 | - flash[:error] = issue_creation.errors[:base].first | |
| 69 | + flash[:error] = issue_creation.errors.full_messages.join(', ') | |
| 32 | 70 | end |
| 33 | 71 | |
| 34 | - redirect_to app_problem_path(@app, @problem) | |
| 72 | + redirect_to app_problem_path(app, problem) | |
| 35 | 73 | end |
| 36 | 74 | |
| 37 | 75 | def unlink_issue |
| 38 | - @problem.update_attribute :issue_link, nil | |
| 39 | - redirect_to app_problem_path(@app, @problem) | |
| 76 | + problem.update_attribute :issue_link, nil | |
| 77 | + redirect_to app_problem_path(app, problem) | |
| 40 | 78 | end |
| 41 | 79 | |
| 42 | 80 | def resolve |
| 43 | - @problem.resolve! | |
| 81 | + problem.resolve! | |
| 44 | 82 | flash[:success] = 'Great news everyone! The err has been resolved.' |
| 45 | 83 | redirect_to :back |
| 46 | 84 | rescue ActionController::RedirectBackError |
| 47 | - redirect_to app_path(@app) | |
| 85 | + redirect_to app_path(app) | |
| 48 | 86 | end |
| 49 | 87 | |
| 50 | 88 | def resolve_several |
| 51 | - @selected_problems.each(&:resolve!) | |
| 52 | - flash[:success] = "Great news everyone! #{I18n.t(:n_errs_have, :count => @selected_problems.count)} been resolved." | |
| 89 | + selected_problems.each(&:resolve!) | |
| 90 | + flash[:success] = "Great news everyone! #{I18n.t(:n_errs_have, :count => selected_problems.count)} been resolved." | |
| 53 | 91 | redirect_to :back |
| 54 | 92 | end |
| 55 | 93 | |
| 56 | 94 | def unresolve_several |
| 57 | - @selected_problems.each(&:unresolve!) | |
| 58 | - flash[:success] = "#{I18n.t(:n_errs_have, :count => @selected_problems.count)} been unresolved." | |
| 95 | + selected_problems.each(&:unresolve!) | |
| 96 | + flash[:success] = "#{I18n.t(:n_errs_have, :count => selected_problems.count)} been unresolved." | |
| 59 | 97 | redirect_to :back |
| 60 | 98 | end |
| 61 | 99 | |
| 100 | + ## | |
| 101 | + # Action to merge several Problem in One problem | |
| 102 | + # | |
| 103 | + # @param [ Array<String> ] :problems the list of problem ids | |
| 104 | + # | |
| 62 | 105 | def merge_several |
| 63 | - if @selected_problems.length < 2 | |
| 64 | - flash[:notice] = "You must select at least two errors to merge" | |
| 106 | + if selected_problems.length < 2 | |
| 107 | + flash[:notice] = I18n.t('controllers.problems.flash.need_two_errors_merge') | |
| 65 | 108 | else |
| 66 | - @merged_problem = Problem.merge!(@selected_problems) | |
| 67 | - flash[:notice] = "#{@selected_problems.count} errors have been merged." | |
| 109 | + ProblemMerge.new(selected_problems).merge | |
| 110 | + flash[:notice] = I18n.t('controllers.problems.flash.merge_several.success', :nb => selected_problems.count) | |
| 68 | 111 | end |
| 69 | 112 | redirect_to :back |
| 70 | 113 | end |
| 71 | 114 | |
| 72 | 115 | def unmerge_several |
| 73 | - all = @selected_problems.map(&:unmerge!).flatten | |
| 116 | + all = selected_problems.map(&:unmerge!).flatten | |
| 74 | 117 | flash[:success] = "#{I18n.t(:n_errs_have, :count => all.length)} been unmerged." |
| 75 | 118 | redirect_to :back |
| 76 | 119 | end |
| 77 | 120 | |
| 78 | 121 | def destroy_several |
| 79 | - nb_problem_destroy = ProblemDestroy.execute(@selected_problems) | |
| 122 | + nb_problem_destroy = ProblemDestroy.execute(selected_problems) | |
| 80 | 123 | flash[:notice] = "#{I18n.t(:n_errs_have, :count => nb_problem_destroy)} been deleted." |
| 81 | 124 | redirect_to :back |
| 82 | 125 | end |
| 83 | 126 | |
| 84 | 127 | def search |
| 85 | - if params[:app_id] | |
| 86 | - app_scope = App.where(:_id => params[:app_id]) | |
| 87 | - else | |
| 88 | - app_scope = current_user.admin? ? App.all : current_user.apps | |
| 89 | - end | |
| 90 | - @problems = Problem.search(params[:search]).for_apps(app_scope).in_env(params[:environment]).all_else_unresolved(params[:all_errs]).ordered_by(@sort, @order) | |
| 128 | + @problems = Problem.search(params[:search]).for_apps(app_scope).in_env(params[:environment]).all_else_unresolved(params[:all_errs]).ordered_by(params_sort, params_order) | |
| 91 | 129 | @selected_problems = params[:problems] || [] |
| 92 | 130 | @problems = @problems.page(params[:page]).per(current_user.per_page) |
| 93 | 131 | render :content_type => 'text/javascript' |
| 94 | 132 | end |
| 95 | 133 | |
| 96 | 134 | protected |
| 97 | - def find_app | |
| 98 | - @app = App.find(params[:app_id]) | |
| 99 | - | |
| 100 | - # Mongoid Bug: could not chain: current_user.apps.find_by_id! | |
| 101 | - # apparently finding by 'watchers.email' and 'id' is broken | |
| 102 | - raise(Mongoid::Errors::DocumentNotFound.new(App,@app.id)) unless current_user.admin? || current_user.watching?(@app) | |
| 103 | - end | |
| 104 | 135 | |
| 105 | - def find_problem | |
| 106 | - @problem = @app.problems.find(params[:id]) | |
| 107 | - end | |
| 108 | - | |
| 109 | - def set_tracker_params | |
| 110 | - IssueTracker.default_url_options[:host] = request.host | |
| 111 | - IssueTracker.default_url_options[:port] = request.port | |
| 112 | - IssueTracker.default_url_options[:protocol] = request.scheme | |
| 113 | - end | |
| 114 | - | |
| 115 | - def find_selected_problems | |
| 116 | - err_ids = (params[:problems] || []).compact | |
| 117 | - if err_ids.empty? | |
| 118 | - flash[:notice] = "You have not selected any errors" | |
| 119 | - redirect_to :back | |
| 120 | - else | |
| 121 | - @selected_problems = Array(Problem.find(err_ids)) | |
| 122 | - end | |
| 123 | - end | |
| 124 | - | |
| 125 | - def set_sorting_params | |
| 126 | - @sort = params[:sort] | |
| 127 | - @sort = "last_notice_at" unless %w{app message last_notice_at last_deploy_at count}.member?(@sort) | |
| 128 | - @order = params[:order] || "desc" | |
| 136 | + ## | |
| 137 | + # Redirect :back if no errors selected | |
| 138 | + # | |
| 139 | + def need_selected_problem | |
| 140 | + if err_ids.empty? | |
| 141 | + flash[:notice] = I18n.t('controllers.problems.flash.no_select_problem') | |
| 142 | + redirect_to :back | |
| 129 | 143 | end |
| 144 | + end | |
| 130 | 145 | end |
| 131 | 146 | ... | ... |
| ... | ... | @@ -0,0 +1,34 @@ |
| 1 | +# Include to do a Search | |
| 2 | +# TODO: Need to be in a Dedicated Object ProblemsSearch with params like input | |
| 3 | +# | |
| 4 | +module ProblemsSearcher | |
| 5 | + extend ActiveSupport::Concern | |
| 6 | + | |
| 7 | + included do | |
| 8 | + | |
| 9 | + expose(:params_sort) { | |
| 10 | + unless %w{app message last_notice_at last_deploy_at count}.member?(params[:sort]) | |
| 11 | + "last_notice_at" | |
| 12 | + else | |
| 13 | + params[:sort] | |
| 14 | + end | |
| 15 | + } | |
| 16 | + | |
| 17 | + expose(:params_order){ | |
| 18 | + unless %w{asc desc}.member?(params[:order]) | |
| 19 | + 'desc' | |
| 20 | + else | |
| 21 | + params[:order] | |
| 22 | + end | |
| 23 | + } | |
| 24 | + | |
| 25 | + expose(:selected_problems) { | |
| 26 | + Array(Problem.find(err_ids)) | |
| 27 | + } | |
| 28 | + | |
| 29 | + expose(:err_ids) { | |
| 30 | + (params[:problems] || []).compact | |
| 31 | + } | |
| 32 | + | |
| 33 | + end | |
| 34 | +end | ... | ... |
app/helpers/apps_helper.rb
| ... | ... | @@ -41,7 +41,7 @@ module AppsHelper |
| 41 | 41 | def detect_any_apps_with_attributes |
| 42 | 42 | @any_github_repos = @any_issue_trackers = @any_deploys = @any_bitbucket_repos = @any_notification_services = false |
| 43 | 43 | |
| 44 | - @apps.each do |app| | |
| 44 | + apps.each do |app| | |
| 45 | 45 | @any_github_repos ||= app.github_repo? |
| 46 | 46 | @any_bitbucket_repos ||= app.bitbucket_repo? |
| 47 | 47 | @any_issue_trackers ||= app.issue_tracker_configured? | ... | ... |
app/helpers/sort_helper.rb
| 1 | 1 | # encoding: utf-8 |
| 2 | 2 | module SortHelper |
| 3 | - | |
| 3 | + | |
| 4 | 4 | def link_for_sort(name, field=nil) |
| 5 | 5 | field ||= name.underscore |
| 6 | - current = (@sort == field) | |
| 7 | - order = (current && (@order == "asc")) ? "desc" : "asc" | |
| 6 | + current = (params_sort == field) | |
| 7 | + order = (current && (params_order == "asc")) ? "desc" : "asc" | |
| 8 | 8 | url = request.path + "?sort=#{field}&order=#{order}" |
| 9 | 9 | options = {} |
| 10 | 10 | options.merge!(:class => "current #{order}") if current |
| 11 | 11 | link_to(name, url, options) |
| 12 | 12 | end |
| 13 | - | |
| 13 | + | |
| 14 | 14 | end | ... | ... |
app/interactors/issue_creation.rb
| ... | ... | @@ -41,15 +41,11 @@ class IssueCreation |
| 41 | 41 | end |
| 42 | 42 | |
| 43 | 43 | def execute |
| 44 | - if tracker | |
| 45 | - begin | |
| 46 | - tracker.create_issue problem, user | |
| 47 | - rescue => ex | |
| 48 | - Rails.logger.error "Error during issue creation: " << ex.message | |
| 49 | - errors.add :base, "There was an error during issue creation: #{ex.message}" | |
| 50 | - end | |
| 51 | - end | |
| 52 | - | |
| 44 | + tracker.create_issue problem, user if tracker | |
| 53 | 45 | errors.empty? |
| 46 | + rescue => ex | |
| 47 | + Rails.logger.error "Error during issue creation: " << ex.message | |
| 48 | + errors.add :base, "There was an error during issue creation: #{ex.message}" | |
| 49 | + false | |
| 54 | 50 | end |
| 55 | 51 | end | ... | ... |
app/models/issue_tracker.rb
| ... | ... | @@ -17,6 +17,14 @@ class IssueTracker |
| 17 | 17 | field :subdomain, :type => String |
| 18 | 18 | field :milestone_id, :type => String |
| 19 | 19 | |
| 20 | + # Is there any better way to enhance the props? Putting them into the subclass leads to | |
| 21 | + # an error while rendering the form fields -.- | |
| 22 | + field :base_url, :type => String | |
| 23 | + field :context_path, :type => String | |
| 24 | + field :issue_type, :type => String | |
| 25 | + field :issue_component, :type => String | |
| 26 | + field :issue_priority, :type => String | |
| 27 | + | |
| 20 | 28 | validate :check_params |
| 21 | 29 | |
| 22 | 30 | # Subclasses are responsible for overwriting this method. |
| ... | ... | @@ -40,4 +48,15 @@ class IssueTracker |
| 40 | 48 | def configured? |
| 41 | 49 | project_id.present? |
| 42 | 50 | end |
| 51 | + | |
| 52 | + ## | |
| 53 | + # Update default_url_option with valid data from the request information | |
| 54 | + # | |
| 55 | + # @param [ Request ] a request with host, port and protocol | |
| 56 | + # | |
| 57 | + def self.update_url_options(request) | |
| 58 | + IssueTracker.default_url_options[:host] = request.host | |
| 59 | + IssueTracker.default_url_options[:port] = request.port | |
| 60 | + IssueTracker.default_url_options[:protocol] = request.scheme | |
| 61 | + end | |
| 43 | 62 | end | ... | ... |
app/models/issue_trackers/bitbucket_issues_tracker.rb
| ... | ... | @@ -0,0 +1,109 @@ |
| 1 | +if defined? JIRA | |
| 2 | + class IssueTrackers::JiraTracker < IssueTracker | |
| 3 | + Label = 'jira' | |
| 4 | + | |
| 5 | + Fields = [ | |
| 6 | + [:base_url, { | |
| 7 | + :label => 'Jira URL without trailing slash', | |
| 8 | + :placeholder => 'https://jira.example.org/' | |
| 9 | + }], | |
| 10 | + [:context_path, { | |
| 11 | + :optional => true, | |
| 12 | + :label => 'Context Path (Just "/" if empty otherwise with leading slash)', | |
| 13 | + :placeholder => "/jira" | |
| 14 | + }], | |
| 15 | + [:username, { | |
| 16 | + :optional => true, | |
| 17 | + :label => 'HTTP Basic Auth User', | |
| 18 | + :placeholder => 'johndoe' | |
| 19 | + }], | |
| 20 | + [:password, { | |
| 21 | + :optional => true, | |
| 22 | + :label => 'HTTP Basic Auth Password', | |
| 23 | + :placeholder => 'p@assW0rd' | |
| 24 | + }], | |
| 25 | + [:project_id, { | |
| 26 | + :label => 'Project Key', | |
| 27 | + :placeholder => 'The project Key where the issue will be created' | |
| 28 | + }], | |
| 29 | + [:account, { | |
| 30 | + :optional => true, | |
| 31 | + :label => 'Assign to this user. If empty, Jira takes the project default.', | |
| 32 | + :placeholder => "username" | |
| 33 | + }], | |
| 34 | + [:issue_component, { | |
| 35 | + :label => 'Issue category', | |
| 36 | + :placeholder => 'Website - Other' | |
| 37 | + }], | |
| 38 | + [:issue_type, { | |
| 39 | + :label => 'Issue type', | |
| 40 | + :placeholder => 'Bug' | |
| 41 | + }], | |
| 42 | + [:issue_priority, { | |
| 43 | + :label => 'Priority', | |
| 44 | + :placeholder => 'Normal' | |
| 45 | + }] | |
| 46 | + ] | |
| 47 | + | |
| 48 | + def check_params | |
| 49 | + if Fields.detect { |f| self[f[0]].blank? && !f[1][:optional] } | |
| 50 | + errors.add :base, 'You must specify all non optional values!' | |
| 51 | + end | |
| 52 | + end | |
| 53 | + | |
| 54 | + | |
| 55 | + # | |
| 56 | + # @param problem Problem | |
| 57 | + def create_issue(problem, reported_by = nil) | |
| 58 | + options = { | |
| 59 | + :username => username, | |
| 60 | + :password => password, | |
| 61 | + :site => base_url, | |
| 62 | + :context_path => context_path, | |
| 63 | + :auth_type => :basic | |
| 64 | + } | |
| 65 | + client = JIRA::Client.new(options) | |
| 66 | + | |
| 67 | + issue = { | |
| 68 | + :fields => { | |
| 69 | + :project => { | |
| 70 | + :key => project_id | |
| 71 | + }, | |
| 72 | + :summary => issue_title(problem), | |
| 73 | + :description => body_template.result(binding), | |
| 74 | + :issuetype => { | |
| 75 | + :name => issue_type | |
| 76 | + }, | |
| 77 | + :priority => { | |
| 78 | + :name => issue_priority, | |
| 79 | + }, | |
| 80 | + | |
| 81 | + :components => [{:name => issue_component}] | |
| 82 | + } | |
| 83 | + } | |
| 84 | + | |
| 85 | + issue[:fields][:assignee] = {:name => account} if account | |
| 86 | + | |
| 87 | + issue_build = client.Issue.build | |
| 88 | + issue_build.save(issue) | |
| 89 | + issue_build.fetch | |
| 90 | + | |
| 91 | + problem.update_attributes( | |
| 92 | + :issue_link => "#{base_url}#{context_path}browse/#{issue_build.key}", | |
| 93 | + :issue_type => Label | |
| 94 | + ) | |
| 95 | + | |
| 96 | + # Maybe in a later version? | |
| 97 | + #remote_link = { | |
| 98 | + # :url => app_problem_url(problem.app, problem), | |
| 99 | + # :name => "Link to Errbit Issue" | |
| 100 | + #} | |
| 101 | + #remote_link_build = issue_build.remotelink.build | |
| 102 | + #remote_link_build.save(remote_link) | |
| 103 | + end | |
| 104 | + | |
| 105 | + def body_template | |
| 106 | + @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/jira_body.txt.erb")) | |
| 107 | + end | |
| 108 | + end | |
| 109 | +end | |
| 0 | 110 | \ No newline at end of file | ... | ... |
app/views/apps/_fields.html.haml
app/views/apps/edit.html.haml
| 1 | 1 | - content_for :title, 'Edit App' |
| 2 | 2 | - content_for :action_bar do |
| 3 | 3 | = link_to_copy_attributes_from_other_app |
| 4 | - = link_to 'destroy application', app_path(@app), :method => :delete, :data => { :confirm => 'Seriously?' }, :class => 'button' | |
| 5 | - = link_to('cancel', app_path(@app), :class => 'button') | |
| 4 | + = link_to 'destroy application', app_path(app), :method => :delete, :data => { :confirm => 'Seriously?' }, :class => 'button' | |
| 5 | + = link_to('cancel', app_path(app), :class => 'button') | |
| 6 | 6 | |
| 7 | -= form_for @app do |f| | |
| 7 | += form_for app do |f| | |
| 8 | 8 | |
| 9 | 9 | = render 'fields', :f => f |
| 10 | 10 | ... | ... |
app/views/apps/index.html.haml
| ... | ... | @@ -16,7 +16,7 @@ |
| 16 | 16 | %th Last Deploy |
| 17 | 17 | %th Errors |
| 18 | 18 | %tbody |
| 19 | - - @apps.each do |app| | |
| 19 | + - apps.each do |app| | |
| 20 | 20 | %tr |
| 21 | 21 | %td.name= link_to app.name, app_path(app) |
| 22 | 22 | - if any_github_repos? or any_bitbucket_repos? |
| ... | ... | @@ -50,7 +50,7 @@ |
| 50 | 50 | - if app.problem_count > 0 |
| 51 | 51 | - unresolved = app.unresolved_count |
| 52 | 52 | = link_to unresolved, app_path(app), :class => (unresolved == 0 ? "resolved" : nil) |
| 53 | - - if @apps.none? | |
| 53 | + - if apps.none? | |
| 54 | 54 | %tr |
| 55 | 55 | %td{:colspan => 3} |
| 56 | 56 | %em | ... | ... |
app/views/apps/new.html.haml
app/views/apps/show.atom.builder
app/views/apps/show.html.haml
| 1 | -- content_for :title, @app.name | |
| 1 | +- content_for :title, app.name | |
| 2 | 2 | - content_for :head do |
| 3 | - = auto_discovery_link_tag :atom, app_path(@app, User.token_authentication_key => current_user.authentication_token, :format => "atom"), :title => "Errbit notices for #{@app.name} at #{request.host}" | |
| 3 | + = auto_discovery_link_tag :atom, app_path(app, User.token_authentication_key => current_user.authentication_token, :format => "atom"), :title => "Errbit notices for #{app.name} at #{request.host}" | |
| 4 | 4 | - content_for :meta do |
| 5 | 5 | %strong Errors Caught: |
| 6 | - = @app.problems.count | |
| 6 | + = app.problems.count | |
| 7 | 7 | %strong Deploy Count: |
| 8 | - = @app.deploys.count | |
| 8 | + = app.deploys.count | |
| 9 | 9 | %strong API Key: |
| 10 | - = @app.api_key | |
| 10 | + = app.api_key | |
| 11 | 11 | - content_for :action_bar do |
| 12 | 12 | - if current_user.admin? |
| 13 | - = link_to 'edit', edit_app_path(@app), :class => 'button' | |
| 14 | - - if @all_errs | |
| 15 | - = link_to 'unresolved errs', app_path(@app), :class => 'button' | |
| 13 | + = link_to 'edit', edit_app_path(app), :class => 'button' | |
| 14 | + - if all_errs | |
| 15 | + = link_to 'unresolved errs', app_path(app), :class => 'button' | |
| 16 | 16 | - else |
| 17 | - = link_to 'all errs', app_path(@app, :all_errs => true), :class => 'button' | |
| 17 | + = link_to 'all errs', app_path(app, :all_errs => true), :class => 'button' | |
| 18 | 18 | |
| 19 | 19 | %h3#watchers_toggle |
| 20 | 20 | Watchers |
| 21 | 21 | %span.click_span (show/hide) |
| 22 | 22 | #watchers_div |
| 23 | - - if @app.notify_all_users | |
| 23 | + - if app.notify_all_users | |
| 24 | 24 | %table.watchers |
| 25 | 25 | %thead |
| 26 | 26 | %tr |
| ... | ... | @@ -31,15 +31,15 @@ |
| 31 | 31 | %tr |
| 32 | 32 | %th User or Email |
| 33 | 33 | %tbody |
| 34 | - - @app.watchers.each do |watcher| | |
| 34 | + - app.watchers.each do |watcher| | |
| 35 | 35 | %tr |
| 36 | 36 | %td= watcher.label |
| 37 | - - if @app.watchers.none? | |
| 37 | + - if app.watchers.none? | |
| 38 | 38 | %tr |
| 39 | 39 | %td |
| 40 | 40 | %em Sadly, no one is watching this app |
| 41 | 41 | |
| 42 | -- if @app.github_repo? | |
| 42 | +- if app.github_repo? | |
| 43 | 43 | %h3#repository_toggle |
| 44 | 44 | Repository |
| 45 | 45 | %span.click_span (show/hide) |
| ... | ... | @@ -50,13 +50,13 @@ |
| 50 | 50 | %th GitHub Repo |
| 51 | 51 | %tbody |
| 52 | 52 | %tr |
| 53 | - %td= link_to(@app.github_repo, @app.github_url, :target => '_blank') | |
| 53 | + %td= link_to(app.github_repo, app.github_url, :target => '_blank') | |
| 54 | 54 | |
| 55 | 55 | %h3#deploys_toggle |
| 56 | 56 | Latest Deploys |
| 57 | 57 | %span.click_span (show/hide) |
| 58 | 58 | #deploys_div |
| 59 | - - if @deploys.any? | |
| 59 | + - if deploys.any? | |
| 60 | 60 | %table.deploys |
| 61 | 61 | %thead |
| 62 | 62 | %tr |
| ... | ... | @@ -68,7 +68,7 @@ |
| 68 | 68 | %th Revision |
| 69 | 69 | |
| 70 | 70 | %tbody |
| 71 | - - @deploys.each do |deploy| | |
| 71 | + - deploys.each do |deploy| | |
| 72 | 72 | %tr |
| 73 | 73 | %td.when #{deploy.created_at.to_s(:micro)} |
| 74 | 74 | %td.environment #{deploy.environment} |
| ... | ... | @@ -76,20 +76,20 @@ |
| 76 | 76 | %td.message #{deploy.message} |
| 77 | 77 | %td.repository #{deploy.repository} |
| 78 | 78 | %td.revision #{deploy.short_revision} |
| 79 | - = link_to "All Deploys (#{@app.deploys.count})", app_deploys_path(@app), :class => 'button' | |
| 79 | + = link_to "All Deploys (#{app.deploys.count})", app_deploys_path(app), :class => 'button' | |
| 80 | 80 | - else |
| 81 | 81 | %h3 No deploys |
| 82 | 82 | |
| 83 | -- if @app.problems.any? | |
| 83 | +- if app.problems.any? | |
| 84 | 84 | %h3.clear Errors |
| 85 | 85 | %section |
| 86 | - = form_tag search_problems_path(:all_errs => @all_errs, :app_id => @app.id), :method => :get, :remote => true do | |
| 86 | + = form_tag search_problems_path(:all_errs => all_errs, :app_id => app.id), :method => :get, :remote => true do | |
| 87 | 87 | = text_field_tag :search, params[:search], :placeholder => 'Search for issues' |
| 88 | 88 | %br |
| 89 | 89 | %section |
| 90 | 90 | .problem_table{:id => 'problem_table'} |
| 91 | - = render 'problems/table', :problems => @problems | |
| 91 | + = render 'problems/table', :problems => problems | |
| 92 | 92 | - else |
| 93 | 93 | %h3.clear No errs have been caught yet, make sure you setup your app |
| 94 | - = render 'configuration_instructions', :app => @app | |
| 94 | + = render 'configuration_instructions', :app => app | |
| 95 | 95 | ... | ... |
| ... | ... | @@ -0,0 +1,17 @@ |
| 1 | +<% if notice = problem.notices.first %> | |
| 2 | +h2. Summary | |
| 3 | +<% if notice.request['url'].present? %> | |
| 4 | +h3. URL | |
| 5 | + | |
| 6 | +"<%= notice.request['url'] %>":<%= notice.request['url'] %> | |
| 7 | +<% end %> | |
| 8 | +h3. Where | |
| 9 | + | |
| 10 | +<%= notice.where %> | |
| 11 | + | |
| 12 | +h3. When | |
| 13 | + | |
| 14 | +<%= notice.created_at.to_s(:micro) %> | |
| 15 | + | |
| 16 | +"More Details on Errbit":<%= app_problem_url problem.app, problem %> | |
| 17 | +<% end %> | |
| 0 | 18 | \ No newline at end of file | ... | ... |
app/views/problems/_issue_tracker_links.html.haml
| 1 | -- if @app.issue_tracker_configured? || current_user.github_account? | |
| 2 | - - if @problem.issue_link.present? | |
| 3 | - %span= link_to 'go to issue', @problem.issue_link, :class => "#{@problem.issue_type}_goto goto-issue" | |
| 4 | - = link_to 'unlink issue', unlink_issue_app_problem_path(@app, @problem), :method => :delete, :data => { :confirm => "Unlink err issues?" }, :class => "unlink-issue" | |
| 5 | - - elsif @problem.issue_link == "pending" | |
| 6 | - %span.disabled= link_to 'creating...', '#', :class => "#{@problem.issue_type}_inactive create-issue" | |
| 7 | - = link_to 'retry', create_issue_app_problem_path(@app, @problem), :method => :post | |
| 1 | +- if app.issue_tracker_configured? || current_user.github_account? | |
| 2 | + - if problem.issue_link.present? | |
| 3 | + %span= link_to 'go to issue', problem.issue_link, :class => "#{problem.issue_type}_goto goto-issue" | |
| 4 | + = link_to 'unlink issue', unlink_issue_app_problem_path(app, problem), :method => :delete, :data => { :confirm => "Unlink err issues?" }, :class => "unlink-issue" | |
| 5 | + - elsif problem.issue_link == "pending" | |
| 6 | + %span.disabled= link_to 'creating...', '#', :class => "#{problem.issue_type}_inactive create-issue" | |
| 7 | + = link_to 'retry', create_issue_app_problem_path(app, problem), :method => :post | |
| 8 | 8 | - else |
| 9 | - - if @app.github_repo? | |
| 9 | + - if app.github_repo? | |
| 10 | 10 | - if current_user.can_create_github_issues? |
| 11 | - %span= link_to 'create issue', create_issue_app_problem_path(@app, @problem, :tracker => 'user_github'), :method => :post, :class => "github_create create-issue" | |
| 12 | - - elsif @app.issue_tracker_configured? && @app.issue_tracker.label.eql?('github') | |
| 13 | - %span= link_to 'create issue', create_issue_app_problem_path(@app, @problem), :method => :post, :class => "github_create create-issue" | |
| 14 | - - if @app.issue_tracker_configured? && !@app.issue_tracker.label.eql?('github') | |
| 15 | - %span= link_to 'create issue', create_issue_app_problem_path(@app, @problem), :method => :post, :class => "#{@app.issue_tracker.label}_create create-issue" | |
| 11 | + %span= link_to 'create issue', create_issue_app_problem_path(app, problem, :tracker => 'user_github'), :method => :post, :class => "github_create create-issue" | |
| 12 | + - elsif app.issue_tracker_configured? && app.issue_tracker.label.eql?('github') | |
| 13 | + %span= link_to 'create issue', create_issue_app_problem_path(app, problem), :method => :post, :class => "github_create create-issue" | |
| 14 | + - if app.issue_tracker_configured? && !app.issue_tracker.label.eql?('github') | |
| 15 | + %span= link_to 'create issue', create_issue_app_problem_path(app, problem), :method => :post, :class => "#{app.issue_tracker.label}_create create-issue" | ... | ... |
app/views/problems/_list.atom.builder
| 1 | -feed.updated(@problems.first.try(:created_at) || Time.now) | |
| 1 | +feed.updated(problems.first.try(:created_at) || Time.now) | |
| 2 | 2 | |
| 3 | -for problem in @problems | |
| 3 | +for problem in problems | |
| 4 | 4 | notice = problem.notices.first |
| 5 | 5 | |
| 6 | - feed.entry(problem, :url => app_problem_url(problem.app, problem)) do |entry| | |
| 6 | + feed.entry(problem, :url => app_problem_url(problem.app.to_param, problem.to_param)) do |entry| | |
| 7 | 7 | entry.title "[#{ problem.where }] #{problem.message.to_s.truncate(27)}" |
| 8 | 8 | entry.author do |author| |
| 9 | 9 | author.name "#{ problem.app.name } [#{ problem.environment }]" | ... | ... |
app/views/problems/_table.html.haml
| ... | ... | @@ -16,7 +16,7 @@ |
| 16 | 16 | - problems.each do |problem| |
| 17 | 17 | %tr{:class => problem.resolved? ? 'resolved' : 'unresolved'} |
| 18 | 18 | %td.select |
| 19 | - = check_box_tag "problems[]", problem.id, @selected_problems.member?(problem.id.to_s) | |
| 19 | + = check_box_tag "problems[]", problem.id, selected_problems.member?(problem.id.to_s) | |
| 20 | 20 | %td.app |
| 21 | 21 | = link_to problem.app.name, app_path(problem.app) |
| 22 | 22 | - if current_page?(:controller => 'problems') | ... | ... |
app/views/problems/index.html.haml
| 1 | 1 | - content_for :title, @all_errs ? 'All Errors' : 'Unresolved Errors' |
| 2 | 2 | - content_for :head do |
| 3 | 3 | = auto_discovery_link_tag :atom, problems_path(User.token_authentication_key => current_user.authentication_token, :format => "atom"), :title => "Errbit notices at #{request.host}" |
| 4 | + | |
| 4 | 5 | - content_for :action_bar do |
| 5 | 6 | - if @all_errs |
| 6 | 7 | = link_to 'hide resolved', problems_path, :class => 'button' |
| 7 | 8 | - else |
| 8 | 9 | = link_to 'show resolved', problems_path(:all_errs => true), :class => 'button' |
| 10 | + | |
| 9 | 11 | %section |
| 10 | 12 | = form_tag search_problems_path(:all_errs => @all_errs), :method => :get, :remote => true do |
| 11 | 13 | = text_field_tag :search, params[:search], :placeholder => 'Search for issues' |
| 12 | 14 | %br |
| 13 | 15 | %section |
| 14 | - .problem_table{:id => 'problem_table'} | |
| 15 | - = render 'problems/table', :problems => @problems | |
| 16 | + #problem_table.problem_table | |
| 17 | + = render 'problems/table' | ... | ... |
app/views/problems/show.html.haml
| 1 | -- content_for :page_title, @problem.message | |
| 1 | +- content_for :page_title, problem.message | |
| 2 | 2 | - content_for :title_css_class, 'err_show' |
| 3 | -- content_for :title, @problem.error_class || truncate(@problem.message, :length => 32) | |
| 3 | +- content_for :title, problem.error_class || truncate(problem.message, :length => 32) | |
| 4 | 4 | - content_for :meta do |
| 5 | 5 | %strong App: |
| 6 | - = link_to @app.name, @app | |
| 6 | + = link_to app.name, app | |
| 7 | 7 | %strong Where: |
| 8 | - = @problem.where | |
| 8 | + = problem.where | |
| 9 | 9 | %br |
| 10 | 10 | %strong Environment: |
| 11 | - = @problem.environment | |
| 11 | + = problem.environment | |
| 12 | 12 | %strong Last Notice: |
| 13 | - = @problem.last_notice_at.to_s(:precise) | |
| 13 | + = problem.last_notice_at.to_s(:precise) | |
| 14 | 14 | - content_for :action_bar do |
| 15 | - - if @problem.unresolved? | |
| 16 | - %span= link_to 'resolve', [:resolve, @app, @problem], :method => :put, :data => { :confirm => problem_confirm }, :class => 'resolve' | |
| 15 | + - if problem.unresolved? | |
| 16 | + %span= link_to 'resolve', [:resolve, app, problem], :method => :put, :data => { :confirm => problem_confirm }, :class => 'resolve' | |
| 17 | 17 | - if current_user.authentication_token |
| 18 | - %span= link_to 'iCal', polymorphic_path([@app, @problem], :format => "ics", :auth_token => current_user.authentication_token), :class => "calendar_link" | |
| 19 | - %span>= link_to 'up', (request.env['HTTP_REFERER'] ? :back : app_problems_path(@app)), :class => 'up' | |
| 18 | + %span= link_to 'iCal', polymorphic_path([app, problem], :format => "ics", :auth_token => current_user.authentication_token), :class => "calendar_link" | |
| 19 | + %span>= link_to 'up', (request.env['HTTP_REFERER'] ? :back : app_problems_path(app)), :class => 'up' | |
| 20 | 20 | %br |
| 21 | 21 | = render "issue_tracker_links" |
| 22 | 22 | |
| 23 | -- if @problem.comments_allowed? || @problem.comments.any? | |
| 23 | +- if problem.comments_allowed? || problem.comments.any? | |
| 24 | 24 | - content_for :comments do |
| 25 | 25 | %h3 Comments |
| 26 | - - @problem.comments.each do |comment| | |
| 26 | + - problem.comments.each do |comment| | |
| 27 | 27 | .window |
| 28 | 28 | %table.comment |
| 29 | 29 | %tr |
| ... | ... | @@ -37,11 +37,11 @@ |
| 37 | 37 | - else |
| 38 | 38 | %span.comment-info |
| 39 | 39 | = time_ago_in_words(comment.created_at, true) << " ago by [Unknown User]" |
| 40 | - %span.delete= link_to '✘'.html_safe, [@app, @problem, comment], :method => :delete, :data => { :confirm => "Are you sure you don't need this comment?" }, :class => "destroy-comment" | |
| 40 | + %span.delete= link_to '✘'.html_safe, [app, problem, comment], :method => :delete, :data => { :confirm => "Are you sure you don't need this comment?" }, :class => "destroy-comment" | |
| 41 | 41 | %tr |
| 42 | 42 | %td= simple_format comment.body |
| 43 | - - if @problem.comments_allowed? | |
| 44 | - = form_for [@app, @problem, @comment] do |comment_form| | |
| 43 | + - if problem.comments_allowed? | |
| 44 | + = form_for [app, problem, @comment] do |comment_form| | |
| 45 | 45 | %p Add a comment |
| 46 | 46 | = comment_form.text_area :body |
| 47 | 47 | = comment_form.submit "Save Comment" |
| ... | ... | @@ -63,7 +63,7 @@ |
| 63 | 63 | - if @notice |
| 64 | 64 | #summary |
| 65 | 65 | %h3 Summary |
| 66 | - = render 'notices/summary', :notice => @notice, :problem => @problem | |
| 66 | + = render 'notices/summary', :notice => @notice | |
| 67 | 67 | |
| 68 | 68 | #backtrace |
| 69 | 69 | %h3 Backtrace | ... | ... |
app/views/problems/show.ics.haml
config/config.example.yml
config/initializers/_load_config.rb
| ... | ... | @@ -71,6 +71,11 @@ if smtp = Errbit::Config.smtp_settings |
| 71 | 71 | ActionMailer::Base.smtp_settings = smtp |
| 72 | 72 | end |
| 73 | 73 | |
| 74 | +if sendmail = Errbit::Config.sendmail_settings | |
| 75 | + ActionMailer::Base.delivery_method = :sendmail | |
| 76 | + ActionMailer::Base.sendmail_settings = sendmail | |
| 77 | +end | |
| 78 | + | |
| 74 | 79 | # Set config specific values |
| 75 | 80 | (ActionMailer::Base.default_url_options ||= {}).tap do |default| |
| 76 | 81 | default.merge! :host => Errbit::Config.host if default[:host].blank? | ... | ... |
config/initializers/inherited_resources.rb
config/locales/en.yml
| ... | ... | @@ -2,7 +2,6 @@ |
| 2 | 2 | # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. |
| 3 | 3 | |
| 4 | 4 | en: |
| 5 | - hello: "Hello world" | |
| 6 | 5 | flash: |
| 7 | 6 | apps: |
| 8 | 7 | create: |
| ... | ... | @@ -11,6 +10,7 @@ en: |
| 11 | 10 | success: "Good news everyone! '%{app_name}' was successfully updated." |
| 12 | 11 | destroy: |
| 13 | 12 | success: "'%{app_name}' was successfully destroyed." |
| 13 | + | |
| 14 | 14 | n_errs_have: |
| 15 | 15 | one: "%{count} err has" |
| 16 | 16 | other: "%{count} errs have" |
| ... | ... | @@ -31,6 +31,18 @@ en: |
| 31 | 31 | edit_profile: 'Edit profile' |
| 32 | 32 | |
| 33 | 33 | controllers: |
| 34 | + apps: | |
| 35 | + flash: | |
| 36 | + create: | |
| 37 | + success: "Your app was successfully created." | |
| 38 | + error: "You app was successfully destroyed." | |
| 39 | + update: | |
| 40 | + success: "You app was successfully updated." | |
| 41 | + error: "You app was not updated" | |
| 42 | + destroy: | |
| 43 | + success: "You app was successfully destroyed." | |
| 44 | + error: "You app could not be destroyed." | |
| 45 | + | |
| 34 | 46 | users: |
| 35 | 47 | flash: |
| 36 | 48 | destroy: |
| ... | ... | @@ -38,6 +50,13 @@ en: |
| 38 | 50 | error: "You can't delete yourself" |
| 39 | 51 | update: |
| 40 | 52 | success: "%{name}'s information was successfully updated." |
| 53 | + problems: | |
| 54 | + flash: | |
| 55 | + no_select_problem: "You have not selected any errors" | |
| 56 | + need_two_errors_merge: "You must select at least two errors to merge" | |
| 57 | + merge_several: | |
| 58 | + success: "%{nb} errors have been merged." | |
| 59 | + | |
| 41 | 60 | |
| 42 | 61 | devise: |
| 43 | 62 | registrations: | ... | ... |
public/javascripts/notifier.js
| ... | ... | @@ -47,76 +47,112 @@ |
| 47 | 47 | // OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 48 | 48 | |
| 49 | 49 | /** |
| 50 | - * Main function giving a function stack trace with a forced or passed in Error | |
| 50 | + * Main function giving a function stack trace with a forced or passed in Error | |
| 51 | 51 | * |
| 52 | 52 | * @cfg {Error} e The error to create a stacktrace from (optional) |
| 53 | 53 | * @cfg {Boolean} guess If we should try to resolve the names of anonymous functions |
| 54 | - * @return {Array} of Strings with functions, lines, files, and arguments where possible | |
| 54 | + * @return {Array} of Strings with functions, lines, files, and arguments where possible | |
| 55 | 55 | */ |
| 56 | 56 | function printStackTrace(options) { |
| 57 | - var ex = (options && options.e) ? options.e : null; | |
| 58 | - var guess = options ? !!options.guess : true; | |
| 59 | - | |
| 60 | - var p = new printStackTrace.implementation(); | |
| 61 | - var result = p.run(ex); | |
| 62 | - return (guess) ? p.guessFunctions(result) : result; | |
| 57 | + options = options || {guess: true}; | |
| 58 | + var ex = options.e || null, guess = !!options.guess; | |
| 59 | + var p = new printStackTrace.implementation(), result = p.run(ex); | |
| 60 | + return (guess) ? p.guessAnonymousFunctions(result) : result; | |
| 61 | +} | |
| 62 | + | |
| 63 | +if (typeof module !== "undefined" && module.exports) { | |
| 64 | + module.exports = printStackTrace; | |
| 63 | 65 | } |
| 64 | 66 | |
| 65 | -printStackTrace.implementation = function() {}; | |
| 67 | +printStackTrace.implementation = function() { | |
| 68 | +}; | |
| 66 | 69 | |
| 67 | 70 | printStackTrace.implementation.prototype = { |
| 68 | - run: function(ex) { | |
| 69 | - ex = ex || | |
| 70 | - (function() { | |
| 71 | - try { | |
| 72 | - var _err = __undef__ << 1; | |
| 73 | - } catch (e) { | |
| 74 | - return e; | |
| 75 | - } | |
| 76 | - })(); | |
| 77 | - // Use either the stored mode, or resolve it | |
| 78 | - var mode = this._mode || this.mode(ex); | |
| 71 | + /** | |
| 72 | + * @param {Error} ex The error to create a stacktrace from (optional) | |
| 73 | + * @param {String} mode Forced mode (optional, mostly for unit tests) | |
| 74 | + */ | |
| 75 | + run: function(ex, mode) { | |
| 76 | + ex = ex || this.createException(); | |
| 77 | + // examine exception properties w/o debugger | |
| 78 | + //for (var prop in ex) {alert("Ex['" + prop + "']=" + ex[prop]);} | |
| 79 | + mode = mode || this.mode(ex); | |
| 79 | 80 | if (mode === 'other') { |
| 80 | 81 | return this.other(arguments.callee); |
| 81 | 82 | } else { |
| 82 | 83 | return this[mode](ex); |
| 83 | 84 | } |
| 84 | 85 | }, |
| 85 | - | |
| 86 | + | |
| 87 | + createException: function() { | |
| 88 | + try { | |
| 89 | + this.undef(); | |
| 90 | + } catch (e) { | |
| 91 | + return e; | |
| 92 | + } | |
| 93 | + }, | |
| 94 | + | |
| 86 | 95 | /** |
| 87 | - * @return {String} mode of operation for the environment in question. | |
| 96 | + * Mode could differ for different exception, e.g. | |
| 97 | + * exceptions in Chrome may or may not have arguments or stack. | |
| 98 | + * | |
| 99 | + * @return {String} mode of operation for the exception | |
| 88 | 100 | */ |
| 89 | 101 | mode: function(e) { |
| 90 | - if (e['arguments']) { | |
| 91 | - return (this._mode = 'chrome'); | |
| 92 | - } else if (window.opera && e.stacktrace) { | |
| 93 | - return (this._mode = 'opera10'); | |
| 102 | + if (e['arguments'] && e.stack) { | |
| 103 | + return 'chrome'; | |
| 104 | + } else if (e.stack && e.sourceURL) { | |
| 105 | + return 'safari'; | |
| 106 | + } else if (e.stack && e.number) { | |
| 107 | + return 'ie'; | |
| 108 | + } else if (typeof e.message === 'string' && typeof window !== 'undefined' && window.opera) { | |
| 109 | + // e.message.indexOf("Backtrace:") > -1 -> opera | |
| 110 | + // !e.stacktrace -> opera | |
| 111 | + if (!e.stacktrace) { | |
| 112 | + return 'opera9'; // use e.message | |
| 113 | + } | |
| 114 | + // 'opera#sourceloc' in e -> opera9, opera10a | |
| 115 | + if (e.message.indexOf('\n') > -1 && e.message.split('\n').length > e.stacktrace.split('\n').length) { | |
| 116 | + return 'opera9'; // use e.message | |
| 117 | + } | |
| 118 | + // e.stacktrace && !e.stack -> opera10a | |
| 119 | + if (!e.stack) { | |
| 120 | + return 'opera10a'; // use e.stacktrace | |
| 121 | + } | |
| 122 | + // e.stacktrace && e.stack -> opera10b | |
| 123 | + if (e.stacktrace.indexOf("called from line") < 0) { | |
| 124 | + return 'opera10b'; // use e.stacktrace, format differs from 'opera10a' | |
| 125 | + } | |
| 126 | + // e.stacktrace && e.stack -> opera11 | |
| 127 | + return 'opera11'; // use e.stacktrace, format differs from 'opera10a', 'opera10b' | |
| 128 | + } else if (e.stack && !e.fileName) { | |
| 129 | + // Chrome 27 does not have e.arguments as earlier versions, | |
| 130 | + // but still does not have e.fileName as Firefox | |
| 131 | + return 'chrome'; | |
| 94 | 132 | } else if (e.stack) { |
| 95 | - return (this._mode = 'firefox'); | |
| 96 | - } else if (window.opera && !('stacktrace' in e)) { //Opera 9- | |
| 97 | - return (this._mode = 'opera'); | |
| 133 | + return 'firefox'; | |
| 98 | 134 | } |
| 99 | - return (this._mode = 'other'); | |
| 135 | + return 'other'; | |
| 100 | 136 | }, |
| 101 | 137 | |
| 102 | 138 | /** |
| 103 | 139 | * Given a context, function name, and callback function, overwrite it so that it calls |
| 104 | 140 | * printStackTrace() first with a callback and then runs the rest of the body. |
| 105 | - * | |
| 141 | + * | |
| 106 | 142 | * @param {Object} context of execution (e.g. window) |
| 107 | 143 | * @param {String} functionName to instrument |
| 108 | - * @param {Function} function to call with a stack trace on invocation | |
| 144 | + * @param {Function} callback function to call with a stack trace on invocation | |
| 109 | 145 | */ |
| 110 | 146 | instrumentFunction: function(context, functionName, callback) { |
| 111 | 147 | context = context || window; |
| 112 | - context['_old' + functionName] = context[functionName]; | |
| 113 | - context[functionName] = function() { | |
| 114 | - callback.call(this, printStackTrace()); | |
| 115 | - return context['_old' + functionName].apply(this, arguments); | |
| 148 | + var original = context[functionName]; | |
| 149 | + context[functionName] = function instrumented() { | |
| 150 | + callback.call(this, printStackTrace().slice(4)); | |
| 151 | + return context[functionName]._instrumented.apply(this, arguments); | |
| 116 | 152 | }; |
| 117 | - context[functionName]._instrumented = true; | |
| 153 | + context[functionName]._instrumented = original; | |
| 118 | 154 | }, |
| 119 | - | |
| 155 | + | |
| 120 | 156 | /** |
| 121 | 157 | * Given a context and function name of a function that has been |
| 122 | 158 | * instrumented, revert the function to it's original (non-instrumented) |
| ... | ... | @@ -128,134 +164,207 @@ printStackTrace.implementation.prototype = { |
| 128 | 164 | deinstrumentFunction: function(context, functionName) { |
| 129 | 165 | if (context[functionName].constructor === Function && |
| 130 | 166 | context[functionName]._instrumented && |
| 131 | - context['_old' + functionName].constructor === Function) { | |
| 132 | - context[functionName] = context['_old' + functionName]; | |
| 167 | + context[functionName]._instrumented.constructor === Function) { | |
| 168 | + context[functionName] = context[functionName]._instrumented; | |
| 133 | 169 | } |
| 134 | 170 | }, |
| 135 | - | |
| 171 | + | |
| 136 | 172 | /** |
| 137 | 173 | * Given an Error object, return a formatted Array based on Chrome's stack string. |
| 138 | - * | |
| 174 | + * | |
| 139 | 175 | * @param e - Error object to inspect |
| 140 | 176 | * @return Array<String> of function calls, files and line numbers |
| 141 | 177 | */ |
| 142 | 178 | chrome: function(e) { |
| 143 | - return e.stack.replace(/^[^\(]+?[\n$]/gm, '').replace(/^\s+at\s+/gm, '').replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@').split('\n'); | |
| 179 | + var stack = (e.stack + '\n').replace(/^\S[^\(]+?[\n$]/gm, ''). | |
| 180 | + replace(/^\s+(at eval )?at\s+/gm, ''). | |
| 181 | + replace(/^([^\(]+?)([\n$])/gm, '{anonymous}()@$1$2'). | |
| 182 | + replace(/^Object.<anonymous>\s*\(([^\)]+)\)/gm, '{anonymous}()@$1').split('\n'); | |
| 183 | + stack.pop(); | |
| 184 | + return stack; | |
| 185 | + }, | |
| 186 | + | |
| 187 | + /** | |
| 188 | + * Given an Error object, return a formatted Array based on Safari's stack string. | |
| 189 | + * | |
| 190 | + * @param e - Error object to inspect | |
| 191 | + * @return Array<String> of function calls, files and line numbers | |
| 192 | + */ | |
| 193 | + safari: function(e) { | |
| 194 | + return e.stack.replace(/\[native code\]\n/m, '') | |
| 195 | + .replace(/^(?=\w+Error\:).*$\n/m, '') | |
| 196 | + .replace(/^@/gm, '{anonymous}()@') | |
| 197 | + .split('\n'); | |
| 198 | + }, | |
| 199 | + | |
| 200 | + /** | |
| 201 | + * Given an Error object, return a formatted Array based on IE's stack string. | |
| 202 | + * | |
| 203 | + * @param e - Error object to inspect | |
| 204 | + * @return Array<String> of function calls, files and line numbers | |
| 205 | + */ | |
| 206 | + ie: function(e) { | |
| 207 | + var lineRE = /^.*at (\w+) \(([^\)]+)\)$/gm; | |
| 208 | + return e.stack.replace(/at Anonymous function /gm, '{anonymous}()@') | |
| 209 | + .replace(/^(?=\w+Error\:).*$\n/m, '') | |
| 210 | + .replace(lineRE, '$1@$2') | |
| 211 | + .split('\n'); | |
| 144 | 212 | }, |
| 145 | 213 | |
| 146 | 214 | /** |
| 147 | 215 | * Given an Error object, return a formatted Array based on Firefox's stack string. |
| 148 | - * | |
| 216 | + * | |
| 149 | 217 | * @param e - Error object to inspect |
| 150 | 218 | * @return Array<String> of function calls, files and line numbers |
| 151 | 219 | */ |
| 152 | 220 | firefox: function(e) { |
| 153 | - return e.stack.replace(/(?:\n@:0)?\s+$/m, '').replace(/^\(/gm, '{anonymous}(').split('\n'); | |
| 221 | + return e.stack.replace(/(?:\n@:0)?\s+$/m, '').replace(/^[\(@]/gm, '{anonymous}()@').split('\n'); | |
| 222 | + }, | |
| 223 | + | |
| 224 | + opera11: function(e) { | |
| 225 | + var ANON = '{anonymous}', lineRE = /^.*line (\d+), column (\d+)(?: in (.+))? in (\S+):$/; | |
| 226 | + var lines = e.stacktrace.split('\n'), result = []; | |
| 227 | + | |
| 228 | + for (var i = 0, len = lines.length; i < len; i += 2) { | |
| 229 | + var match = lineRE.exec(lines[i]); | |
| 230 | + if (match) { | |
| 231 | + var location = match[4] + ':' + match[1] + ':' + match[2]; | |
| 232 | + var fnName = match[3] || "global code"; | |
| 233 | + fnName = fnName.replace(/<anonymous function: (\S+)>/, "$1").replace(/<anonymous function>/, ANON); | |
| 234 | + result.push(fnName + '@' + location + ' -- ' + lines[i + 1].replace(/^\s+/, '')); | |
| 235 | + } | |
| 236 | + } | |
| 237 | + | |
| 238 | + return result; | |
| 239 | + }, | |
| 240 | + | |
| 241 | + opera10b: function(e) { | |
| 242 | + // "<anonymous function: run>([arguments not available])@file://localhost/G:/js/stacktrace.js:27\n" + | |
| 243 | + // "printStackTrace([arguments not available])@file://localhost/G:/js/stacktrace.js:18\n" + | |
| 244 | + // "@file://localhost/G:/js/test/functional/testcase1.html:15" | |
| 245 | + var lineRE = /^(.*)@(.+):(\d+)$/; | |
| 246 | + var lines = e.stacktrace.split('\n'), result = []; | |
| 247 | + | |
| 248 | + for (var i = 0, len = lines.length; i < len; i++) { | |
| 249 | + var match = lineRE.exec(lines[i]); | |
| 250 | + if (match) { | |
| 251 | + var fnName = match[1]? (match[1] + '()') : "global code"; | |
| 252 | + result.push(fnName + '@' + match[2] + ':' + match[3]); | |
| 253 | + } | |
| 254 | + } | |
| 255 | + | |
| 256 | + return result; | |
| 154 | 257 | }, |
| 155 | 258 | |
| 156 | 259 | /** |
| 157 | 260 | * Given an Error object, return a formatted Array based on Opera 10's stacktrace string. |
| 158 | - * | |
| 261 | + * | |
| 159 | 262 | * @param e - Error object to inspect |
| 160 | 263 | * @return Array<String> of function calls, files and line numbers |
| 161 | 264 | */ |
| 162 | - opera10: function(e) { | |
| 163 | - var stack = e.stacktrace; | |
| 164 | - var lines = stack.split('\n'), ANON = '{anonymous}', | |
| 165 | - lineRE = /.*line (\d+), column (\d+) in ((<anonymous function\:?\s*(\S+))|([^\(]+)\([^\)]*\))(?: in )?(.*)\s*$/i, i, j, len; | |
| 166 | - for (i = 2, j = 0, len = lines.length; i < len - 2; i++) { | |
| 167 | - if (lineRE.test(lines[i])) { | |
| 168 | - var location = RegExp.$6 + ':' + RegExp.$1 + ':' + RegExp.$2; | |
| 169 | - var fnName = RegExp.$3; | |
| 170 | - fnName = fnName.replace(/<anonymous function\:?\s?(\S+)?>/g, ANON); | |
| 171 | - lines[j++] = fnName + '@' + location; | |
| 265 | + opera10a: function(e) { | |
| 266 | + // " Line 27 of linked script file://localhost/G:/js/stacktrace.js\n" | |
| 267 | + // " Line 11 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function foo\n" | |
| 268 | + var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i; | |
| 269 | + var lines = e.stacktrace.split('\n'), result = []; | |
| 270 | + | |
| 271 | + for (var i = 0, len = lines.length; i < len; i += 2) { | |
| 272 | + var match = lineRE.exec(lines[i]); | |
| 273 | + if (match) { | |
| 274 | + var fnName = match[3] || ANON; | |
| 275 | + result.push(fnName + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, '')); | |
| 172 | 276 | } |
| 173 | 277 | } |
| 174 | - | |
| 175 | - lines.splice(j, lines.length - j); | |
| 176 | - return lines; | |
| 278 | + | |
| 279 | + return result; | |
| 177 | 280 | }, |
| 178 | - | |
| 179 | - // Opera 7.x-9.x only! | |
| 180 | - opera: function(e) { | |
| 181 | - var lines = e.message.split('\n'), ANON = '{anonymous}', | |
| 182 | - lineRE = /Line\s+(\d+).*script\s+(http\S+)(?:.*in\s+function\s+(\S+))?/i, | |
| 183 | - i, j, len; | |
| 184 | - | |
| 185 | - for (i = 4, j = 0, len = lines.length; i < len; i += 2) { | |
| 186 | - //TODO: RegExp.exec() would probably be cleaner here | |
| 187 | - if (lineRE.test(lines[i])) { | |
| 188 | - lines[j++] = (RegExp.$3 ? RegExp.$3 + '()@' + RegExp.$2 + RegExp.$1 : ANON + '()@' + RegExp.$2 + ':' + RegExp.$1) + ' -- ' + lines[i + 1].replace(/^\s+/, ''); | |
| 281 | + | |
| 282 | + // Opera 7.x-9.2x only! | |
| 283 | + opera9: function(e) { | |
| 284 | + // " Line 43 of linked script file://localhost/G:/js/stacktrace.js\n" | |
| 285 | + // " Line 7 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n" | |
| 286 | + var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)/i; | |
| 287 | + var lines = e.message.split('\n'), result = []; | |
| 288 | + | |
| 289 | + for (var i = 2, len = lines.length; i < len; i += 2) { | |
| 290 | + var match = lineRE.exec(lines[i]); | |
| 291 | + if (match) { | |
| 292 | + result.push(ANON + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, '')); | |
| 189 | 293 | } |
| 190 | 294 | } |
| 191 | - | |
| 192 | - lines.splice(j, lines.length - j); | |
| 193 | - return lines; | |
| 295 | + | |
| 296 | + return result; | |
| 194 | 297 | }, |
| 195 | - | |
| 196 | - // Safari, IE, and others | |
| 298 | + | |
| 299 | + // Safari 5-, IE 9-, and others | |
| 197 | 300 | other: function(curr) { |
| 198 | - var ANON = '{anonymous}', fnRE = /function\s*([\w\-$]+)?\s*\(/i, | |
| 199 | - stack = [], fn, args, maxStackSize = 10; | |
| 200 | - | |
| 201 | - while (curr && stack.length < maxStackSize) { | |
| 301 | + var ANON = '{anonymous}', fnRE = /function\s*([\w\-$]+)?\s*\(/i, stack = [], fn, args, maxStackSize = 10; | |
| 302 | + while (curr && curr['arguments'] && stack.length < maxStackSize) { | |
| 202 | 303 | fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON; |
| 203 | - args = Array.prototype.slice.call(curr['arguments']); | |
| 304 | + args = Array.prototype.slice.call(curr['arguments'] || []); | |
| 204 | 305 | stack[stack.length] = fn + '(' + this.stringifyArguments(args) + ')'; |
| 205 | 306 | curr = curr.caller; |
| 206 | 307 | } |
| 207 | 308 | return stack; |
| 208 | 309 | }, |
| 209 | - | |
| 310 | + | |
| 210 | 311 | /** |
| 211 | - * Given arguments array as a String, subsituting type names for non-string types. | |
| 312 | + * Given arguments array as a String, substituting type names for non-string types. | |
| 212 | 313 | * |
| 213 | - * @param {Arguments} object | |
| 214 | - * @return {Array} of Strings with stringified arguments | |
| 314 | + * @param {Arguments,Array} args | |
| 315 | + * @return {String} stringified arguments | |
| 215 | 316 | */ |
| 216 | 317 | stringifyArguments: function(args) { |
| 318 | + var result = []; | |
| 319 | + var slice = Array.prototype.slice; | |
| 217 | 320 | for (var i = 0; i < args.length; ++i) { |
| 218 | 321 | var arg = args[i]; |
| 219 | 322 | if (arg === undefined) { |
| 220 | - args[i] = 'undefined'; | |
| 323 | + result[i] = 'undefined'; | |
| 221 | 324 | } else if (arg === null) { |
| 222 | - args[i] = 'null'; | |
| 325 | + result[i] = 'null'; | |
| 223 | 326 | } else if (arg.constructor) { |
| 224 | 327 | if (arg.constructor === Array) { |
| 225 | 328 | if (arg.length < 3) { |
| 226 | - args[i] = '[' + this.stringifyArguments(arg) + ']'; | |
| 329 | + result[i] = '[' + this.stringifyArguments(arg) + ']'; | |
| 227 | 330 | } else { |
| 228 | - args[i] = '[' + this.stringifyArguments(Array.prototype.slice.call(arg, 0, 1)) + '...' + this.stringifyArguments(Array.prototype.slice.call(arg, -1)) + ']'; | |
| 331 | + result[i] = '[' + this.stringifyArguments(slice.call(arg, 0, 1)) + '...' + this.stringifyArguments(slice.call(arg, -1)) + ']'; | |
| 229 | 332 | } |
| 230 | 333 | } else if (arg.constructor === Object) { |
| 231 | - args[i] = '#object'; | |
| 334 | + result[i] = '#object'; | |
| 232 | 335 | } else if (arg.constructor === Function) { |
| 233 | - args[i] = '#function'; | |
| 336 | + result[i] = '#function'; | |
| 234 | 337 | } else if (arg.constructor === String) { |
| 235 | - args[i] = '"' + arg + '"'; | |
| 338 | + result[i] = '"' + arg + '"'; | |
| 339 | + } else if (arg.constructor === Number) { | |
| 340 | + result[i] = arg; | |
| 236 | 341 | } |
| 237 | 342 | } |
| 238 | 343 | } |
| 239 | - return args.join(','); | |
| 344 | + return result.join(','); | |
| 240 | 345 | }, |
| 241 | - | |
| 346 | + | |
| 242 | 347 | sourceCache: {}, |
| 243 | - | |
| 348 | + | |
| 244 | 349 | /** |
| 245 | - * @return the text from a given URL. | |
| 350 | + * @return the text from a given URL | |
| 246 | 351 | */ |
| 247 | 352 | ajax: function(url) { |
| 248 | 353 | var req = this.createXMLHTTPObject(); |
| 249 | - if (!req) { | |
| 250 | - return; | |
| 354 | + if (req) { | |
| 355 | + try { | |
| 356 | + req.open('GET', url, false); | |
| 357 | + //req.overrideMimeType('text/plain'); | |
| 358 | + //req.overrideMimeType('text/javascript'); | |
| 359 | + req.send(null); | |
| 360 | + //return req.status == 200 ? req.responseText : ''; | |
| 361 | + return req.responseText; | |
| 362 | + } catch (e) { | |
| 363 | + } | |
| 251 | 364 | } |
| 252 | - req.open('GET', url, false); | |
| 253 | - // REMOVED FOR JS TEST. | |
| 254 | - //req.setRequestHeader('User-Agent', 'XMLHTTP/1.0'); | |
| 255 | - req.send(''); | |
| 256 | - return req.responseText; | |
| 365 | + return ''; | |
| 257 | 366 | }, |
| 258 | - | |
| 367 | + | |
| 259 | 368 | /** |
| 260 | 369 | * Try XHR methods in order and store XHR factory. |
| 261 | 370 | * |
| ... | ... | @@ -279,7 +388,8 @@ printStackTrace.implementation.prototype = { |
| 279 | 388 | // Use memoization to cache the factory |
| 280 | 389 | this.createXMLHTTPObject = XMLHttpFactories[i]; |
| 281 | 390 | return xmlhttp; |
| 282 | - } catch (e) {} | |
| 391 | + } catch (e) { | |
| 392 | + } | |
| 283 | 393 | } |
| 284 | 394 | }, |
| 285 | 395 | |
| ... | ... | @@ -288,12 +398,12 @@ printStackTrace.implementation.prototype = { |
| 288 | 398 | * via Ajax). |
| 289 | 399 | * |
| 290 | 400 | * @param url <String> source url |
| 291 | - * @return False if we need a cross-domain request | |
| 401 | + * @return <Boolean> False if we need a cross-domain request | |
| 292 | 402 | */ |
| 293 | 403 | isSameDomain: function(url) { |
| 294 | - return url.indexOf(location.hostname) !== -1; | |
| 404 | + return typeof location !== "undefined" && url.indexOf(location.hostname) !== -1; // location may not be defined, e.g. when running from nodejs. | |
| 295 | 405 | }, |
| 296 | - | |
| 406 | + | |
| 297 | 407 | /** |
| 298 | 408 | * Get source code from given URL if in the same domain. |
| 299 | 409 | * |
| ... | ... | @@ -301,52 +411,78 @@ printStackTrace.implementation.prototype = { |
| 301 | 411 | * @return <Array> Array of source code lines |
| 302 | 412 | */ |
| 303 | 413 | getSource: function(url) { |
| 414 | + // TODO reuse source from script tags? | |
| 304 | 415 | if (!(url in this.sourceCache)) { |
| 305 | 416 | this.sourceCache[url] = this.ajax(url).split('\n'); |
| 306 | 417 | } |
| 307 | 418 | return this.sourceCache[url]; |
| 308 | 419 | }, |
| 309 | - | |
| 310 | - guessFunctions: function(stack) { | |
| 420 | + | |
| 421 | + guessAnonymousFunctions: function(stack) { | |
| 311 | 422 | for (var i = 0; i < stack.length; ++i) { |
| 312 | - var reStack = /\{anonymous\}\(.*\)@(\w+:\/\/([\-\w\.]+)+(:\d+)?[^:]+):(\d+):?(\d+)?/; | |
| 313 | - var frame = stack[i], m = reStack.exec(frame); | |
| 314 | - if (m) { | |
| 315 | - var file = m[1], lineno = m[4]; //m[7] is character position in Chrome | |
| 316 | - if (file && this.isSameDomain(file) && lineno) { | |
| 317 | - var functionName = this.guessFunctionName(file, lineno); | |
| 318 | - stack[i] = frame.replace('{anonymous}', functionName); | |
| 423 | + var reStack = /\{anonymous\}\(.*\)@(.*)/, | |
| 424 | + reRef = /^(.*?)(?::(\d+))(?::(\d+))?(?: -- .+)?$/, | |
| 425 | + frame = stack[i], ref = reStack.exec(frame); | |
| 426 | + | |
| 427 | + if (ref) { | |
| 428 | + var m = reRef.exec(ref[1]); | |
| 429 | + if (m) { // If falsey, we did not get any file/line information | |
| 430 | + var file = m[1], lineno = m[2], charno = m[3] || 0; | |
| 431 | + if (file && this.isSameDomain(file) && lineno) { | |
| 432 | + var functionName = this.guessAnonymousFunction(file, lineno, charno); | |
| 433 | + stack[i] = frame.replace('{anonymous}', functionName); | |
| 434 | + } | |
| 319 | 435 | } |
| 320 | 436 | } |
| 321 | 437 | } |
| 322 | 438 | return stack; |
| 323 | 439 | }, |
| 324 | - | |
| 325 | - guessFunctionName: function(url, lineNo) { | |
| 440 | + | |
| 441 | + guessAnonymousFunction: function(url, lineNo, charNo) { | |
| 442 | + var ret; | |
| 326 | 443 | try { |
| 327 | - return this.guessFunctionNameFromLines(lineNo, this.getSource(url)); | |
| 444 | + ret = this.findFunctionName(this.getSource(url), lineNo); | |
| 328 | 445 | } catch (e) { |
| 329 | - return 'getSource failed with url: ' + url + ', exception: ' + e.toString(); | |
| 446 | + ret = 'getSource failed with url: ' + url + ', exception: ' + e.toString(); | |
| 330 | 447 | } |
| 448 | + return ret; | |
| 331 | 449 | }, |
| 332 | - | |
| 333 | - guessFunctionNameFromLines: function(lineNo, source) { | |
| 334 | - var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/; | |
| 335 | - var reGuessFunction = /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*(function|eval|new Function)/; | |
| 336 | - // Walk backwards from the first line in the function until we find the line which | |
| 337 | - // matches the pattern above, which is the function definition | |
| 338 | - var line = "", maxLines = 10; | |
| 450 | + | |
| 451 | + findFunctionName: function(source, lineNo) { | |
| 452 | + // FIXME findFunctionName fails for compressed source | |
| 453 | + // (more than one function on the same line) | |
| 454 | + // function {name}({args}) m[1]=name m[2]=args | |
| 455 | + var reFunctionDeclaration = /function\s+([^(]*?)\s*\(([^)]*)\)/; | |
| 456 | + // {name} = function ({args}) TODO args capture | |
| 457 | + // /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*function(?:[^(]*)/ | |
| 458 | + var reFunctionExpression = /['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*function\b/; | |
| 459 | + // {name} = eval() | |
| 460 | + var reFunctionEvaluation = /['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*(?:eval|new Function)\b/; | |
| 461 | + // Walk backwards in the source lines until we find | |
| 462 | + // the line which matches one of the patterns above | |
| 463 | + var code = "", line, maxLines = Math.min(lineNo, 20), m, commentPos; | |
| 339 | 464 | for (var i = 0; i < maxLines; ++i) { |
| 340 | - line = source[lineNo - i] + line; | |
| 341 | - if (line !== undefined) { | |
| 342 | - var m = reGuessFunction.exec(line); | |
| 465 | + // lineNo is 1-based, source[] is 0-based | |
| 466 | + line = source[lineNo - i - 1]; | |
| 467 | + commentPos = line.indexOf('//'); | |
| 468 | + if (commentPos >= 0) { | |
| 469 | + line = line.substr(0, commentPos); | |
| 470 | + } | |
| 471 | + // TODO check other types of comments? Commented code may lead to false positive | |
| 472 | + if (line) { | |
| 473 | + code = line + code; | |
| 474 | + m = reFunctionExpression.exec(code); | |
| 475 | + if (m && m[1]) { | |
| 476 | + return m[1]; | |
| 477 | + } | |
| 478 | + m = reFunctionDeclaration.exec(code); | |
| 479 | + if (m && m[1]) { | |
| 480 | + //return m[1] + "(" + (m[2] || "") + ")"; | |
| 481 | + return m[1]; | |
| 482 | + } | |
| 483 | + m = reFunctionEvaluation.exec(code); | |
| 343 | 484 | if (m && m[1]) { |
| 344 | 485 | return m[1]; |
| 345 | - } else { | |
| 346 | - m = reFunctionArgNames.exec(line); | |
| 347 | - if (m && m[1]) { | |
| 348 | - return m[1]; | |
| 349 | - } | |
| 350 | 486 | } |
| 351 | 487 | } |
| 352 | 488 | } |
| ... | ... | @@ -379,6 +515,11 @@ printStackTrace.implementation.prototype = { |
| 379 | 515 | '<project-root>{project_root}</project-root>' + |
| 380 | 516 | '<environment-name>{environment}</environment-name>' + |
| 381 | 517 | '</server-environment>' + |
| 518 | + '<current-user>' + | |
| 519 | + '<id>{user_id}</id>' + | |
| 520 | + '<name>{user_name}</name>' + | |
| 521 | + '<email>{user_email}</email>' + | |
| 522 | + '</current-user>' + | |
| 382 | 523 | '</notice>', |
| 383 | 524 | REQUEST_VARIABLE_GROUP_XML = '<{group_name}>{inner_content}</{group_name}>', |
| 384 | 525 | REQUEST_VARIABLE_XML = '<var key="{key}">{value}</var>', |
| ... | ... | @@ -411,9 +552,9 @@ printStackTrace.implementation.prototype = { |
| 411 | 552 | "rootDirectory": "{project_root}", |
| 412 | 553 | "action": "{request_action}", |
| 413 | 554 | |
| 414 | - "userId": "{}", | |
| 415 | - "userName": "{}", | |
| 416 | - "userEmail": "{}", | |
| 555 | + "userId": "{user_id}", | |
| 556 | + "userName": "{user_name}", | |
| 557 | + "userEmail": "{user_email}", | |
| 417 | 558 | }, |
| 418 | 559 | "environment": {}, |
| 419 | 560 | //"session": "", |
| ... | ... | @@ -683,6 +824,15 @@ printStackTrace.implementation.prototype = { |
| 683 | 824 | variable: 'outputFormat', |
| 684 | 825 | namespace: 'options' |
| 685 | 826 | }, { |
| 827 | + methodName: 'setCurrentUser', | |
| 828 | + method: (function (value) { | |
| 829 | + for (var key in value) { | |
| 830 | + if (value.hasOwnProperty(key)) { | |
| 831 | + Config.xmlData['user_' + key] = value[key]; | |
| 832 | + } | |
| 833 | + } | |
| 834 | + }) | |
| 835 | + }, { | |
| 686 | 836 | methodName: 'setTrackJQ', |
| 687 | 837 | variable: 'trackJQ', |
| 688 | 838 | namespace: 'options', | ... | ... |
spec/controllers/api/v1/notices_controller_spec.rb
| ... | ... | @@ -17,7 +17,7 @@ describe Api::V1::NoticesController do |
| 17 | 17 | |
| 18 | 18 | it "should return JSON if JSON is requested" do |
| 19 | 19 | get :index, :auth_token => @user.authentication_token, :format => "json" |
| 20 | - lambda { JSON.load(response.body) }.should_not raise_error(JSON::ParserError) | |
| 20 | + expect { JSON.load(response.body) }.not_to raise_error() #JSON::ParserError) | |
| 21 | 21 | end |
| 22 | 22 | |
| 23 | 23 | it "should return XML if XML is requested" do |
| ... | ... | @@ -27,7 +27,7 @@ describe Api::V1::NoticesController do |
| 27 | 27 | |
| 28 | 28 | it "should return JSON by default" do |
| 29 | 29 | get :index, :auth_token => @user.authentication_token |
| 30 | - lambda { JSON.load(response.body) }.should_not raise_error(JSON::ParserError) | |
| 30 | + expect { JSON.load(response.body) }.not_to raise_error() #JSON::ParserError) | |
| 31 | 31 | end |
| 32 | 32 | |
| 33 | 33 | describe "given a date range" do | ... | ... |
spec/controllers/api/v1/problems_controller_spec.rb
| ... | ... | @@ -19,7 +19,7 @@ describe Api::V1::ProblemsController do |
| 19 | 19 | |
| 20 | 20 | it "should return JSON if JSON is requested" do |
| 21 | 21 | get :index, :auth_token => @user.authentication_token, :format => "json" |
| 22 | - lambda { JSON.load(response.body) }.should_not raise_error(JSON::ParserError) | |
| 22 | + expect { JSON.load(response.body) }.not_to raise_error()#JSON::ParserError) | |
| 23 | 23 | end |
| 24 | 24 | |
| 25 | 25 | it "should return XML if XML is requested" do |
| ... | ... | @@ -29,7 +29,7 @@ describe Api::V1::ProblemsController do |
| 29 | 29 | |
| 30 | 30 | it "should return JSON by default" do |
| 31 | 31 | get :index, :auth_token => @user.authentication_token |
| 32 | - lambda { JSON.load(response.body) }.should_not raise_error(JSON::ParserError) | |
| 32 | + expect { JSON.load(response.body) }.not_to raise_error()#JSON::ParserError) | |
| 33 | 33 | end |
| 34 | 34 | |
| 35 | 35 | ... | ... |
spec/controllers/apps_controller_spec.rb
| 1 | 1 | require 'spec_helper' |
| 2 | 2 | |
| 3 | 3 | describe AppsController do |
| 4 | - render_views | |
| 5 | 4 | |
| 6 | 5 | it_requires_authentication |
| 7 | 6 | it_requires_admin_privileges :for => {:new => :get, :edit => :get, :create => :post, :update => :put, :destroy => :delete} |
| 8 | 7 | |
| 9 | - | |
| 10 | 8 | describe "GET /apps" do |
| 11 | 9 | context 'when logged in as an admin' do |
| 12 | 10 | it 'finds all apps' do |
| 13 | 11 | sign_in Fabricate(:admin) |
| 14 | 12 | 3.times { Fabricate(:app) } |
| 15 | - apps = App.all | |
| 16 | 13 | get :index |
| 17 | - assigns(:apps).should == apps | |
| 14 | + controller.apps.should == App.all.sort.entries | |
| 18 | 15 | end |
| 19 | 16 | end |
| 20 | 17 | |
| ... | ... | @@ -27,8 +24,8 @@ describe AppsController do |
| 27 | 24 | Fabricate(:user_watcher, :user => user, :app => watched_app1) |
| 28 | 25 | Fabricate(:user_watcher, :user => user, :app => watched_app2) |
| 29 | 26 | get :index |
| 30 | - assigns(:apps).should include(watched_app1, watched_app2) | |
| 31 | - assigns(:apps).should_not include(unwatched_app) | |
| 27 | + controller.apps.should include(watched_app1, watched_app2) | |
| 28 | + controller.apps.should_not include(unwatched_app) | |
| 32 | 29 | end |
| 33 | 30 | end |
| 34 | 31 | end |
| ... | ... | @@ -44,7 +41,7 @@ describe AppsController do |
| 44 | 41 | |
| 45 | 42 | it 'finds the app' do |
| 46 | 43 | get :show, :id => @app.id |
| 47 | - assigns(:app).should == @app | |
| 44 | + controller.app.should == @app | |
| 48 | 45 | end |
| 49 | 46 | |
| 50 | 47 | it "should not raise errors for app with err without notices" do |
| ... | ... | @@ -55,7 +52,6 @@ describe AppsController do |
| 55 | 52 | it "should list atom feed successfully" do |
| 56 | 53 | get :show, :id => @app.id, :format => "atom" |
| 57 | 54 | response.should be_success |
| 58 | - response.body.should match(@problem.message) | |
| 59 | 55 | end |
| 60 | 56 | |
| 61 | 57 | context "pagination" do |
| ... | ... | @@ -65,13 +61,13 @@ describe AppsController do |
| 65 | 61 | |
| 66 | 62 | it "should have default per_page value for user" do |
| 67 | 63 | get :show, :id => @app.id |
| 68 | - assigns(:problems).to_a.size.should == User::PER_PAGE | |
| 64 | + controller.problems.to_a.size.should == User::PER_PAGE | |
| 69 | 65 | end |
| 70 | 66 | |
| 71 | 67 | it "should be able to override default per_page value" do |
| 72 | 68 | @user.update_attribute :per_page, 10 |
| 73 | 69 | get :show, :id => @app.id |
| 74 | - assigns(:problems).to_a.size.should == 10 | |
| 70 | + controller.problems.to_a.size.should == 10 | |
| 75 | 71 | end |
| 76 | 72 | end |
| 77 | 73 | |
| ... | ... | @@ -85,14 +81,14 @@ describe AppsController do |
| 85 | 81 | context 'and no params' do |
| 86 | 82 | it 'shows only unresolved problems' do |
| 87 | 83 | get :show, :id => @app.id |
| 88 | - assigns(:problems).size.should == 1 | |
| 84 | + controller.problems.size.should == 1 | |
| 89 | 85 | end |
| 90 | 86 | end |
| 91 | 87 | |
| 92 | 88 | context 'and all_problems=true params' do |
| 93 | 89 | it 'shows all errors' do |
| 94 | 90 | get :show, :id => @app.id, :all_errs => true |
| 95 | - assigns(:problems).size.should == 2 | |
| 91 | + controller.problems.size.should == 2 | |
| 96 | 92 | end |
| 97 | 93 | end |
| 98 | 94 | end |
| ... | ... | @@ -108,35 +104,35 @@ describe AppsController do |
| 108 | 104 | context 'no params' do |
| 109 | 105 | it 'shows errs for all environments' do |
| 110 | 106 | get :show, :id => @app.id |
| 111 | - assigns(:problems).size.should == 21 | |
| 107 | + controller.problems.size.should == 21 | |
| 112 | 108 | end |
| 113 | 109 | end |
| 114 | 110 | |
| 115 | 111 | context 'environment production' do |
| 116 | 112 | it 'shows errs for just production' do |
| 117 | 113 | get :show, :id => @app.id, :environment => 'production' |
| 118 | - assigns(:problems).size.should == 6 | |
| 114 | + controller.problems.size.should == 6 | |
| 119 | 115 | end |
| 120 | 116 | end |
| 121 | 117 | |
| 122 | 118 | context 'environment staging' do |
| 123 | 119 | it 'shows errs for just staging' do |
| 124 | 120 | get :show, :id => @app.id, :environment => 'staging' |
| 125 | - assigns(:problems).size.should == 5 | |
| 121 | + controller.problems.size.should == 5 | |
| 126 | 122 | end |
| 127 | 123 | end |
| 128 | 124 | |
| 129 | 125 | context 'environment development' do |
| 130 | 126 | it 'shows errs for just development' do |
| 131 | 127 | get :show, :id => @app.id, :environment => 'development' |
| 132 | - assigns(:problems).size.should == 5 | |
| 128 | + controller.problems.size.should == 5 | |
| 133 | 129 | end |
| 134 | 130 | end |
| 135 | 131 | |
| 136 | 132 | context 'environment test' do |
| 137 | 133 | it 'shows errs for just test' do |
| 138 | 134 | get :show, :id => @app.id, :environment => 'test' |
| 139 | - assigns(:problems).size.should == 5 | |
| 135 | + controller.problems.size.should == 5 | |
| 140 | 136 | end |
| 141 | 137 | end |
| 142 | 138 | end |
| ... | ... | @@ -149,7 +145,7 @@ describe AppsController do |
| 149 | 145 | watcher = Fabricate(:user_watcher, :app => app, :user => user) |
| 150 | 146 | sign_in user |
| 151 | 147 | get :show, :id => app.id |
| 152 | - assigns(:app).should == app | |
| 148 | + controller.app.should == app | |
| 153 | 149 | end |
| 154 | 150 | |
| 155 | 151 | it 'does not find the app if the user is not watching it' do |
| ... | ... | @@ -170,19 +166,19 @@ describe AppsController do |
| 170 | 166 | describe "GET /apps/new" do |
| 171 | 167 | it 'instantiates a new app with a prebuilt watcher' do |
| 172 | 168 | get :new |
| 173 | - assigns(:app).should be_a(App) | |
| 174 | - assigns(:app).should be_new_record | |
| 175 | - assigns(:app).watchers.should_not be_empty | |
| 169 | + controller.app.should be_a(App) | |
| 170 | + controller.app.should be_new_record | |
| 171 | + controller.app.watchers.should_not be_empty | |
| 176 | 172 | end |
| 177 | 173 | |
| 178 | 174 | it "should copy attributes from an existing app" do |
| 179 | 175 | @app = Fabricate(:app, :name => "do not copy", |
| 180 | 176 | :github_repo => "test/example") |
| 181 | 177 | get :new, :copy_attributes_from => @app.id |
| 182 | - assigns(:app).should be_a(App) | |
| 183 | - assigns(:app).should be_new_record | |
| 184 | - assigns(:app).name.should be_blank | |
| 185 | - assigns(:app).github_repo.should == "test/example" | |
| 178 | + controller.app.should be_a(App) | |
| 179 | + controller.app.should be_new_record | |
| 180 | + controller.app.name.should be_blank | |
| 181 | + controller.app.github_repo.should == "test/example" | |
| 186 | 182 | end |
| 187 | 183 | end |
| 188 | 184 | |
| ... | ... | @@ -190,7 +186,7 @@ describe AppsController do |
| 190 | 186 | it 'finds the correct app' do |
| 191 | 187 | app = Fabricate(:app) |
| 192 | 188 | get :edit, :id => app.id |
| 193 | - assigns(:app).should == app | |
| 189 | + controller.app.should == app | |
| 194 | 190 | end |
| 195 | 191 | end |
| 196 | 192 | |
| ... | ... | @@ -316,7 +312,6 @@ describe AppsController do |
| 316 | 312 | |
| 317 | 313 | @app.reload |
| 318 | 314 | @app.issue_tracker_configured?.should == false |
| 319 | - response.body.should match(/You must specify your/) | |
| 320 | 315 | end |
| 321 | 316 | end |
| 322 | 317 | end |
| ... | ... | @@ -326,12 +321,11 @@ describe AppsController do |
| 326 | 321 | describe "DELETE /apps/:id" do |
| 327 | 322 | before do |
| 328 | 323 | @app = Fabricate(:app) |
| 329 | - App.stub(:find).with(@app.id).and_return(@app) | |
| 330 | 324 | end |
| 331 | 325 | |
| 332 | 326 | it "should find the app" do |
| 333 | 327 | delete :destroy, :id => @app.id |
| 334 | - assigns(:app).should == @app | |
| 328 | + controller.app.should == @app | |
| 335 | 329 | end |
| 336 | 330 | |
| 337 | 331 | it "should destroy the app" do | ... | ... |
spec/controllers/notices_controller_spec.rb
| ... | ... | @@ -6,7 +6,7 @@ describe NoticesController do |
| 6 | 6 | let(:notice) { Fabricate(:notice) } |
| 7 | 7 | let(:xml) { Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read } |
| 8 | 8 | let(:app) { Fabricate(:app) } |
| 9 | - let(:error_report) { mock(:valid? => true, :generate_notice! => true, :notice => notice) } | |
| 9 | + let(:error_report) { double(:valid? => true, :generate_notice! => true, :notice => notice) } | |
| 10 | 10 | |
| 11 | 11 | context 'notices API' do |
| 12 | 12 | context "with all params" do |
| ... | ... | @@ -46,7 +46,7 @@ describe NoticesController do |
| 46 | 46 | response.body.should match(%r{<url[^>]*>(.+)#{locate_path(notice.id)}</url>}) |
| 47 | 47 | end |
| 48 | 48 | context "with an invalid API_KEY" do |
| 49 | - let(:error_report) { mock(:valid? => false) } | |
| 49 | + let(:error_report) { double(:valid? => false) } | |
| 50 | 50 | it 'return 422' do |
| 51 | 51 | post :create, :format => :xml, :data => xml |
| 52 | 52 | expect(response.status).to eq 422 | ... | ... |
spec/controllers/problems_controller_spec.rb
| ... | ... | @@ -12,7 +12,7 @@ describe ProblemsController do |
| 12 | 12 | |
| 13 | 13 | |
| 14 | 14 | describe "GET /problems" do |
| 15 | - render_views | |
| 15 | + #render_views | |
| 16 | 16 | context 'when logged in as an admin' do |
| 17 | 17 | before(:each) do |
| 18 | 18 | @user = Fabricate(:admin) |
| ... | ... | @@ -20,18 +20,6 @@ describe ProblemsController do |
| 20 | 20 | @problem = Fabricate(:notice, :err => Fabricate(:err, :problem => Fabricate(:problem, :app => app, :environment => "production"))).problem |
| 21 | 21 | end |
| 22 | 22 | |
| 23 | - it "should successfully list problems" do | |
| 24 | - get :index | |
| 25 | - response.should be_success | |
| 26 | - response.body.gsub("​", "").should match(@problem.message) | |
| 27 | - end | |
| 28 | - | |
| 29 | - it "should list atom feed successfully" do | |
| 30 | - get :index, :format => "atom" | |
| 31 | - response.should be_success | |
| 32 | - response.body.should match(@problem.message) | |
| 33 | - end | |
| 34 | - | |
| 35 | 23 | context "pagination" do |
| 36 | 24 | before(:each) do |
| 37 | 25 | 35.times { Fabricate :err } |
| ... | ... | @@ -39,13 +27,13 @@ describe ProblemsController do |
| 39 | 27 | |
| 40 | 28 | it "should have default per_page value for user" do |
| 41 | 29 | get :index |
| 42 | - assigns(:problems).to_a.size.should == User::PER_PAGE | |
| 30 | + controller.problems.to_a.size.should == User::PER_PAGE | |
| 43 | 31 | end |
| 44 | 32 | |
| 45 | 33 | it "should be able to override default per_page value" do |
| 46 | 34 | @user.update_attribute :per_page, 10 |
| 47 | 35 | get :index |
| 48 | - assigns(:problems).to_a.size.should == 10 | |
| 36 | + controller.problems.to_a.size.should == 10 | |
| 49 | 37 | end |
| 50 | 38 | end |
| 51 | 39 | |
| ... | ... | @@ -60,35 +48,35 @@ describe ProblemsController do |
| 60 | 48 | context 'no params' do |
| 61 | 49 | it 'shows problems for all environments' do |
| 62 | 50 | get :index |
| 63 | - assigns(:problems).size.should == 21 | |
| 51 | + controller.problems.size.should == 21 | |
| 64 | 52 | end |
| 65 | 53 | end |
| 66 | 54 | |
| 67 | 55 | context 'environment production' do |
| 68 | 56 | it 'shows problems for just production' do |
| 69 | 57 | get :index, :environment => 'production' |
| 70 | - assigns(:problems).size.should == 6 | |
| 58 | + controller.problems.size.should == 6 | |
| 71 | 59 | end |
| 72 | 60 | end |
| 73 | 61 | |
| 74 | 62 | context 'environment staging' do |
| 75 | 63 | it 'shows problems for just staging' do |
| 76 | 64 | get :index, :environment => 'staging' |
| 77 | - assigns(:problems).size.should == 5 | |
| 65 | + controller.problems.size.should == 5 | |
| 78 | 66 | end |
| 79 | 67 | end |
| 80 | 68 | |
| 81 | 69 | context 'environment development' do |
| 82 | 70 | it 'shows problems for just development' do |
| 83 | 71 | get :index, :environment => 'development' |
| 84 | - assigns(:problems).size.should == 5 | |
| 72 | + controller.problems.size.should == 5 | |
| 85 | 73 | end |
| 86 | 74 | end |
| 87 | 75 | |
| 88 | 76 | context 'environment test' do |
| 89 | 77 | it 'shows problems for just test' do |
| 90 | 78 | get :index, :environment => 'test' |
| 91 | - assigns(:problems).size.should == 5 | |
| 79 | + controller.problems.size.should == 5 | |
| 92 | 80 | end |
| 93 | 81 | end |
| 94 | 82 | end |
| ... | ... | @@ -101,8 +89,8 @@ describe ProblemsController do |
| 101 | 89 | watched_unresolved_err = Fabricate(:err, :problem => Fabricate(:problem, :app => Fabricate(:user_watcher, :user => user).app, :resolved => false)) |
| 102 | 90 | watched_resolved_err = Fabricate(:err, :problem => Fabricate(:problem, :app => Fabricate(:user_watcher, :user => user).app, :resolved => true)) |
| 103 | 91 | get :index |
| 104 | - assigns(:problems).should include(watched_unresolved_err.problem) | |
| 105 | - assigns(:problems).should_not include(unwatched_err.problem, watched_resolved_err.problem) | |
| 92 | + controller.problems.should include(watched_unresolved_err.problem) | |
| 93 | + controller.problems.should_not include(unwatched_err.problem, watched_resolved_err.problem) | |
| 106 | 94 | end |
| 107 | 95 | end |
| 108 | 96 | end |
| ... | ... | @@ -115,10 +103,10 @@ describe ProblemsController do |
| 115 | 103 | 3.times { problems << Fabricate(:err).problem } |
| 116 | 104 | 3.times { problems << Fabricate(:err, :problem => Fabricate(:problem, :resolved => true)).problem } |
| 117 | 105 | Problem.should_receive(:ordered_by).and_return( |
| 118 | - mock('proxy', :page => mock('other_proxy', :per => problems)) | |
| 106 | + double('proxy', :page => double('other_proxy', :per => problems)) | |
| 119 | 107 | ) |
| 120 | 108 | get :index, :all_errs => true |
| 121 | - assigns(:problems).should == problems | |
| 109 | + controller.problems.should == problems | |
| 122 | 110 | end |
| 123 | 111 | end |
| 124 | 112 | |
| ... | ... | @@ -129,14 +117,14 @@ describe ProblemsController do |
| 129 | 117 | watched_unresolved_problem = Fabricate(:problem, :app => Fabricate(:user_watcher, :user => user).app, :resolved => false) |
| 130 | 118 | watched_resolved_problem = Fabricate(:problem, :app => Fabricate(:user_watcher, :user => user).app, :resolved => true) |
| 131 | 119 | get :index, :all_errs => true |
| 132 | - assigns(:problems).should include(watched_resolved_problem, watched_unresolved_problem) | |
| 133 | - assigns(:problems).should_not include(unwatched_problem) | |
| 120 | + controller.problems.should include(watched_resolved_problem, watched_unresolved_problem) | |
| 121 | + controller.problems.should_not include(unwatched_problem) | |
| 134 | 122 | end |
| 135 | 123 | end |
| 136 | 124 | end |
| 137 | 125 | |
| 138 | 126 | describe "GET /apps/:app_id/problems/:id" do |
| 139 | - render_views | |
| 127 | + #render_views | |
| 140 | 128 | |
| 141 | 129 | context 'when logged in as an admin' do |
| 142 | 130 | before do |
| ... | ... | @@ -145,12 +133,12 @@ describe ProblemsController do |
| 145 | 133 | |
| 146 | 134 | it "finds the app" do |
| 147 | 135 | get :show, :app_id => app.id, :id => err.problem.id |
| 148 | - assigns(:app).should == app | |
| 136 | + controller.app.should == app | |
| 149 | 137 | end |
| 150 | 138 | |
| 151 | 139 | it "finds the problem" do |
| 152 | 140 | get :show, :app_id => app.id, :id => err.problem.id |
| 153 | - assigns(:problem).should == err.problem | |
| 141 | + controller.problem.should == err.problem | |
| 154 | 142 | end |
| 155 | 143 | |
| 156 | 144 | it "successfully render page" do |
| ... | ... | @@ -178,32 +166,6 @@ describe ProblemsController do |
| 178 | 166 | end |
| 179 | 167 | end |
| 180 | 168 | |
| 181 | - context "create issue button" do | |
| 182 | - let(:button_matcher) { match(/create issue/) } | |
| 183 | - | |
| 184 | - it "should not exist for problem's app without issue tracker" do | |
| 185 | - err = Fabricate :err | |
| 186 | - get :show, :app_id => err.app.id, :id => err.problem.id | |
| 187 | - | |
| 188 | - response.body.should_not button_matcher | |
| 189 | - end | |
| 190 | - | |
| 191 | - it "should exist for problem's app with issue tracker" do | |
| 192 | - tracker = Fabricate(:lighthouse_tracker) | |
| 193 | - err = Fabricate(:err, :problem => Fabricate(:problem, :app => tracker.app)) | |
| 194 | - get :show, :app_id => err.app.id, :id => err.problem.id | |
| 195 | - | |
| 196 | - response.body.should button_matcher | |
| 197 | - end | |
| 198 | - | |
| 199 | - it "should not exist for problem with issue_link" do | |
| 200 | - tracker = Fabricate(:lighthouse_tracker) | |
| 201 | - err = Fabricate(:err, :problem => Fabricate(:problem, :app => tracker.app, :issue_link => "http://some.host")) | |
| 202 | - get :show, :app_id => err.app.id, :id => err.problem.id | |
| 203 | - | |
| 204 | - response.body.should_not button_matcher | |
| 205 | - end | |
| 206 | - end | |
| 207 | 169 | end |
| 208 | 170 | |
| 209 | 171 | context 'when logged in as a user' do |
| ... | ... | @@ -217,7 +179,7 @@ describe ProblemsController do |
| 217 | 179 | |
| 218 | 180 | it 'finds the problem if the user is watching the app' do |
| 219 | 181 | get :show, :app_id => @watched_app.to_param, :id => @watched_err.problem.id |
| 220 | - assigns(:problem).should == @watched_err.problem | |
| 182 | + controller.problem.should == @watched_err.problem | |
| 221 | 183 | end |
| 222 | 184 | |
| 223 | 185 | it 'raises a DocumentNotFound error if the user is not watching the app' do |
| ... | ... | @@ -242,8 +204,8 @@ describe ProblemsController do |
| 242 | 204 | App.should_receive(:find).with(@problem.app.id).and_return(@problem.app) |
| 243 | 205 | @problem.app.problems.should_receive(:find).and_return(@problem.problem) |
| 244 | 206 | put :resolve, :app_id => @problem.app.id, :id => @problem.problem.id |
| 245 | - assigns(:app).should == @problem.app | |
| 246 | - assigns(:problem).should == @problem.problem | |
| 207 | + controller.app.should == @problem.app | |
| 208 | + controller.problem.should == @problem.problem | |
| 247 | 209 | end |
| 248 | 210 | |
| 249 | 211 | it "should resolve the issue" do |
| ... | ... | @@ -269,7 +231,7 @@ describe ProblemsController do |
| 269 | 231 | end |
| 270 | 232 | |
| 271 | 233 | describe "POST /apps/:app_id/problems/:id/create_issue" do |
| 272 | - render_views | |
| 234 | + #render_views | |
| 273 | 235 | |
| 274 | 236 | before(:each) do |
| 275 | 237 | sign_in Fabricate(:admin) |
| ... | ... | @@ -379,31 +341,25 @@ describe ProblemsController do |
| 379 | 341 | @problem2 = Fabricate(:err, :problem => Fabricate(:problem, :resolved => false)).problem |
| 380 | 342 | end |
| 381 | 343 | |
| 382 | - it "should apply to multiple problems" do | |
| 383 | - post :resolve_several, :problems => [@problem1.id.to_s, @problem2.id.to_s] | |
| 384 | - assigns(:selected_problems).should == [@problem1, @problem2] | |
| 385 | - end | |
| 386 | - | |
| 387 | - it "should require at least one problem" do | |
| 388 | - post :resolve_several, :problems => [] | |
| 389 | - request.flash[:notice].should match(/You have not selected any/) | |
| 390 | - end | |
| 391 | - | |
| 392 | 344 | context "POST /problems/merge_several" do |
| 393 | 345 | it "should require at least two problems" do |
| 394 | 346 | post :merge_several, :problems => [@problem1.id.to_s] |
| 395 | - request.flash[:notice].should match(/You must select at least two/) | |
| 347 | + request.flash[:notice].should eql I18n.t('controllers.problems.flash.need_two_errors_merge') | |
| 396 | 348 | end |
| 397 | 349 | |
| 398 | 350 | it "should merge the problems" do |
| 399 | - lambda { | |
| 400 | - post :merge_several, :problems => [@problem1.id.to_s, @problem2.id.to_s] | |
| 401 | - assigns(:merged_problem).reload.errs.length.should == 2 | |
| 402 | - }.should change(Problem, :count).by(-1) | |
| 351 | + ProblemMerge.should_receive(:new).and_return(double(:merge => true)) | |
| 352 | + post :merge_several, :problems => [@problem1.id.to_s, @problem2.id.to_s] | |
| 403 | 353 | end |
| 404 | 354 | end |
| 405 | 355 | |
| 406 | 356 | context "POST /problems/unmerge_several" do |
| 357 | + | |
| 358 | + it "should require at least one problem" do | |
| 359 | + post :unmerge_several, :problems => [] | |
| 360 | + request.flash[:notice].should eql I18n.t('controllers.problems.flash.no_select_problem') | |
| 361 | + end | |
| 362 | + | |
| 407 | 363 | it "should unmerge a merged problem" do |
| 408 | 364 | merged_problem = Problem.merge!(@problem1, @problem2) |
| 409 | 365 | merged_problem.errs.length.should == 2 |
| ... | ... | @@ -412,9 +368,16 @@ describe ProblemsController do |
| 412 | 368 | merged_problem.reload.errs.length.should == 1 |
| 413 | 369 | }.should change(Problem, :count).by(1) |
| 414 | 370 | end |
| 371 | + | |
| 415 | 372 | end |
| 416 | 373 | |
| 417 | 374 | context "POST /problems/resolve_several" do |
| 375 | + | |
| 376 | + it "should require at least one problem" do | |
| 377 | + post :resolve_several, :problems => [] | |
| 378 | + request.flash[:notice].should eql I18n.t('controllers.problems.flash.no_select_problem') | |
| 379 | + end | |
| 380 | + | |
| 418 | 381 | it "should resolve the issue" do |
| 419 | 382 | post :resolve_several, :problems => [@problem2.id.to_s] |
| 420 | 383 | @problem2.reload.resolved?.should == true |
| ... | ... | @@ -428,10 +391,17 @@ describe ProblemsController do |
| 428 | 391 | it "should display a message about 2 errs" do |
| 429 | 392 | post :resolve_several, :problems => [@problem1.id.to_s, @problem2.id.to_s] |
| 430 | 393 | flash[:success].should match(/2 errs have been resolved/) |
| 394 | + controller.selected_problems.should == [@problem1, @problem2] | |
| 431 | 395 | end |
| 432 | 396 | end |
| 433 | 397 | |
| 434 | 398 | context "POST /problems/unresolve_several" do |
| 399 | + | |
| 400 | + it "should require at least one problem" do | |
| 401 | + post :unresolve_several, :problems => [] | |
| 402 | + request.flash[:notice].should eql I18n.t('controllers.problems.flash.no_select_problem') | |
| 403 | + end | |
| 404 | + | |
| 435 | 405 | it "should unresolve the issue" do |
| 436 | 406 | post :unresolve_several, :problems => [@problem1.id.to_s] |
| 437 | 407 | @problem1.reload.resolved?.should == false | ... | ... |
spec/controllers/users/omniauth_callbacks_controller_spec.rb
| ... | ... | @@ -12,7 +12,7 @@ describe Users::OmniauthCallbacksController do |
| 12 | 12 | :credentials => { :token => token } |
| 13 | 13 | ) |
| 14 | 14 | } |
| 15 | - @controller.stub!(:env).and_return(env) | |
| 15 | + @controller.stub(:env).and_return(env) | |
| 16 | 16 | end |
| 17 | 17 | |
| 18 | 18 | context 'Linking a GitHub account to a signed in user' do | ... | ... |
spec/controllers/users_controller_spec.rb
| ... | ... | @@ -207,7 +207,7 @@ describe UsersController do |
| 207 | 207 | context "DELETE /users/:id" do |
| 208 | 208 | |
| 209 | 209 | context "with a destroy success" do |
| 210 | - let(:user_destroy) { mock(:destroy => true) } | |
| 210 | + let(:user_destroy) { double(:destroy => true) } | |
| 211 | 211 | |
| 212 | 212 | before { |
| 213 | 213 | UserDestroy.should_receive(:new).with(user).and_return(user_destroy) | ... | ... |
spec/fabricators_spec.rb
| ... | ... | @@ -1,18 +0,0 @@ |
| 1 | -require 'spec_helper' | |
| 2 | - | |
| 3 | -Fabrication::Config.fabricator_dir.each do |folder| | |
| 4 | - Dir.glob(File.join(Rails.root, folder, '**', '*.rb')).each do |file| | |
| 5 | - require file | |
| 6 | - end | |
| 7 | -end | |
| 8 | - | |
| 9 | -describe "Fabrication" do | |
| 10 | - #TODO : when 1.8.7 drop support se directly Symbol#sort | |
| 11 | - Fabrication::Fabricator.schematics.keys.sort_by(&:to_s).each do |fabricator_name| | |
| 12 | - context "Fabricate(:#{fabricator_name})" do | |
| 13 | - subject { Fabricate.build(fabricator_name) } | |
| 14 | - | |
| 15 | - it { should be_valid } | |
| 16 | - end | |
| 17 | - end | |
| 18 | -end |
spec/interactors/problem_destroy_spec.rb
| ... | ... | @@ -8,8 +8,8 @@ describe ProblemDestroy do |
| 8 | 8 | context "in unit way" do |
| 9 | 9 | let(:problem) { |
| 10 | 10 | problem = Problem.new |
| 11 | - problem.stub(:errs).and_return(mock(:criteria, :only => [err_1, err_2])) | |
| 12 | - problem.stub(:comments).and_return(mock(:criteria, :only => [comment_1, comment_2])) | |
| 11 | + problem.stub(:errs).and_return(double(:criteria, :only => [err_1, err_2])) | |
| 12 | + problem.stub(:comments).and_return(double(:criteria, :only => [comment_1, comment_2])) | |
| 13 | 13 | problem.stub(:delete) |
| 14 | 14 | problem |
| 15 | 15 | } | ... | ... |
spec/interactors/problem_merge_spec.rb
| ... | ... | @@ -42,7 +42,7 @@ describe ProblemMerge do |
| 42 | 42 | end |
| 43 | 43 | |
| 44 | 44 | it 'update problem cache' do |
| 45 | - ProblemUpdaterCache.should_receive(:new).with(problem).and_return(mock(:update => true)) | |
| 45 | + ProblemUpdaterCache.should_receive(:new).with(problem).and_return(double(:update => true)) | |
| 46 | 46 | problem_merge.merge |
| 47 | 47 | end |
| 48 | 48 | ... | ... |
spec/models/backtrace_spec.rb
| ... | ... | @@ -22,8 +22,8 @@ describe Backtrace do |
| 22 | 22 | |
| 23 | 23 | describe "find_or_create" do |
| 24 | 24 | subject { described_class.find_or_create(attributes) } |
| 25 | - let(:attributes) { mock :attributes } | |
| 26 | - let(:backtrace) { mock :backtrace } | |
| 25 | + let(:attributes) { double :attributes } | |
| 26 | + let(:backtrace) { double :backtrace } | |
| 27 | 27 | |
| 28 | 28 | before { described_class.stub(:new => backtrace) } |
| 29 | 29 | |
| ... | ... | @@ -37,7 +37,7 @@ describe Backtrace do |
| 37 | 37 | end |
| 38 | 38 | |
| 39 | 39 | context "similar backtrace exist" do |
| 40 | - let(:similar_backtrace) { mock :similar_backtrace } | |
| 40 | + let(:similar_backtrace) { double :similar_backtrace } | |
| 41 | 41 | before { backtrace.stub(:similar => similar_backtrace) } |
| 42 | 42 | |
| 43 | 43 | it { should == similar_backtrace } | ... | ... |
spec/models/comment_observer_spec.rb
| ... | ... | @@ -10,7 +10,7 @@ describe CommentObserver do |
| 10 | 10 | it 'should send an email notification' do |
| 11 | 11 | Mailer.should_receive(:comment_notification). |
| 12 | 12 | with(comment). |
| 13 | - and_return(mock('email', :deliver => true)) | |
| 13 | + and_return(double('email', :deliver => true)) | |
| 14 | 14 | comment.save |
| 15 | 15 | end |
| 16 | 16 | end | ... | ... |
spec/models/deploy_observer_spec.rb
| ... | ... | @@ -5,7 +5,7 @@ describe DeployObserver do |
| 5 | 5 | context 'and the app should notify on deploys' do |
| 6 | 6 | it 'should send an email notification' do |
| 7 | 7 | Mailer.should_receive(:deploy_notification). |
| 8 | - and_return(mock('email', :deliver => true)) | |
| 8 | + and_return(double('email', :deliver => true)) | |
| 9 | 9 | Fabricate(:deploy, :app => Fabricate(:app_with_watcher, :notify_on_deploys => true)) |
| 10 | 10 | end |
| 11 | 11 | end | ... | ... |
| ... | ... | @@ -0,0 +1,18 @@ |
| 1 | +require 'spec_helper' | |
| 2 | + | |
| 3 | +Fabrication::Config.fabricator_dir.each do |folder| | |
| 4 | + Dir.glob(File.join(Rails.root, folder, '**', '*.rb')).each do |file| | |
| 5 | + require file | |
| 6 | + end | |
| 7 | +end | |
| 8 | + | |
| 9 | +describe "Fabrication" do | |
| 10 | + #TODO : when 1.8.7 drop support se directly Symbol#sort | |
| 11 | + Fabrication::Fabricator.schematics.keys.sort_by(&:to_s).each do |fabricator_name| | |
| 12 | + context "Fabricate(:#{fabricator_name})" do | |
| 13 | + subject { Fabricate.build(fabricator_name) } | |
| 14 | + | |
| 15 | + it { should be_valid } | |
| 16 | + end | |
| 17 | + end | |
| 18 | +end | ... | ... |
spec/models/issue_trackers/fogbugz_tracker_spec.rb
| ... | ... | @@ -9,7 +9,7 @@ describe IssueTrackers::FogbugzTracker do |
| 9 | 9 | number = 123 |
| 10 | 10 | @issue_link = "https://#{tracker.account}.fogbugz.com/default.asp?#{number}" |
| 11 | 11 | response = "<response><token>12345</token><case><ixBug>123</ixBug></case></response>" |
| 12 | - http_mock = mock | |
| 12 | + http_mock = double | |
| 13 | 13 | http_mock.should_receive(:new).and_return(http_mock) |
| 14 | 14 | http_mock.should_receive(:request).twice.and_return(response) |
| 15 | 15 | Fogbugz.adapter[:http] = http_mock | ... | ... |
spec/models/notice_observer_spec.rb
| ... | ... | @@ -18,7 +18,7 @@ describe NoticeObserver do |
| 18 | 18 | it "sends an email notification after #{threshold} notice(s)" do |
| 19 | 19 | @err.problem.stub(:notices_count).and_return(threshold) |
| 20 | 20 | Mailer.should_receive(:err_notification). |
| 21 | - and_return(mock('email', :deliver => true)) | |
| 21 | + and_return(double('email', :deliver => true)) | |
| 22 | 22 | Fabricate(:notice, :err => @err) |
| 23 | 23 | end |
| 24 | 24 | end |
| ... | ... | @@ -38,7 +38,7 @@ describe NoticeObserver do |
| 38 | 38 | it "should send email notification after 1 notice since an error has been resolved" do |
| 39 | 39 | @err.problem.resolve! |
| 40 | 40 | Mailer.should_receive(:err_notification). |
| 41 | - and_return(mock('email', :deliver => true)) | |
| 41 | + and_return(double('email', :deliver => true)) | |
| 42 | 42 | Fabricate(:notice, :err => @err) |
| 43 | 43 | end |
| 44 | 44 | end |
| ... | ... | @@ -103,7 +103,7 @@ describe NoticeObserver do |
| 103 | 103 | Fabricate(:notice, :err => err) |
| 104 | 104 | end |
| 105 | 105 | end |
| 106 | - | |
| 106 | + | |
| 107 | 107 | describe "should send a notification at desired intervals" do |
| 108 | 108 | let(:app) { Fabricate(:app, :email_at_notices => [1], :notification_service => Fabricate(:campfire_notification_service, :notify_at_notices => [1,2]))} |
| 109 | 109 | let(:backtrace) { Fabricate(:backtrace) } | ... | ... |
spec/models/notification_service/campfire_service_spec.rb
spec/models/notification_service/gtalk_service_spec.rb
| ... | ... | @@ -9,7 +9,7 @@ describe NotificationService::GtalkService do |
| 9 | 9 | problem = notice.problem |
| 10 | 10 | |
| 11 | 11 | #gtalk stubbing |
| 12 | - gtalk = mock('GtalkService') | |
| 12 | + gtalk = double('GtalkService') | |
| 13 | 13 | jid = double("jid") |
| 14 | 14 | message = double("message") |
| 15 | 15 | Jabber::JID.should_receive(:new).with(notification_service.subdomain).and_return(jid) |
| ... | ... | @@ -19,13 +19,13 @@ describe NotificationService::GtalkService do |
| 19 | 19 | message_value = """#{problem.app.name.to_s} |
| 20 | 20 | http://#{Errbit::Config.host}/apps/#{problem.app.id.to_s} |
| 21 | 21 | #{notification_service.notification_description problem}""" |
| 22 | - | |
| 22 | + | |
| 23 | 23 | Jabber::Message.should_receive(:new).with(notification_service.user_id, message_value).and_return(message) |
| 24 | 24 | Jabber::Message.should_receive(:new).with(notification_service.room_id, message_value).and_return(message) |
| 25 | - | |
| 25 | + | |
| 26 | 26 | Jabber::MUC::SimpleMUCClient.should_receive(:new).and_return(gtalk) |
| 27 | 27 | gtalk.should_receive(:join).with(notification_service.room_id + "/errbit") |
| 28 | - | |
| 28 | + | |
| 29 | 29 | #assert |
| 30 | 30 | gtalk.should_receive(:send).exactly(2).times.with(message) |
| 31 | 31 | |
| ... | ... | @@ -43,7 +43,7 @@ http://#{Errbit::Config.host}/apps/#{@problem.app.id.to_s} |
| 43 | 43 | #{@notification_service.notification_description @problem}""" |
| 44 | 44 | |
| 45 | 45 | # gtalk stubbing |
| 46 | - @gtalk = mock('GtalkService') | |
| 46 | + @gtalk = double('GtalkService') | |
| 47 | 47 | @gtalk.should_receive(:connect) |
| 48 | 48 | @gtalk.should_receive(:auth) |
| 49 | 49 | jid = double("jid") |
| ... | ... | @@ -86,7 +86,7 @@ http://#{Errbit::Config.host}/apps/#{@problem.app.id.to_s} |
| 86 | 86 | @notification_service.room_id = "" |
| 87 | 87 | @notification_service.create_notification(@problem) |
| 88 | 88 | end |
| 89 | - | |
| 89 | + | |
| 90 | 90 | end |
| 91 | 91 | |
| 92 | 92 | it "it should send a notification to room only" do |
| ... | ... | @@ -97,7 +97,7 @@ http://#{Errbit::Config.host}/apps/#{@problem.app.id.to_s} |
| 97 | 97 | problem = notice.problem |
| 98 | 98 | |
| 99 | 99 | #gtalk stubbing |
| 100 | - gtalk = mock('GtalkService') | |
| 100 | + gtalk = double('GtalkService') | |
| 101 | 101 | jid = double("jid") |
| 102 | 102 | message = double("message") |
| 103 | 103 | Jabber::JID.should_receive(:new).with(notification_service.subdomain).and_return(jid) |
| ... | ... | @@ -107,11 +107,11 @@ http://#{Errbit::Config.host}/apps/#{@problem.app.id.to_s} |
| 107 | 107 | message_value = """#{problem.app.name.to_s} |
| 108 | 108 | http://#{Errbit::Config.host}/apps/#{problem.app.id.to_s} |
| 109 | 109 | #{notification_service.notification_description problem}""" |
| 110 | - | |
| 110 | + | |
| 111 | 111 | Jabber::Message.should_receive(:new).with(notification_service.room_id, message_value).and_return(message) |
| 112 | - | |
| 112 | + | |
| 113 | 113 | Jabber::MUC::SimpleMUCClient.should_receive(:new).and_return(gtalk) |
| 114 | - gtalk.should_receive(:join).with(notification_service.room_id + "/errbit") | |
| 114 | + gtalk.should_receive(:join).with(notification_service.room_id + "/errbit") | |
| 115 | 115 | |
| 116 | 116 | notification_service.user_id = "" |
| 117 | 117 | ... | ... |
spec/models/notification_service/hoiio_service_spec.rb
spec/models/notification_service/pushover_service_spec.rb
| ... | ... | @@ -8,7 +8,7 @@ describe NotificationService::PushoverService do |
| 8 | 8 | problem = notice.problem |
| 9 | 9 | |
| 10 | 10 | # hoi stubbing |
| 11 | - notification = mock('PushoverService') | |
| 11 | + notification = double('PushoverService') | |
| 12 | 12 | Rushover::Client.stub(:new).and_return(notification) |
| 13 | 13 | notification.stub(:notify) { true } |
| 14 | 14 | ... | ... |
spec/models/problem_spec.rb
| ... | ... | @@ -123,12 +123,12 @@ describe Problem do |
| 123 | 123 | it "should throw an err if it's not successful" do |
| 124 | 124 | problem = Fabricate(:problem) |
| 125 | 125 | problem.should_not be_resolved |
| 126 | - problem.stub!(:valid?).and_return(false) | |
| 126 | + problem.stub(:valid?).and_return(false) | |
| 127 | 127 | ## update_attributes not test #valid? but #errors.any? |
| 128 | 128 | # https://github.com/mongoid/mongoid/blob/master/lib/mongoid/persistence.rb#L137 |
| 129 | 129 | er = ActiveModel::Errors.new(problem) |
| 130 | 130 | er.add_on_blank(:resolved) |
| 131 | - problem.stub!(:errors).and_return(er) | |
| 131 | + problem.stub(:errors).and_return(er) | |
| 132 | 132 | problem.should_not be_valid |
| 133 | 133 | lambda { |
| 134 | 134 | problem.resolve! |
| ... | ... | @@ -152,7 +152,6 @@ describe Problem do |
| 152 | 152 | end |
| 153 | 153 | end |
| 154 | 154 | |
| 155 | - | |
| 156 | 155 | context "Scopes" do |
| 157 | 156 | context "resolved" do |
| 158 | 157 | it 'only finds resolved Problems' do | ... | ... |
spec/views/apps/edit.html.haml_spec.rb
| 1 | 1 | require 'spec_helper' |
| 2 | 2 | |
| 3 | 3 | describe "apps/edit.html.haml" do |
| 4 | + let(:app) { stub_model(App) } | |
| 4 | 5 | before do |
| 5 | - app = stub_model(App) | |
| 6 | - assign :app, app | |
| 6 | + view.stub(:app).and_return(app) | |
| 7 | 7 | controller.stub(:current_user) { stub_model(User) } |
| 8 | 8 | end |
| 9 | 9 | |
| ... | ... | @@ -19,5 +19,19 @@ describe "apps/edit.html.haml" do |
| 19 | 19 | end |
| 20 | 20 | |
| 21 | 21 | end |
| 22 | + | |
| 23 | + context "with unvalid app" do | |
| 24 | + let(:app) { | |
| 25 | + app = stub_model(App) | |
| 26 | + app.errors.add(:base,'You must specify your') | |
| 27 | + app | |
| 28 | + } | |
| 29 | + | |
| 30 | + it 'see the error' do | |
| 31 | + render | |
| 32 | + rendered.should match(/You must specify your/) | |
| 33 | + end | |
| 34 | + end | |
| 35 | + | |
| 22 | 36 | end |
| 23 | 37 | ... | ... |
spec/views/apps/index.html.haml_spec.rb
| ... | ... | @@ -3,7 +3,7 @@ require 'spec_helper' |
| 3 | 3 | describe "apps/index.html.haml" do |
| 4 | 4 | before do |
| 5 | 5 | app = stub_model(App, :deploys => [stub_model(Deploy, :created_at => Time.now, :revision => "123456789abcdef")]) |
| 6 | - assign :apps, [app] | |
| 6 | + view.stub(:apps).and_return([app]) | |
| 7 | 7 | controller.stub(:current_user) { stub_model(User) } |
| 8 | 8 | end |
| 9 | 9 | ... | ... |
| ... | ... | @@ -0,0 +1,37 @@ |
| 1 | +require 'spec_helper' | |
| 2 | + | |
| 3 | +describe "apps/new.html.haml" do | |
| 4 | + let(:app) { stub_model(App) } | |
| 5 | + before do | |
| 6 | + view.stub(:app).and_return(app) | |
| 7 | + controller.stub(:current_user) { stub_model(User) } | |
| 8 | + end | |
| 9 | + | |
| 10 | + describe "content_for :action_bar" do | |
| 11 | + def action_bar | |
| 12 | + view.content_for(:action_bar) | |
| 13 | + end | |
| 14 | + | |
| 15 | + it "should confirm the 'cancel' link" do | |
| 16 | + render | |
| 17 | + | |
| 18 | + action_bar.should have_selector('a.button', :text => 'cancel') | |
| 19 | + end | |
| 20 | + | |
| 21 | + end | |
| 22 | + | |
| 23 | + context "with unvalid app" do | |
| 24 | + let(:app) { | |
| 25 | + app = stub_model(App) | |
| 26 | + app.errors.add(:base,'You must specify your') | |
| 27 | + app | |
| 28 | + } | |
| 29 | + | |
| 30 | + it 'see the error' do | |
| 31 | + render | |
| 32 | + rendered.should match(/You must specify your/) | |
| 33 | + end | |
| 34 | + end | |
| 35 | + | |
| 36 | +end | |
| 37 | + | ... | ... |
| ... | ... | @@ -0,0 +1,21 @@ |
| 1 | +require 'spec_helper' | |
| 2 | + | |
| 3 | +describe "apps/show.atom.builder" do | |
| 4 | + let(:app) { stub_model(App) } | |
| 5 | + let(:problems) { [ | |
| 6 | + stub_model(Problem, :message => 'foo', :app => app) | |
| 7 | + ]} | |
| 8 | + | |
| 9 | + before do | |
| 10 | + view.stub(:app).and_return(app) | |
| 11 | + view.stub(:problems).and_return(problems) | |
| 12 | + end | |
| 13 | + | |
| 14 | + context "with errs" do | |
| 15 | + it 'see the errs message' do | |
| 16 | + render | |
| 17 | + expect(rendered).to match(problems.first.message) | |
| 18 | + end | |
| 19 | + end | |
| 20 | + | |
| 21 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,32 @@ |
| 1 | +require 'spec_helper' | |
| 2 | + | |
| 3 | +describe "apps/show.html.haml" do | |
| 4 | + let(:app) { stub_model(App) } | |
| 5 | + before do | |
| 6 | + view.stub(:app).and_return(app) | |
| 7 | + view.stub(:all_errs).and_return(false) | |
| 8 | + view.stub(:deploys).and_return([]) | |
| 9 | + controller.stub(:current_user) { stub_model(User) } | |
| 10 | + end | |
| 11 | + | |
| 12 | + describe "content_for :action_bar" do | |
| 13 | + def action_bar | |
| 14 | + view.content_for(:action_bar) | |
| 15 | + end | |
| 16 | + | |
| 17 | + it "should confirm the 'cancel' link" do | |
| 18 | + render | |
| 19 | + | |
| 20 | + action_bar.should have_selector('a.button', :text => 'all errs') | |
| 21 | + end | |
| 22 | + | |
| 23 | + end | |
| 24 | + | |
| 25 | + context "without errs" do | |
| 26 | + it 'see no errs' do | |
| 27 | + render | |
| 28 | + rendered.should match(/No errs have been/) | |
| 29 | + end | |
| 30 | + end | |
| 31 | +end | |
| 32 | + | ... | ... |
| ... | ... | @@ -0,0 +1,14 @@ |
| 1 | +require 'spec_helper' | |
| 2 | + | |
| 3 | +describe "problems/index.atom.builder" do | |
| 4 | + | |
| 5 | + it 'display problem message' do | |
| 6 | + app = App.new(:new_record => false) | |
| 7 | + view.stub(:problems).and_return([Problem.new( | |
| 8 | + :message => 'foo', | |
| 9 | + :new_record => false, :app => app), Problem.new(:new_record => false, :app => app)]) | |
| 10 | + render | |
| 11 | + rendered.should match('foo') | |
| 12 | + end | |
| 13 | + | |
| 14 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,25 @@ |
| 1 | +require 'spec_helper' | |
| 2 | + | |
| 3 | +describe "problems/index.html.haml" do | |
| 4 | + let(:problem_1) { Fabricate(:problem) } | |
| 5 | + let(:problem_2) { Fabricate(:problem, :app => problem_1.app) } | |
| 6 | + | |
| 7 | + before do | |
| 8 | + # view.stub(:app).and_return(problem.app) | |
| 9 | + view.stub(:selected_problems).and_return([]) | |
| 10 | + view.stub(:problems).and_return(Kaminari.paginate_array([problem_1, problem_2]).page(1).per(10)) | |
| 11 | + view.stub(:params_sort).and_return('asc') | |
| 12 | + controller.stub(:current_user) { Fabricate(:user) } | |
| 13 | + end | |
| 14 | + | |
| 15 | + describe "with problem" do | |
| 16 | + before { problem_1 && problem_2 } | |
| 17 | + | |
| 18 | + it 'should works' do | |
| 19 | + render | |
| 20 | + rendered.should have_selector('div#problem_table.problem_table') | |
| 21 | + end | |
| 22 | + end | |
| 23 | + | |
| 24 | +end | |
| 25 | + | ... | ... |
spec/views/problems/show.html.haml_spec.rb
| 1 | 1 | require 'spec_helper' |
| 2 | 2 | |
| 3 | 3 | describe "problems/show.html.haml" do |
| 4 | + let(:problem) { Fabricate(:problem) } | |
| 5 | + let(:comment) { Fabricate(:comment) } | |
| 6 | + | |
| 4 | 7 | before do |
| 5 | - problem = Fabricate(:problem) | |
| 6 | - comment = Fabricate(:comment) | |
| 7 | - assign :problem, problem | |
| 8 | + view.stub(:app).and_return(problem.app) | |
| 9 | + view.stub(:problem).and_return(problem) | |
| 10 | + | |
| 8 | 11 | assign :comment, comment |
| 9 | - assign :app, problem.app | |
| 10 | 12 | assign :notices, problem.notices.page(1).per(1) |
| 11 | 13 | assign :notice, problem.notices.first |
| 14 | + | |
| 12 | 15 | controller.stub(:current_user) { Fabricate(:user) } |
| 13 | 16 | end |
| 14 | 17 | |
| 15 | 18 | def with_issue_tracker(tracker, problem) |
| 16 | 19 | problem.app.issue_tracker = tracker.new :api_token => "token token token", :project_id => "1234" |
| 17 | - assign :problem, problem | |
| 18 | - assign :app, problem.app | |
| 20 | + view.stub(:problem).and_return(problem) | |
| 21 | + view.stub(:app).and_return(problem.app) | |
| 19 | 22 | end |
| 20 | 23 | |
| 21 | 24 | describe "content_for :action_bar" do |
| ... | ... | @@ -54,8 +57,8 @@ describe "problems/show.html.haml" do |
| 54 | 57 | it "should link 'up' to app_problems_path if HTTP_REFERER isn't set'" do |
| 55 | 58 | controller.request.env['HTTP_REFERER'] = nil |
| 56 | 59 | problem = Fabricate(:problem_with_comments) |
| 57 | - assign :problem, problem | |
| 58 | - assign :app, problem.app | |
| 60 | + view.stub(:problem).and_return(problem) | |
| 61 | + view.stub(:app).and_return(problem.app) | |
| 59 | 62 | render |
| 60 | 63 | |
| 61 | 64 | action_bar.should have_selector("span a.up[href='#{app_problems_path(problem.app)}']", :text => 'up') |
| ... | ... | @@ -67,8 +70,8 @@ describe "problems/show.html.haml" do |
| 67 | 70 | controller.stub(:current_user) { user } |
| 68 | 71 | |
| 69 | 72 | problem = Fabricate(:problem_with_comments, :app => Fabricate(:app, :github_repo => "test_user/test_repo")) |
| 70 | - assign :problem, problem | |
| 71 | - assign :app, problem.app | |
| 73 | + view.stub(:problem).and_return(problem) | |
| 74 | + view.stub(:app).and_return(problem.app) | |
| 72 | 75 | render |
| 73 | 76 | |
| 74 | 77 | action_bar.should have_selector("span a.github_create.create-issue", :text => 'create issue') |
| ... | ... | @@ -77,12 +80,54 @@ describe "problems/show.html.haml" do |
| 77 | 80 | it 'should allow creating issue for github if application has a github tracker' do |
| 78 | 81 | problem = Fabricate(:problem_with_comments, :app => Fabricate(:app, :github_repo => "test_user/test_repo")) |
| 79 | 82 | with_issue_tracker(GithubIssuesTracker, problem) |
| 80 | - assign :problem, problem | |
| 81 | - assign :app, problem.app | |
| 83 | + view.stub(:problem).and_return(problem) | |
| 84 | + view.stub(:app).and_return(problem.app) | |
| 82 | 85 | render |
| 83 | 86 | |
| 84 | 87 | action_bar.should have_selector("span a.github_create.create-issue", :text => 'create issue') |
| 85 | 88 | end |
| 89 | + | |
| 90 | + context "without issue tracker associate on app" do | |
| 91 | + let(:problem){ Problem.new(:new_record => false, :app => app) } | |
| 92 | + let(:app) { App.new(:new_record => false) } | |
| 93 | + | |
| 94 | + it 'not see link to create issue' do | |
| 95 | + view.stub(:problem).and_return(problem) | |
| 96 | + view.stub(:app).and_return(problem.app) | |
| 97 | + render | |
| 98 | + expect(view.content_for(:action_bar)).to_not match(/create issue/) | |
| 99 | + end | |
| 100 | + | |
| 101 | + end | |
| 102 | + | |
| 103 | + context "with lighthouse tracker on app" do | |
| 104 | + let(:app) { App.new(:new_record => false, :issue_tracker => tracker ) } | |
| 105 | + let(:tracker) { | |
| 106 | + IssueTrackers::LighthouseTracker.new(:project_id => 'x') | |
| 107 | + } | |
| 108 | + context "with problem without issue link" do | |
| 109 | + let(:problem){ Problem.new(:new_record => false, :app => app) } | |
| 110 | + it 'not see link if no issue tracker' do | |
| 111 | + view.stub(:problem).and_return(problem) | |
| 112 | + view.stub(:app).and_return(problem.app) | |
| 113 | + render | |
| 114 | + expect(view.content_for(:action_bar)).to match(/create issue/) | |
| 115 | + end | |
| 116 | + | |
| 117 | + end | |
| 118 | + | |
| 119 | + context "with problem with issue link" do | |
| 120 | + let(:problem){ Problem.new(:new_record => false, :app => app, :issue_link => 'http://foo') } | |
| 121 | + | |
| 122 | + it 'not see link if no issue tracker' do | |
| 123 | + view.stub(:problem).and_return(problem) | |
| 124 | + view.stub(:app).and_return(problem.app) | |
| 125 | + render | |
| 126 | + expect(view.content_for(:action_bar)).to_not match(/create issue/) | |
| 127 | + end | |
| 128 | + end | |
| 129 | + | |
| 130 | + end | |
| 86 | 131 | end |
| 87 | 132 | end |
| 88 | 133 | |
| ... | ... | @@ -94,8 +139,8 @@ describe "problems/show.html.haml" do |
| 94 | 139 | |
| 95 | 140 | it 'should display comments and new comment form when no issue tracker' do |
| 96 | 141 | problem = Fabricate(:problem_with_comments) |
| 97 | - assign :problem, problem | |
| 98 | - assign :app, problem.app | |
| 142 | + view.stub(:problem).and_return(problem) | |
| 143 | + view.stub(:app).and_return(problem.app) | |
| 99 | 144 | render |
| 100 | 145 | |
| 101 | 146 | view.content_for(:comments).should include('Test comment') | ... | ... |
| ... | ... | @@ -0,0 +1,14 @@ |
| 1 | +require 'spec_helper' | |
| 2 | + | |
| 3 | +describe "problems/show.html.ics" do | |
| 4 | + let(:problem) { Fabricate(:problem) } | |
| 5 | + before do | |
| 6 | + view.stub(:problem).and_return(problem) | |
| 7 | + end | |
| 8 | + | |
| 9 | + it 'should work' do | |
| 10 | + render :template => 'problems/show', :formats => [:ics], :handlers => [:haml] | |
| 11 | + end | |
| 12 | + | |
| 13 | + | |
| 14 | +end | ... | ... |