Commit 72e373804c57903d195f8b5e1957f0bc5a11f1b5

Authored by Brendon Rapp
2 parents 1eed79c8 fe1b7f5f
Exists in master and in 1 other branch production

Merge branch 'master' of github:errbit/errbit

Showing 66 changed files with 1136 additions and 563 deletions   Show diff stats
@@ -18,3 +18,4 @@ bin @@ -18,3 +18,4 @@ bin
18 bundle 18 bundle
19 coverage 19 coverage
20 *# 20 *#
  21 +.ruby-version
@@ -4,12 +4,17 @@ @@ -4,12 +4,17 @@
4 4
5 - Update some gems ([@shingara][]) 5 - Update some gems ([@shingara][])
6 - [#492][] Improve some Pjax call ([@nfedyashev][]) 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 - Avoid to delete his own user ([@shingara][]) 8 - Avoid to delete his own user ([@shingara][])
9 - [#456][] Avoid to delete admin access of current user logged ([@shingara][]) 9 - [#456][] Avoid to delete admin access of current user logged ([@shingara][])
10 - [#253][] Refactor the Fingerprint generation ([@boblail][]) 10 - [#253][] Refactor the Fingerprint generation ([@boblail][])
11 - [#508][] Merge comments to when you merge problems ([@shingara][]) 11 - [#508][] Merge comments to when you merge problems ([@shingara][])
12 - Update the Devise Gem to the last one ([@shingara][]) 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 ### Bug Fixes 19 ### Bug Fixes
15 20
@@ -76,24 +81,28 @@ @@ -76,24 +81,28 @@
76 [#506]: https://github.com/errbit/errbit/issues/506 81 [#506]: https://github.com/errbit/errbit/issues/506
77 [#508]: https://github.com/errbit/errbit/issues/508 82 [#508]: https://github.com/errbit/errbit/issues/508
78 [#514]: https://github.com/errbit/errbit/issues/514 83 [#514]: https://github.com/errbit/errbit/issues/514
  84 +[#516]: https://github.com/errbit/errbit/issues/516
79 [#517]: https://github.com/errbit/errbit/issues/517 85 [#517]: https://github.com/errbit/errbit/issues/517
  86 +[#524]: https://github.com/errbit/errbit/issues/524
80 87
81 <!-- Contributor on Errbit Thanks to all of them --> 88 <!-- Contributor on Errbit Thanks to all of them -->
82 89
  90 +[@Gonzih]: https://github.com/Gonzih
83 [@SamSaffron]: https://github.com/SamSaffron 91 [@SamSaffron]: https://github.com/SamSaffron
84 [@adamjt]: https://github.com/adamjt 92 [@adamjt]: https://github.com/adamjt
  93 +[@aliscott]: http://github.com/aliscott
85 [@alvarobp]: https://github.com/alvarobp 94 [@alvarobp]: https://github.com/alvarobp
  95 +[@boblail]: https://github.com/boblail
86 [@chadcf]: https://github.com/chadcf 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 [@ivanyv]: https://github.com/ivanyv 97 [@ivanyv]: https://github.com/ivanyv
92 [@manuelmeurer]: https://github.com/manuelmeurer 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 [@nfedyashev]: https://github.com/nfedyashev 101 [@nfedyashev]: https://github.com/nfedyashev
95 [@parallel588]: https://github.com/parallel588 102 [@parallel588]: https://github.com/parallel588
96 -[@Gonzih]: https://github.com/Gonzih  
97 -[@boblail]: https://github.com/boblail  
98 [@roryf]: https://github.com/roryf 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
@@ -11,7 +11,6 @@ gem &#39;htmlentities&#39; @@ -11,7 +11,6 @@ gem &#39;htmlentities&#39;
11 gem 'rack-ssl', :require => 'rack/ssl' # force SSL 11 gem 'rack-ssl', :require => 'rack/ssl' # force SSL
12 12
13 gem 'useragent' 13 gem 'useragent'
14 -gem 'inherited_resources'  
15 gem 'decent_exposure' 14 gem 'decent_exposure'
16 gem 'strong_parameters' 15 gem 'strong_parameters'
17 gem 'SystemTimer', :platform => :ruby_18 16 gem 'SystemTimer', :platform => :ruby_18
@@ -45,12 +44,15 @@ gem &#39;octokit&#39; @@ -45,12 +44,15 @@ gem &#39;octokit&#39;
45 gem 'gitlab', :git => 'https://github.com/NARKOZ/gitlab.git' 44 gem 'gitlab', :git => 'https://github.com/NARKOZ/gitlab.git'
46 45
47 # Bitbucket Issues 46 # Bitbucket Issues
48 -gem 'bitbucket_rest_api' 47 +gem 'bitbucket_rest_api', :require => false
49 48
50 # Unfuddle 49 # Unfuddle
51 gem "taskmapper", "~> 0.8.0" 50 gem "taskmapper", "~> 0.8.0"
52 gem "taskmapper-unfuddle", "~> 0.7.0" 51 gem "taskmapper-unfuddle", "~> 0.7.0"
53 52
  53 +# Jira
  54 +gem 'jira-ruby', :require => 'jira'
  55 +
54 # Notification services 56 # Notification services
55 # --------------------------------------- 57 # ---------------------------------------
56 # Campfire ( We can't upgrade to 1.0 because drop support of ruby 1.8 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,11 +89,9 @@ group :development, :test do
87 gem 'rspec-rails', '~> 2.6' 89 gem 'rspec-rails', '~> 2.6'
88 gem 'webmock', :require => false 90 gem 'webmock', :require => false
89 gem 'airbrake', :require => false 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 # gem 'rpm_contrib' 95 # gem 'rpm_contrib'
96 # gem 'newrelic_rpm' 96 # gem 'newrelic_rpm'
97 gem 'quiet_assets' 97 gem 'quiet_assets'
@@ -118,7 +118,7 @@ group :test do @@ -118,7 +118,7 @@ group :test do
118 # DatabaseCleaner 1.0.0 drop the support of ruby 1.8.7 118 # DatabaseCleaner 1.0.0 drop the support of ruby 1.8.7
119 gem 'database_cleaner', '~> 0.9.0' 119 gem 'database_cleaner', '~> 0.9.0'
120 gem 'email_spec' 120 gem 'email_spec'
121 - gem 'timecop' 121 + gem 'timecop', '0.6.1' # last version compatible to ruby 1.8
122 gem 'coveralls', :require => false 122 gem 'coveralls', :require => false
123 end 123 end
124 124
@@ -40,13 +40,13 @@ GEM @@ -40,13 +40,13 @@ GEM
40 activesupport (3.2.13) 40 activesupport (3.2.13)
41 i18n (= 0.6.1) 41 i18n (= 0.6.1)
42 multi_json (~> 1.0) 42 multi_json (~> 1.0)
43 - addressable (2.3.4) 43 + addressable (2.3.5)
44 airbrake (3.1.12) 44 airbrake (3.1.12)
45 activesupport 45 activesupport
46 builder 46 builder
47 json 47 json
48 arel (3.0.2) 48 arel (3.0.2)
49 - bcrypt-ruby (3.0.1) 49 + bcrypt-ruby (3.1.1)
50 better_errors (0.9.0) 50 better_errors (0.9.0)
51 coderay (>= 1.0.0) 51 coderay (>= 1.0.0)
52 erubis (>= 2.6.6) 52 erubis (>= 2.6.6)
@@ -66,7 +66,7 @@ GEM @@ -66,7 +66,7 @@ GEM
66 callsite (0.0.11) 66 callsite (0.0.11)
67 campy (0.1.3) 67 campy (0.1.3)
68 multi_json (~> 1.0) 68 multi_json (~> 1.0)
69 - capistrano (2.15.4) 69 + capistrano (2.15.5)
70 highline 70 highline
71 net-scp (>= 1.0.0) 71 net-scp (>= 1.0.0)
72 net-sftp (>= 2.0.0) 72 net-sftp (>= 2.0.0)
@@ -90,19 +90,19 @@ GEM @@ -90,19 +90,19 @@ GEM
90 rest-client 90 rest-client
91 simplecov (>= 0.7) 91 simplecov (>= 0.7)
92 thor 92 thor
93 - crack (0.4.0) 93 + crack (0.4.1)
94 safe_yaml (~> 0.9.0) 94 safe_yaml (~> 0.9.0)
95 css_parser (1.3.4) 95 css_parser (1.3.4)
96 addressable 96 addressable
97 daemons (1.1.9) 97 daemons (1.1.9)
98 database_cleaner (0.9.1) 98 database_cleaner (0.9.1)
99 debug_inspector (0.0.2) 99 debug_inspector (0.0.2)
100 - debugger (1.6.0) 100 + debugger (1.6.1)
101 columnize (>= 0.3.1) 101 columnize (>= 0.3.1)
102 debugger-linecache (~> 1.2.0) 102 debugger-linecache (~> 1.2.0)
103 - debugger-ruby_core_source (~> 1.2.1) 103 + debugger-ruby_core_source (~> 1.2.3)
104 debugger-linecache (1.2.0) 104 debugger-linecache (1.2.0)
105 - debugger-ruby_core_source (1.2.2) 105 + debugger-ruby_core_source (1.2.3)
106 decent_exposure (2.2.0) 106 decent_exposure (2.2.0)
107 devise (2.2.4) 107 devise (2.2.4)
108 bcrypt-ruby (~> 3.0) 108 bcrypt-ruby (~> 3.0)
@@ -111,7 +111,7 @@ GEM @@ -111,7 +111,7 @@ GEM
111 warden (~> 1.2.1) 111 warden (~> 1.2.1)
112 diff-lcs (1.2.4) 112 diff-lcs (1.2.4)
113 dotenv (0.8.0) 113 dotenv (0.8.0)
114 - email_spec (1.4.0) 114 + email_spec (1.5.0)
115 launchy (~> 2.1) 115 launchy (~> 2.1)
116 mail (~> 2.2) 116 mail (~> 2.2)
117 erubis (2.7.0) 117 erubis (2.7.0)
@@ -119,10 +119,11 @@ GEM @@ -119,10 +119,11 @@ GEM
119 execjs (1.4.0) 119 execjs (1.4.0)
120 multi_json (~> 1.0) 120 multi_json (~> 1.0)
121 fabrication (1.3.2) 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 faraday_middleware (0.8.8) 124 faraday_middleware (0.8.8)
125 faraday (>= 0.7.4, < 0.9) 125 faraday (>= 0.7.4, < 0.9)
  126 + ffi (1.9.0)
126 flowdock (0.3.1) 127 flowdock (0.3.1)
127 httparty (~> 0.7) 128 httparty (~> 0.7)
128 multi_json 129 multi_json
@@ -133,7 +134,6 @@ GEM @@ -133,7 +134,6 @@ GEM
133 tilt 134 tilt
134 happymapper (0.4.0) 135 happymapper (0.4.0)
135 libxml-ruby (~> 2.0) 136 libxml-ruby (~> 2.0)
136 - has_scope (0.5.1)  
137 hashie (1.2.0) 137 hashie (1.2.0)
138 highline (1.6.19) 138 highline (1.6.19)
139 hike (1.2.3) 139 hike (1.2.3)
@@ -151,9 +151,10 @@ GEM @@ -151,9 +151,10 @@ GEM
151 multi_xml (>= 0.5.2) 151 multi_xml (>= 0.5.2)
152 httpauth (0.2.0) 152 httpauth (0.2.0)
153 i18n (0.6.1) 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 journey (1.0.4) 158 journey (1.0.4)
158 jquery-rails (2.1.4) 159 jquery-rails (2.1.4)
159 railties (>= 3.0, < 5.0) 160 railties (>= 3.0, < 5.0)
@@ -181,7 +182,7 @@ GEM @@ -181,7 +182,7 @@ GEM
181 callsite 182 callsite
182 rack-contrib 183 rack-contrib
183 railties 184 railties
184 - method_source (0.8.1) 185 + method_source (0.8.2)
185 mime-types (1.23) 186 mime-types (1.23)
186 mongo (1.8.6) 187 mongo (1.8.6)
187 bson (~> 1.8.6) 188 bson (~> 1.8.6)
@@ -197,16 +198,17 @@ GEM @@ -197,16 +198,17 @@ GEM
197 multi_json (1.7.7) 198 multi_json (1.7.7)
198 multi_xml (0.5.4) 199 multi_xml (0.5.4)
199 multipart-post (1.2.0) 200 multipart-post (1.2.0)
200 - net-scp (1.1.1) 201 + net-scp (1.1.2)
201 net-ssh (>= 2.6.5) 202 net-ssh (>= 2.6.5)
202 net-sftp (2.1.2) 203 net-sftp (2.1.2)
203 net-ssh (>= 2.6.5) 204 net-ssh (>= 2.6.5)
204 - net-ssh (2.6.7) 205 + net-ssh (2.6.8)
205 net-ssh-gateway (1.2.0) 206 net-ssh-gateway (1.2.0)
206 net-ssh (>= 2.6.5) 207 net-ssh (>= 2.6.5)
207 nokogiri (1.5.10) 208 nokogiri (1.5.10)
208 nokogiri-happymapper (0.5.7) 209 nokogiri-happymapper (0.5.7)
209 nokogiri (~> 1.5) 210 nokogiri (~> 1.5)
  211 + oauth (0.4.7)
210 oauth2 (0.8.1) 212 oauth2 (0.8.1)
211 faraday (~> 0.8) 213 faraday (~> 0.8)
212 httpauth (~> 0.1) 214 httpauth (~> 0.1)
@@ -222,7 +224,7 @@ GEM @@ -222,7 +224,7 @@ GEM
222 omniauth (1.1.4) 224 omniauth (1.1.4)
223 hashie (>= 1.2, < 3) 225 hashie (>= 1.2, < 3)
224 rack 226 rack
225 - omniauth-github (1.1.0) 227 + omniauth-github (1.1.1)
226 omniauth (~> 1.0) 228 omniauth (~> 1.0)
227 omniauth-oauth2 (~> 1.1) 229 omniauth-oauth2 (~> 1.1)
228 omniauth-oauth2 (1.1.1) 230 omniauth-oauth2 (1.1.1)
@@ -251,7 +253,7 @@ GEM @@ -251,7 +253,7 @@ GEM
251 coderay (~> 1.0.5) 253 coderay (~> 1.0.5)
252 method_source (~> 0.8) 254 method_source (~> 0.8)
253 slop (~> 3.4) 255 slop (~> 3.4)
254 - pry-rails (0.3.1) 256 + pry-rails (0.3.2)
255 pry (>= 0.9.10) 257 pry (>= 0.9.10)
256 quiet_assets (1.0.2) 258 quiet_assets (1.0.2)
257 railties (>= 3.1, < 5.0) 259 railties (>= 3.1, < 5.0)
@@ -288,22 +290,20 @@ GEM @@ -288,22 +290,20 @@ GEM
288 rdoc (3.12.2) 290 rdoc (3.12.2)
289 json (~> 1.4) 291 json (~> 1.4)
290 ref (1.0.5) 292 ref (1.0.5)
291 - responders (0.9.3)  
292 - railties (~> 3.1)  
293 rest-client (1.6.7) 293 rest-client (1.6.7)
294 mime-types (>= 1.16) 294 mime-types (>= 1.16)
295 ri_cal (0.8.8) 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 diff-lcs (>= 1.1.3, < 2.0) 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 actionpack (>= 3.0) 301 actionpack (>= 3.0)
302 activesupport (>= 3.0) 302 activesupport (>= 3.0)
303 railties (>= 3.0) 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 ruby-debug (0.10.4) 307 ruby-debug (0.10.4)
308 columnize (>= 0.1) 308 columnize (>= 0.1)
309 ruby-debug-base (~> 0.10.4.0) 309 ruby-debug-base (~> 0.10.4.0)
@@ -315,7 +315,7 @@ GEM @@ -315,7 +315,7 @@ GEM
315 rushover (0.3.0) 315 rushover (0.3.0)
316 json 316 json
317 rest-client 317 rest-client
318 - safe_yaml (0.9.3) 318 + safe_yaml (0.9.5)
319 selenium-webdriver (2.33.0) 319 selenium-webdriver (2.33.0)
320 childprocess (>= 0.2.5) 320 childprocess (>= 0.2.5)
321 multi_json (~> 1.0) 321 multi_json (~> 1.0)
@@ -326,7 +326,7 @@ GEM @@ -326,7 +326,7 @@ GEM
326 multi_json (~> 1.0) 326 multi_json (~> 1.0)
327 simplecov-html (~> 0.7.1) 327 simplecov-html (~> 0.7.1)
328 simplecov-html (0.7.1) 328 simplecov-html (0.7.1)
329 - slop (3.4.5) 329 + slop (3.4.6)
330 sprockets (2.2.2) 330 sprockets (2.2.2)
331 hike (~> 1.2) 331 hike (~> 1.2)
332 multi_json (~> 1.0) 332 multi_json (~> 1.0)
@@ -360,18 +360,18 @@ GEM @@ -360,18 +360,18 @@ GEM
360 railties (> 3.2.8, < 4.0.0) 360 railties (> 3.2.8, < 4.0.0)
361 sprockets (>= 2.0.0) 361 sprockets (>= 2.0.0)
362 tzinfo (0.3.37) 362 tzinfo (0.3.37)
363 - uglifier (2.1.1) 363 + uglifier (2.1.2)
364 execjs (>= 0.3.0) 364 execjs (>= 0.3.0)
365 multi_json (~> 1.0, >= 1.0.2) 365 multi_json (~> 1.0, >= 1.0.2)
366 - underscore-rails (1.4.4) 366 + underscore-rails (1.5.1)
367 unicorn (4.6.3) 367 unicorn (4.6.3)
368 kgio (~> 2.6) 368 kgio (~> 2.6)
369 rack 369 rack
370 raindrops (~> 0.7) 370 raindrops (~> 0.7)
371 useragent (0.6.0) 371 useragent (0.6.0)
372 - warden (1.2.1) 372 + warden (1.2.3)
373 rack (>= 1.0) 373 rack (>= 1.0)
374 - webmock (1.12.0) 374 + webmock (1.13.0)
375 addressable (>= 2.2.7) 375 addressable (>= 2.2.7)
376 crack (>= 0.3.2) 376 crack (>= 0.3.2)
377 websocket (1.0.7) 377 websocket (1.0.7)
@@ -412,7 +412,7 @@ DEPENDENCIES @@ -412,7 +412,7 @@ DEPENDENCIES
412 hoptoad_notifier (~> 2.4) 412 hoptoad_notifier (~> 2.4)
413 htmlentities 413 htmlentities
414 httparty 414 httparty
415 - inherited_resources 415 + jira-ruby
416 jquery-rails (~> 2.1.4) 416 jquery-rails (~> 2.1.4)
417 kaminari (>= 0.14.1) 417 kaminari (>= 0.14.1)
418 launchy 418 launchy
@@ -442,7 +442,7 @@ DEPENDENCIES @@ -442,7 +442,7 @@ DEPENDENCIES
442 taskmapper-unfuddle (~> 0.7.0) 442 taskmapper-unfuddle (~> 0.7.0)
443 therubyracer 443 therubyracer
444 thin 444 thin
445 - timecop 445 + timecop (= 0.6.1)
446 turbo-sprockets-rails3 446 turbo-sprockets-rails3
447 uglifier (>= 1.0.3) 447 uglifier (>= 1.0.3)
448 underscore-rails 448 underscore-rails
@@ -423,6 +423,18 @@ card_type = Defect, status = Open, priority = Essential @@ -423,6 +423,18 @@ card_type = Defect, status = Open, priority = Essential
423 * Project id the id of your project where your ticket is create 423 * Project id the id of your project where your ticket is create
424 * Milestone id the id of your milestone where your ticket is create 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 What if Errbit has an error? 439 What if Errbit has an error?
428 ---------------------------- 440 ----------------------------
app/assets/images/jira_active.png 0 → 100644

1.97 KB

app/assets/images/jira_create.png 0 → 100644

1.97 KB

app/assets/images/jira_goto.png 0 → 100644

1.97 KB

app/assets/images/jira_inactive.png 0 → 100644

1.91 KB

app/controllers/apps_controller.rb
1 -class AppsController < InheritedResources::Base 1 +class AppsController < ApplicationController
  2 +
  3 + include ProblemsSearcher
  4 +
2 before_filter :require_admin!, :except => [:index, :show] 5 before_filter :require_admin!, :except => [:index, :show]
3 before_filter :parse_email_at_notices_or_set_default, :only => [:create, :update] 6 before_filter :parse_email_at_notices_or_set_default, :only => [:create, :update]
4 before_filter :parse_notice_at_notices_or_set_default, :only => [:create, :update] 7 before_filter :parse_notice_at_notices_or_set_default, :only => [:create, :update]
5 respond_to :html 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 end 46 end
29 47
30 def create 48 def create
31 - @app = App.new(params[:app])  
32 initialize_subclassed_issue_tracker 49 initialize_subclassed_issue_tracker
33 initialize_subclassed_notification_service 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 end 57 end
36 58
37 def update 59 def update
38 - @app = resource  
39 initialize_subclassed_issue_tracker 60 initialize_subclassed_issue_tracker
40 initialize_subclassed_notification_service 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 end 68 end
43 69
44 - def new  
45 - plug_params(build_resource)  
46 - new! 70 + def edit
  71 + plug_params(app)
47 end 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 end 81 end
53 82
54 protected 83 protected
55 - def collection  
56 - @apps ||= end_of_association_chain.all.sort  
57 - end  
58 84
59 def initialize_subclassed_issue_tracker 85 def initialize_subclassed_issue_tracker
60 # set the app's issue tracker 86 # set the app's issue tracker
61 if params[:app][:issue_tracker_attributes] && tracker_type = params[:app][:issue_tracker_attributes][:type] 87 if params[:app][:issue_tracker_attributes] && tracker_type = params[:app][:issue_tracker_attributes][:type]
62 if IssueTracker.subclasses.map(&:name).concat(["IssueTracker"]).include?(tracker_type) 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 end 90 end
65 end 91 end
66 end 92 end
@@ -69,21 +95,11 @@ class AppsController &lt; InheritedResources::Base @@ -69,21 +95,11 @@ class AppsController &lt; InheritedResources::Base
69 # set the app's notification service 95 # set the app's notification service
70 if params[:app][:notification_service_attributes] && notification_type = params[:app][:notification_service_attributes][:type] 96 if params[:app][:notification_service_attributes] && notification_type = params[:app][:notification_service_attributes][:type]
71 if NotificationService.subclasses.map(&:name).concat(["NotificationService"]).include?(notification_type) 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 end 99 end
74 end 100 end
75 end 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 def plug_params app 103 def plug_params app
88 app.watchers.build if app.watchers.none? 104 app.watchers.build if app.watchers.none?
89 app.issue_tracker = IssueTracker.new unless app.issue_tracker_configured? 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 class ProblemsController < ApplicationController 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 end 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 def show 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 @notice = @notices.first 60 @notice = @notices.first
24 @comment = Comment.new 61 @comment = Comment.new
25 end 62 end
26 63
27 def create_issue 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 unless issue_creation.execute 68 unless issue_creation.execute
31 - flash[:error] = issue_creation.errors[:base].first 69 + flash[:error] = issue_creation.errors.full_messages.join(', ')
32 end 70 end
33 71
34 - redirect_to app_problem_path(@app, @problem) 72 + redirect_to app_problem_path(app, problem)
35 end 73 end
36 74
37 def unlink_issue 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 end 78 end
41 79
42 def resolve 80 def resolve
43 - @problem.resolve! 81 + problem.resolve!
44 flash[:success] = 'Great news everyone! The err has been resolved.' 82 flash[:success] = 'Great news everyone! The err has been resolved.'
45 redirect_to :back 83 redirect_to :back
46 rescue ActionController::RedirectBackError 84 rescue ActionController::RedirectBackError
47 - redirect_to app_path(@app) 85 + redirect_to app_path(app)
48 end 86 end
49 87
50 def resolve_several 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 redirect_to :back 91 redirect_to :back
54 end 92 end
55 93
56 def unresolve_several 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 redirect_to :back 97 redirect_to :back
60 end 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 def merge_several 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 else 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 end 111 end
69 redirect_to :back 112 redirect_to :back
70 end 113 end
71 114
72 def unmerge_several 115 def unmerge_several
73 - all = @selected_problems.map(&:unmerge!).flatten 116 + all = selected_problems.map(&:unmerge!).flatten
74 flash[:success] = "#{I18n.t(:n_errs_have, :count => all.length)} been unmerged." 117 flash[:success] = "#{I18n.t(:n_errs_have, :count => all.length)} been unmerged."
75 redirect_to :back 118 redirect_to :back
76 end 119 end
77 120
78 def destroy_several 121 def destroy_several
79 - nb_problem_destroy = ProblemDestroy.execute(@selected_problems) 122 + nb_problem_destroy = ProblemDestroy.execute(selected_problems)
80 flash[:notice] = "#{I18n.t(:n_errs_have, :count => nb_problem_destroy)} been deleted." 123 flash[:notice] = "#{I18n.t(:n_errs_have, :count => nb_problem_destroy)} been deleted."
81 redirect_to :back 124 redirect_to :back
82 end 125 end
83 126
84 def search 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 @selected_problems = params[:problems] || [] 129 @selected_problems = params[:problems] || []
92 @problems = @problems.page(params[:page]).per(current_user.per_page) 130 @problems = @problems.page(params[:page]).per(current_user.per_page)
93 render :content_type => 'text/javascript' 131 render :content_type => 'text/javascript'
94 end 132 end
95 133
96 protected 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 end 143 end
  144 + end
130 end 145 end
131 146
app/controllers/problems_searcher.rb 0 → 100644
@@ -0,0 +1,34 @@ @@ -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,7 +41,7 @@ module AppsHelper
41 def detect_any_apps_with_attributes 41 def detect_any_apps_with_attributes
42 @any_github_repos = @any_issue_trackers = @any_deploys = @any_bitbucket_repos = @any_notification_services = false 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 @any_github_repos ||= app.github_repo? 45 @any_github_repos ||= app.github_repo?
46 @any_bitbucket_repos ||= app.bitbucket_repo? 46 @any_bitbucket_repos ||= app.bitbucket_repo?
47 @any_issue_trackers ||= app.issue_tracker_configured? 47 @any_issue_trackers ||= app.issue_tracker_configured?
app/helpers/sort_helper.rb
1 # encoding: utf-8 1 # encoding: utf-8
2 module SortHelper 2 module SortHelper
3 - 3 +
4 def link_for_sort(name, field=nil) 4 def link_for_sort(name, field=nil)
5 field ||= name.underscore 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 url = request.path + "?sort=#{field}&order=#{order}" 8 url = request.path + "?sort=#{field}&order=#{order}"
9 options = {} 9 options = {}
10 options.merge!(:class => "current #{order}") if current 10 options.merge!(:class => "current #{order}") if current
11 link_to(name, url, options) 11 link_to(name, url, options)
12 end 12 end
13 - 13 +
14 end 14 end
app/interactors/issue_creation.rb
@@ -41,15 +41,11 @@ class IssueCreation @@ -41,15 +41,11 @@ class IssueCreation
41 end 41 end
42 42
43 def execute 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 errors.empty? 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 end 50 end
55 end 51 end
app/models/issue_tracker.rb
@@ -17,6 +17,14 @@ class IssueTracker @@ -17,6 +17,14 @@ class IssueTracker
17 field :subdomain, :type => String 17 field :subdomain, :type => String
18 field :milestone_id, :type => String 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 validate :check_params 28 validate :check_params
21 29
22 # Subclasses are responsible for overwriting this method. 30 # Subclasses are responsible for overwriting this method.
@@ -40,4 +48,15 @@ class IssueTracker @@ -40,4 +48,15 @@ class IssueTracker
40 def configured? 48 def configured?
41 project_id.present? 49 project_id.present?
42 end 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 end 62 end
app/models/issue_trackers/bitbucket_issues_tracker.rb
  1 +require 'bitbucket_rest_api'
1 class IssueTrackers::BitbucketIssuesTracker < IssueTracker 2 class IssueTrackers::BitbucketIssuesTracker < IssueTracker
2 Label = "bitbucket" 3 Label = "bitbucket"
3 Note = 'Please configure your Bitbucket repository in the <strong>BITBUCKET REPO</strong> field above.' 4 Note = 'Please configure your Bitbucket repository in the <strong>BITBUCKET REPO</strong> field above.'
app/models/issue_trackers/jira_tracker.rb 0 → 100644
@@ -0,0 +1,109 @@ @@ -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 \ No newline at end of file 110 \ No newline at end of file
app/views/apps/_fields.html.haml
1 -= errors_for @app 1 += errors_for app
2 2
3 %div.required 3 %div.required
4 = f.label :name 4 = f.label :name
app/views/apps/edit.html.haml
1 - content_for :title, 'Edit App' 1 - content_for :title, 'Edit App'
2 - content_for :action_bar do 2 - content_for :action_bar do
3 = link_to_copy_attributes_from_other_app 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 = render 'fields', :f => f 9 = render 'fields', :f => f
10 10
app/views/apps/index.html.haml
@@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
16 %th Last Deploy 16 %th Last Deploy
17 %th Errors 17 %th Errors
18 %tbody 18 %tbody
19 - - @apps.each do |app| 19 + - apps.each do |app|
20 %tr 20 %tr
21 %td.name= link_to app.name, app_path(app) 21 %td.name= link_to app.name, app_path(app)
22 - if any_github_repos? or any_bitbucket_repos? 22 - if any_github_repos? or any_bitbucket_repos?
@@ -50,7 +50,7 @@ @@ -50,7 +50,7 @@
50 - if app.problem_count > 0 50 - if app.problem_count > 0
51 - unresolved = app.unresolved_count 51 - unresolved = app.unresolved_count
52 = link_to unresolved, app_path(app), :class => (unresolved == 0 ? "resolved" : nil) 52 = link_to unresolved, app_path(app), :class => (unresolved == 0 ? "resolved" : nil)
53 - - if @apps.none? 53 + - if apps.none?
54 %tr 54 %tr
55 %td{:colspan => 3} 55 %td{:colspan => 3}
56 %em 56 %em
app/views/apps/new.html.haml
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 = link_to_copy_attributes_from_other_app 3 = link_to_copy_attributes_from_other_app
4 = link_to('cancel', apps_path, :class => 'button') 4 = link_to('cancel', apps_path, :class => 'button')
5 5
6 -= form_for @app do |f| 6 += form_for app do |f|
7 7
8 = render 'fields', :f => f 8 = render 'fields', :f => f
9 9
app/views/apps/show.atom.builder
1 atom_feed do |feed| 1 atom_feed do |feed|
2 - feed.title("Errbit notices for #{h @app.name} at #{root_url}") 2 + feed.title("Errbit notices for #{h app.name} at #{root_url}")
3 render "problems/list", :feed => feed 3 render "problems/list", :feed => feed
4 end 4 end
app/views/apps/show.html.haml
1 -- content_for :title, @app.name 1 +- content_for :title, app.name
2 - content_for :head do 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 - content_for :meta do 4 - content_for :meta do
5 %strong Errors Caught: 5 %strong Errors Caught:
6 - = @app.problems.count 6 + = app.problems.count
7 %strong Deploy Count: 7 %strong Deploy Count:
8 - = @app.deploys.count 8 + = app.deploys.count
9 %strong API Key: 9 %strong API Key:
10 - = @app.api_key 10 + = app.api_key
11 - content_for :action_bar do 11 - content_for :action_bar do
12 - if current_user.admin? 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 - else 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 %h3#watchers_toggle 19 %h3#watchers_toggle
20 Watchers 20 Watchers
21 %span.click_span (show/hide) 21 %span.click_span (show/hide)
22 #watchers_div 22 #watchers_div
23 - - if @app.notify_all_users 23 + - if app.notify_all_users
24 %table.watchers 24 %table.watchers
25 %thead 25 %thead
26 %tr 26 %tr
@@ -31,15 +31,15 @@ @@ -31,15 +31,15 @@
31 %tr 31 %tr
32 %th User or Email 32 %th User or Email
33 %tbody 33 %tbody
34 - - @app.watchers.each do |watcher| 34 + - app.watchers.each do |watcher|
35 %tr 35 %tr
36 %td= watcher.label 36 %td= watcher.label
37 - - if @app.watchers.none? 37 + - if app.watchers.none?
38 %tr 38 %tr
39 %td 39 %td
40 %em Sadly, no one is watching this app 40 %em Sadly, no one is watching this app
41 41
42 -- if @app.github_repo? 42 +- if app.github_repo?
43 %h3#repository_toggle 43 %h3#repository_toggle
44 Repository 44 Repository
45 %span.click_span (show/hide) 45 %span.click_span (show/hide)
@@ -50,13 +50,13 @@ @@ -50,13 +50,13 @@
50 %th GitHub Repo 50 %th GitHub Repo
51 %tbody 51 %tbody
52 %tr 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 %h3#deploys_toggle 55 %h3#deploys_toggle
56 Latest Deploys 56 Latest Deploys
57 %span.click_span (show/hide) 57 %span.click_span (show/hide)
58 #deploys_div 58 #deploys_div
59 - - if @deploys.any? 59 + - if deploys.any?
60 %table.deploys 60 %table.deploys
61 %thead 61 %thead
62 %tr 62 %tr
@@ -68,7 +68,7 @@ @@ -68,7 +68,7 @@
68 %th Revision 68 %th Revision
69 69
70 %tbody 70 %tbody
71 - - @deploys.each do |deploy| 71 + - deploys.each do |deploy|
72 %tr 72 %tr
73 %td.when #{deploy.created_at.to_s(:micro)} 73 %td.when #{deploy.created_at.to_s(:micro)}
74 %td.environment #{deploy.environment} 74 %td.environment #{deploy.environment}
@@ -76,20 +76,20 @@ @@ -76,20 +76,20 @@
76 %td.message #{deploy.message} 76 %td.message #{deploy.message}
77 %td.repository #{deploy.repository} 77 %td.repository #{deploy.repository}
78 %td.revision #{deploy.short_revision} 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 - else 80 - else
81 %h3 No deploys 81 %h3 No deploys
82 82
83 -- if @app.problems.any? 83 +- if app.problems.any?
84 %h3.clear Errors 84 %h3.clear Errors
85 %section 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 = text_field_tag :search, params[:search], :placeholder => 'Search for issues' 87 = text_field_tag :search, params[:search], :placeholder => 'Search for issues'
88 %br 88 %br
89 %section 89 %section
90 .problem_table{:id => 'problem_table'} 90 .problem_table{:id => 'problem_table'}
91 - = render 'problems/table', :problems => @problems 91 + = render 'problems/table', :problems => problems
92 - else 92 - else
93 %h3.clear No errs have been caught yet, make sure you setup your app 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
app/views/issue_trackers/jira_body.txt.erb 0 → 100644
@@ -0,0 +1,17 @@ @@ -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 \ No newline at end of file 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 - else 8 - else
9 - - if @app.github_repo? 9 + - if app.github_repo?
10 - if current_user.can_create_github_issues? 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 notice = problem.notices.first 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 entry.title "[#{ problem.where }] #{problem.message.to_s.truncate(27)}" 7 entry.title "[#{ problem.where }] #{problem.message.to_s.truncate(27)}"
8 entry.author do |author| 8 entry.author do |author|
9 author.name "#{ problem.app.name } [#{ problem.environment }]" 9 author.name "#{ problem.app.name } [#{ problem.environment }]"
app/views/problems/_table.html.haml
@@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
16 - problems.each do |problem| 16 - problems.each do |problem|
17 %tr{:class => problem.resolved? ? 'resolved' : 'unresolved'} 17 %tr{:class => problem.resolved? ? 'resolved' : 'unresolved'}
18 %td.select 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 %td.app 20 %td.app
21 = link_to problem.app.name, app_path(problem.app) 21 = link_to problem.app.name, app_path(problem.app)
22 - if current_page?(:controller => 'problems') 22 - if current_page?(:controller => 'problems')
app/views/problems/index.html.haml
1 - content_for :title, @all_errs ? 'All Errors' : 'Unresolved Errors' 1 - content_for :title, @all_errs ? 'All Errors' : 'Unresolved Errors'
2 - content_for :head do 2 - content_for :head do
3 = auto_discovery_link_tag :atom, problems_path(User.token_authentication_key => current_user.authentication_token, :format => "atom"), :title => "Errbit notices at #{request.host}" 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 - content_for :action_bar do 5 - content_for :action_bar do
5 - if @all_errs 6 - if @all_errs
6 = link_to 'hide resolved', problems_path, :class => 'button' 7 = link_to 'hide resolved', problems_path, :class => 'button'
7 - else 8 - else
8 = link_to 'show resolved', problems_path(:all_errs => true), :class => 'button' 9 = link_to 'show resolved', problems_path(:all_errs => true), :class => 'button'
  10 +
9 %section 11 %section
10 = form_tag search_problems_path(:all_errs => @all_errs), :method => :get, :remote => true do 12 = form_tag search_problems_path(:all_errs => @all_errs), :method => :get, :remote => true do
11 = text_field_tag :search, params[:search], :placeholder => 'Search for issues' 13 = text_field_tag :search, params[:search], :placeholder => 'Search for issues'
12 %br 14 %br
13 %section 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 - content_for :title_css_class, 'err_show' 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 - content_for :meta do 4 - content_for :meta do
5 %strong App: 5 %strong App:
6 - = link_to @app.name, @app 6 + = link_to app.name, app
7 %strong Where: 7 %strong Where:
8 - = @problem.where 8 + = problem.where
9 %br 9 %br
10 %strong Environment: 10 %strong Environment:
11 - = @problem.environment 11 + = problem.environment
12 %strong Last Notice: 12 %strong Last Notice:
13 - = @problem.last_notice_at.to_s(:precise) 13 + = problem.last_notice_at.to_s(:precise)
14 - content_for :action_bar do 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 - if current_user.authentication_token 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 %br 20 %br
21 = render "issue_tracker_links" 21 = render "issue_tracker_links"
22 22
23 -- if @problem.comments_allowed? || @problem.comments.any? 23 +- if problem.comments_allowed? || problem.comments.any?
24 - content_for :comments do 24 - content_for :comments do
25 %h3 Comments 25 %h3 Comments
26 - - @problem.comments.each do |comment| 26 + - problem.comments.each do |comment|
27 .window 27 .window
28 %table.comment 28 %table.comment
29 %tr 29 %tr
@@ -37,11 +37,11 @@ @@ -37,11 +37,11 @@
37 - else 37 - else
38 %span.comment-info 38 %span.comment-info
39 = time_ago_in_words(comment.created_at, true) << " ago by [Unknown User]" 39 = time_ago_in_words(comment.created_at, true) << " ago by [Unknown User]"
40 - %span.delete= link_to '&#10008;'.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 '&#10008;'.html_safe, [app, problem, comment], :method => :delete, :data => { :confirm => "Are you sure you don't need this comment?" }, :class => "destroy-comment"
41 %tr 41 %tr
42 %td= simple_format comment.body 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 %p Add a comment 45 %p Add a comment
46 = comment_form.text_area :body 46 = comment_form.text_area :body
47 = comment_form.submit "Save Comment" 47 = comment_form.submit "Save Comment"
@@ -63,7 +63,7 @@ @@ -63,7 +63,7 @@
63 - if @notice 63 - if @notice
64 #summary 64 #summary
65 %h3 Summary 65 %h3 Summary
66 - = render 'notices/summary', :notice => @notice, :problem => @problem 66 + = render 'notices/summary', :notice => @notice
67 67
68 #backtrace 68 #backtrace
69 %h3 Backtrace 69 %h3 Backtrace
app/views/problems/show.ics.haml
1 -= generate_problem_ical(@problem.notices.order_by(:created_at.asc)) 1 += generate_problem_ical(problem.notices.order_by(:created_at.asc))
config/config.example.yml
@@ -102,3 +102,8 @@ github_access_scope: [&#39;repo&#39;] @@ -102,3 +102,8 @@ github_access_scope: [&#39;repo&#39;]
102 # :user_name: USERNAME 102 # :user_name: USERNAME
103 # :password: PASSWORD 103 # :password: PASSWORD
104 104
  105 +
  106 +# If you want send your email by your sendmail
  107 +# sendmail_settings:
  108 +# :location: '/usr/sbin/sendmail'
  109 +# :arguments: '-i -t'
config/initializers/_load_config.rb
@@ -71,6 +71,11 @@ if smtp = Errbit::Config.smtp_settings @@ -71,6 +71,11 @@ if smtp = Errbit::Config.smtp_settings
71 ActionMailer::Base.smtp_settings = smtp 71 ActionMailer::Base.smtp_settings = smtp
72 end 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 # Set config specific values 79 # Set config specific values
75 (ActionMailer::Base.default_url_options ||= {}).tap do |default| 80 (ActionMailer::Base.default_url_options ||= {}).tap do |default|
76 default.merge! :host => Errbit::Config.host if default[:host].blank? 81 default.merge! :host => Errbit::Config.host if default[:host].blank?
config/initializers/inherited_resources.rb
@@ -1,2 +0,0 @@ @@ -1,2 +0,0 @@
1 -InheritedResources.flash_keys = [:success, :error]  
2 -  
config/locales/en.yml
@@ -2,7 +2,6 @@ @@ -2,7 +2,6 @@
2 # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 2 # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3 3
4 en: 4 en:
5 - hello: "Hello world"  
6 flash: 5 flash:
7 apps: 6 apps:
8 create: 7 create:
@@ -11,6 +10,7 @@ en: @@ -11,6 +10,7 @@ en:
11 success: "Good news everyone! '%{app_name}' was successfully updated." 10 success: "Good news everyone! '%{app_name}' was successfully updated."
12 destroy: 11 destroy:
13 success: "'%{app_name}' was successfully destroyed." 12 success: "'%{app_name}' was successfully destroyed."
  13 +
14 n_errs_have: 14 n_errs_have:
15 one: "%{count} err has" 15 one: "%{count} err has"
16 other: "%{count} errs have" 16 other: "%{count} errs have"
@@ -31,6 +31,18 @@ en: @@ -31,6 +31,18 @@ en:
31 edit_profile: 'Edit profile' 31 edit_profile: 'Edit profile'
32 32
33 controllers: 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 users: 46 users:
35 flash: 47 flash:
36 destroy: 48 destroy:
@@ -38,6 +50,13 @@ en: @@ -38,6 +50,13 @@ en:
38 error: "You can't delete yourself" 50 error: "You can't delete yourself"
39 update: 51 update:
40 success: "%{name}'s information was successfully updated." 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 devise: 61 devise:
43 registrations: 62 registrations:
public/javascripts/notifier.js
@@ -47,76 +47,112 @@ @@ -47,76 +47,112 @@
47 // OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 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 * @cfg {Error} e The error to create a stacktrace from (optional) 52 * @cfg {Error} e The error to create a stacktrace from (optional)
53 * @cfg {Boolean} guess If we should try to resolve the names of anonymous functions 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 function printStackTrace(options) { 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 printStackTrace.implementation.prototype = { 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 if (mode === 'other') { 80 if (mode === 'other') {
80 return this.other(arguments.callee); 81 return this.other(arguments.callee);
81 } else { 82 } else {
82 return this[mode](ex); 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 mode: function(e) { 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 } else if (e.stack) { 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 * Given a context, function name, and callback function, overwrite it so that it calls 139 * Given a context, function name, and callback function, overwrite it so that it calls
104 * printStackTrace() first with a callback and then runs the rest of the body. 140 * printStackTrace() first with a callback and then runs the rest of the body.
105 - * 141 + *
106 * @param {Object} context of execution (e.g. window) 142 * @param {Object} context of execution (e.g. window)
107 * @param {String} functionName to instrument 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 instrumentFunction: function(context, functionName, callback) { 146 instrumentFunction: function(context, functionName, callback) {
111 context = context || window; 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 * Given a context and function name of a function that has been 157 * Given a context and function name of a function that has been
122 * instrumented, revert the function to it's original (non-instrumented) 158 * instrumented, revert the function to it's original (non-instrumented)
@@ -128,134 +164,207 @@ printStackTrace.implementation.prototype = { @@ -128,134 +164,207 @@ printStackTrace.implementation.prototype = {
128 deinstrumentFunction: function(context, functionName) { 164 deinstrumentFunction: function(context, functionName) {
129 if (context[functionName].constructor === Function && 165 if (context[functionName].constructor === Function &&
130 context[functionName]._instrumented && 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 * Given an Error object, return a formatted Array based on Chrome's stack string. 173 * Given an Error object, return a formatted Array based on Chrome's stack string.
138 - * 174 + *
139 * @param e - Error object to inspect 175 * @param e - Error object to inspect
140 * @return Array<String> of function calls, files and line numbers 176 * @return Array<String> of function calls, files and line numbers
141 */ 177 */
142 chrome: function(e) { 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 * Given an Error object, return a formatted Array based on Firefox's stack string. 215 * Given an Error object, return a formatted Array based on Firefox's stack string.
148 - * 216 + *
149 * @param e - Error object to inspect 217 * @param e - Error object to inspect
150 * @return Array<String> of function calls, files and line numbers 218 * @return Array<String> of function calls, files and line numbers
151 */ 219 */
152 firefox: function(e) { 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 * Given an Error object, return a formatted Array based on Opera 10's stacktrace string. 260 * Given an Error object, return a formatted Array based on Opera 10's stacktrace string.
158 - * 261 + *
159 * @param e - Error object to inspect 262 * @param e - Error object to inspect
160 * @return Array<String> of function calls, files and line numbers 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 other: function(curr) { 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 fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON; 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 stack[stack.length] = fn + '(' + this.stringifyArguments(args) + ')'; 305 stack[stack.length] = fn + '(' + this.stringifyArguments(args) + ')';
205 curr = curr.caller; 306 curr = curr.caller;
206 } 307 }
207 return stack; 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 stringifyArguments: function(args) { 317 stringifyArguments: function(args) {
  318 + var result = [];
  319 + var slice = Array.prototype.slice;
217 for (var i = 0; i < args.length; ++i) { 320 for (var i = 0; i < args.length; ++i) {
218 var arg = args[i]; 321 var arg = args[i];
219 if (arg === undefined) { 322 if (arg === undefined) {
220 - args[i] = 'undefined'; 323 + result[i] = 'undefined';
221 } else if (arg === null) { 324 } else if (arg === null) {
222 - args[i] = 'null'; 325 + result[i] = 'null';
223 } else if (arg.constructor) { 326 } else if (arg.constructor) {
224 if (arg.constructor === Array) { 327 if (arg.constructor === Array) {
225 if (arg.length < 3) { 328 if (arg.length < 3) {
226 - args[i] = '[' + this.stringifyArguments(arg) + ']'; 329 + result[i] = '[' + this.stringifyArguments(arg) + ']';
227 } else { 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 } else if (arg.constructor === Object) { 333 } else if (arg.constructor === Object) {
231 - args[i] = '#object'; 334 + result[i] = '#object';
232 } else if (arg.constructor === Function) { 335 } else if (arg.constructor === Function) {
233 - args[i] = '#function'; 336 + result[i] = '#function';
234 } else if (arg.constructor === String) { 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 sourceCache: {}, 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 ajax: function(url) { 352 ajax: function(url) {
248 var req = this.createXMLHTTPObject(); 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 * Try XHR methods in order and store XHR factory. 369 * Try XHR methods in order and store XHR factory.
261 * 370 *
@@ -279,7 +388,8 @@ printStackTrace.implementation.prototype = { @@ -279,7 +388,8 @@ printStackTrace.implementation.prototype = {
279 // Use memoization to cache the factory 388 // Use memoization to cache the factory
280 this.createXMLHTTPObject = XMLHttpFactories[i]; 389 this.createXMLHTTPObject = XMLHttpFactories[i];
281 return xmlhttp; 390 return xmlhttp;
282 - } catch (e) {} 391 + } catch (e) {
  392 + }
283 } 393 }
284 }, 394 },
285 395
@@ -288,12 +398,12 @@ printStackTrace.implementation.prototype = { @@ -288,12 +398,12 @@ printStackTrace.implementation.prototype = {
288 * via Ajax). 398 * via Ajax).
289 * 399 *
290 * @param url <String> source url 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 isSameDomain: function(url) { 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 * Get source code from given URL if in the same domain. 408 * Get source code from given URL if in the same domain.
299 * 409 *
@@ -301,52 +411,78 @@ printStackTrace.implementation.prototype = { @@ -301,52 +411,78 @@ printStackTrace.implementation.prototype = {
301 * @return <Array> Array of source code lines 411 * @return <Array> Array of source code lines
302 */ 412 */
303 getSource: function(url) { 413 getSource: function(url) {
  414 + // TODO reuse source from script tags?
304 if (!(url in this.sourceCache)) { 415 if (!(url in this.sourceCache)) {
305 this.sourceCache[url] = this.ajax(url).split('\n'); 416 this.sourceCache[url] = this.ajax(url).split('\n');
306 } 417 }
307 return this.sourceCache[url]; 418 return this.sourceCache[url];
308 }, 419 },
309 -  
310 - guessFunctions: function(stack) { 420 +
  421 + guessAnonymousFunctions: function(stack) {
311 for (var i = 0; i < stack.length; ++i) { 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 return stack; 438 return stack;
323 }, 439 },
324 -  
325 - guessFunctionName: function(url, lineNo) { 440 +
  441 + guessAnonymousFunction: function(url, lineNo, charNo) {
  442 + var ret;
326 try { 443 try {
327 - return this.guessFunctionNameFromLines(lineNo, this.getSource(url)); 444 + ret = this.findFunctionName(this.getSource(url), lineNo);
328 } catch (e) { 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 for (var i = 0; i < maxLines; ++i) { 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 if (m && m[1]) { 484 if (m && m[1]) {
344 return m[1]; 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,6 +515,11 @@ printStackTrace.implementation.prototype = {
379 '<project-root>{project_root}</project-root>' + 515 '<project-root>{project_root}</project-root>' +
380 '<environment-name>{environment}</environment-name>' + 516 '<environment-name>{environment}</environment-name>' +
381 '</server-environment>' + 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 '</notice>', 523 '</notice>',
383 REQUEST_VARIABLE_GROUP_XML = '<{group_name}>{inner_content}</{group_name}>', 524 REQUEST_VARIABLE_GROUP_XML = '<{group_name}>{inner_content}</{group_name}>',
384 REQUEST_VARIABLE_XML = '<var key="{key}">{value}</var>', 525 REQUEST_VARIABLE_XML = '<var key="{key}">{value}</var>',
@@ -411,9 +552,9 @@ printStackTrace.implementation.prototype = { @@ -411,9 +552,9 @@ printStackTrace.implementation.prototype = {
411 "rootDirectory": "{project_root}", 552 "rootDirectory": "{project_root}",
412 "action": "{request_action}", 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 "environment": {}, 559 "environment": {},
419 //"session": "", 560 //"session": "",
@@ -683,6 +824,15 @@ printStackTrace.implementation.prototype = { @@ -683,6 +824,15 @@ printStackTrace.implementation.prototype = {
683 variable: 'outputFormat', 824 variable: 'outputFormat',
684 namespace: 'options' 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 methodName: 'setTrackJQ', 836 methodName: 'setTrackJQ',
687 variable: 'trackJQ', 837 variable: 'trackJQ',
688 namespace: 'options', 838 namespace: 'options',
spec/controllers/api/v1/notices_controller_spec.rb
@@ -17,7 +17,7 @@ describe Api::V1::NoticesController do @@ -17,7 +17,7 @@ describe Api::V1::NoticesController do
17 17
18 it "should return JSON if JSON is requested" do 18 it "should return JSON if JSON is requested" do
19 get :index, :auth_token => @user.authentication_token, :format => "json" 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 end 21 end
22 22
23 it "should return XML if XML is requested" do 23 it "should return XML if XML is requested" do
@@ -27,7 +27,7 @@ describe Api::V1::NoticesController do @@ -27,7 +27,7 @@ describe Api::V1::NoticesController do
27 27
28 it "should return JSON by default" do 28 it "should return JSON by default" do
29 get :index, :auth_token => @user.authentication_token 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 end 31 end
32 32
33 describe "given a date range" do 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,7 +19,7 @@ describe Api::V1::ProblemsController do
19 19
20 it "should return JSON if JSON is requested" do 20 it "should return JSON if JSON is requested" do
21 get :index, :auth_token => @user.authentication_token, :format => "json" 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 end 23 end
24 24
25 it "should return XML if XML is requested" do 25 it "should return XML if XML is requested" do
@@ -29,7 +29,7 @@ describe Api::V1::ProblemsController do @@ -29,7 +29,7 @@ describe Api::V1::ProblemsController do
29 29
30 it "should return JSON by default" do 30 it "should return JSON by default" do
31 get :index, :auth_token => @user.authentication_token 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 end 33 end
34 34
35 35
spec/controllers/apps_controller_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 describe AppsController do 3 describe AppsController do
4 - render_views  
5 4
6 it_requires_authentication 5 it_requires_authentication
7 it_requires_admin_privileges :for => {:new => :get, :edit => :get, :create => :post, :update => :put, :destroy => :delete} 6 it_requires_admin_privileges :for => {:new => :get, :edit => :get, :create => :post, :update => :put, :destroy => :delete}
8 7
9 -  
10 describe "GET /apps" do 8 describe "GET /apps" do
11 context 'when logged in as an admin' do 9 context 'when logged in as an admin' do
12 it 'finds all apps' do 10 it 'finds all apps' do
13 sign_in Fabricate(:admin) 11 sign_in Fabricate(:admin)
14 3.times { Fabricate(:app) } 12 3.times { Fabricate(:app) }
15 - apps = App.all  
16 get :index 13 get :index
17 - assigns(:apps).should == apps 14 + controller.apps.should == App.all.sort.entries
18 end 15 end
19 end 16 end
20 17
@@ -27,8 +24,8 @@ describe AppsController do @@ -27,8 +24,8 @@ describe AppsController do
27 Fabricate(:user_watcher, :user => user, :app => watched_app1) 24 Fabricate(:user_watcher, :user => user, :app => watched_app1)
28 Fabricate(:user_watcher, :user => user, :app => watched_app2) 25 Fabricate(:user_watcher, :user => user, :app => watched_app2)
29 get :index 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 end 29 end
33 end 30 end
34 end 31 end
@@ -44,7 +41,7 @@ describe AppsController do @@ -44,7 +41,7 @@ describe AppsController do
44 41
45 it 'finds the app' do 42 it 'finds the app' do
46 get :show, :id => @app.id 43 get :show, :id => @app.id
47 - assigns(:app).should == @app 44 + controller.app.should == @app
48 end 45 end
49 46
50 it "should not raise errors for app with err without notices" do 47 it "should not raise errors for app with err without notices" do
@@ -55,7 +52,6 @@ describe AppsController do @@ -55,7 +52,6 @@ describe AppsController do
55 it "should list atom feed successfully" do 52 it "should list atom feed successfully" do
56 get :show, :id => @app.id, :format => "atom" 53 get :show, :id => @app.id, :format => "atom"
57 response.should be_success 54 response.should be_success
58 - response.body.should match(@problem.message)  
59 end 55 end
60 56
61 context "pagination" do 57 context "pagination" do
@@ -65,13 +61,13 @@ describe AppsController do @@ -65,13 +61,13 @@ describe AppsController do
65 61
66 it "should have default per_page value for user" do 62 it "should have default per_page value for user" do
67 get :show, :id => @app.id 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 end 65 end
70 66
71 it "should be able to override default per_page value" do 67 it "should be able to override default per_page value" do
72 @user.update_attribute :per_page, 10 68 @user.update_attribute :per_page, 10
73 get :show, :id => @app.id 69 get :show, :id => @app.id
74 - assigns(:problems).to_a.size.should == 10 70 + controller.problems.to_a.size.should == 10
75 end 71 end
76 end 72 end
77 73
@@ -85,14 +81,14 @@ describe AppsController do @@ -85,14 +81,14 @@ describe AppsController do
85 context 'and no params' do 81 context 'and no params' do
86 it 'shows only unresolved problems' do 82 it 'shows only unresolved problems' do
87 get :show, :id => @app.id 83 get :show, :id => @app.id
88 - assigns(:problems).size.should == 1 84 + controller.problems.size.should == 1
89 end 85 end
90 end 86 end
91 87
92 context 'and all_problems=true params' do 88 context 'and all_problems=true params' do
93 it 'shows all errors' do 89 it 'shows all errors' do
94 get :show, :id => @app.id, :all_errs => true 90 get :show, :id => @app.id, :all_errs => true
95 - assigns(:problems).size.should == 2 91 + controller.problems.size.should == 2
96 end 92 end
97 end 93 end
98 end 94 end
@@ -108,35 +104,35 @@ describe AppsController do @@ -108,35 +104,35 @@ describe AppsController do
108 context 'no params' do 104 context 'no params' do
109 it 'shows errs for all environments' do 105 it 'shows errs for all environments' do
110 get :show, :id => @app.id 106 get :show, :id => @app.id
111 - assigns(:problems).size.should == 21 107 + controller.problems.size.should == 21
112 end 108 end
113 end 109 end
114 110
115 context 'environment production' do 111 context 'environment production' do
116 it 'shows errs for just production' do 112 it 'shows errs for just production' do
117 get :show, :id => @app.id, :environment => 'production' 113 get :show, :id => @app.id, :environment => 'production'
118 - assigns(:problems).size.should == 6 114 + controller.problems.size.should == 6
119 end 115 end
120 end 116 end
121 117
122 context 'environment staging' do 118 context 'environment staging' do
123 it 'shows errs for just staging' do 119 it 'shows errs for just staging' do
124 get :show, :id => @app.id, :environment => 'staging' 120 get :show, :id => @app.id, :environment => 'staging'
125 - assigns(:problems).size.should == 5 121 + controller.problems.size.should == 5
126 end 122 end
127 end 123 end
128 124
129 context 'environment development' do 125 context 'environment development' do
130 it 'shows errs for just development' do 126 it 'shows errs for just development' do
131 get :show, :id => @app.id, :environment => 'development' 127 get :show, :id => @app.id, :environment => 'development'
132 - assigns(:problems).size.should == 5 128 + controller.problems.size.should == 5
133 end 129 end
134 end 130 end
135 131
136 context 'environment test' do 132 context 'environment test' do
137 it 'shows errs for just test' do 133 it 'shows errs for just test' do
138 get :show, :id => @app.id, :environment => 'test' 134 get :show, :id => @app.id, :environment => 'test'
139 - assigns(:problems).size.should == 5 135 + controller.problems.size.should == 5
140 end 136 end
141 end 137 end
142 end 138 end
@@ -149,7 +145,7 @@ describe AppsController do @@ -149,7 +145,7 @@ describe AppsController do
149 watcher = Fabricate(:user_watcher, :app => app, :user => user) 145 watcher = Fabricate(:user_watcher, :app => app, :user => user)
150 sign_in user 146 sign_in user
151 get :show, :id => app.id 147 get :show, :id => app.id
152 - assigns(:app).should == app 148 + controller.app.should == app
153 end 149 end
154 150
155 it 'does not find the app if the user is not watching it' do 151 it 'does not find the app if the user is not watching it' do
@@ -170,19 +166,19 @@ describe AppsController do @@ -170,19 +166,19 @@ describe AppsController do
170 describe "GET /apps/new" do 166 describe "GET /apps/new" do
171 it 'instantiates a new app with a prebuilt watcher' do 167 it 'instantiates a new app with a prebuilt watcher' do
172 get :new 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 end 172 end
177 173
178 it "should copy attributes from an existing app" do 174 it "should copy attributes from an existing app" do
179 @app = Fabricate(:app, :name => "do not copy", 175 @app = Fabricate(:app, :name => "do not copy",
180 :github_repo => "test/example") 176 :github_repo => "test/example")
181 get :new, :copy_attributes_from => @app.id 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 end 182 end
187 end 183 end
188 184
@@ -190,7 +186,7 @@ describe AppsController do @@ -190,7 +186,7 @@ describe AppsController do
190 it 'finds the correct app' do 186 it 'finds the correct app' do
191 app = Fabricate(:app) 187 app = Fabricate(:app)
192 get :edit, :id => app.id 188 get :edit, :id => app.id
193 - assigns(:app).should == app 189 + controller.app.should == app
194 end 190 end
195 end 191 end
196 192
@@ -316,7 +312,6 @@ describe AppsController do @@ -316,7 +312,6 @@ describe AppsController do
316 312
317 @app.reload 313 @app.reload
318 @app.issue_tracker_configured?.should == false 314 @app.issue_tracker_configured?.should == false
319 - response.body.should match(/You must specify your/)  
320 end 315 end
321 end 316 end
322 end 317 end
@@ -326,12 +321,11 @@ describe AppsController do @@ -326,12 +321,11 @@ describe AppsController do
326 describe "DELETE /apps/:id" do 321 describe "DELETE /apps/:id" do
327 before do 322 before do
328 @app = Fabricate(:app) 323 @app = Fabricate(:app)
329 - App.stub(:find).with(@app.id).and_return(@app)  
330 end 324 end
331 325
332 it "should find the app" do 326 it "should find the app" do
333 delete :destroy, :id => @app.id 327 delete :destroy, :id => @app.id
334 - assigns(:app).should == @app 328 + controller.app.should == @app
335 end 329 end
336 330
337 it "should destroy the app" do 331 it "should destroy the app" do
spec/controllers/notices_controller_spec.rb
@@ -6,7 +6,7 @@ describe NoticesController do @@ -6,7 +6,7 @@ describe NoticesController do
6 let(:notice) { Fabricate(:notice) } 6 let(:notice) { Fabricate(:notice) }
7 let(:xml) { Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read } 7 let(:xml) { Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read }
8 let(:app) { Fabricate(:app) } 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 context 'notices API' do 11 context 'notices API' do
12 context "with all params" do 12 context "with all params" do
@@ -46,7 +46,7 @@ describe NoticesController do @@ -46,7 +46,7 @@ describe NoticesController do
46 response.body.should match(%r{<url[^>]*>(.+)#{locate_path(notice.id)}</url>}) 46 response.body.should match(%r{<url[^>]*>(.+)#{locate_path(notice.id)}</url>})
47 end 47 end
48 context "with an invalid API_KEY" do 48 context "with an invalid API_KEY" do
49 - let(:error_report) { mock(:valid? => false) } 49 + let(:error_report) { double(:valid? => false) }
50 it 'return 422' do 50 it 'return 422' do
51 post :create, :format => :xml, :data => xml 51 post :create, :format => :xml, :data => xml
52 expect(response.status).to eq 422 52 expect(response.status).to eq 422
spec/controllers/problems_controller_spec.rb
@@ -12,7 +12,7 @@ describe ProblemsController do @@ -12,7 +12,7 @@ describe ProblemsController do
12 12
13 13
14 describe "GET /problems" do 14 describe "GET /problems" do
15 - render_views 15 + #render_views
16 context 'when logged in as an admin' do 16 context 'when logged in as an admin' do
17 before(:each) do 17 before(:each) do
18 @user = Fabricate(:admin) 18 @user = Fabricate(:admin)
@@ -20,18 +20,6 @@ describe ProblemsController do @@ -20,18 +20,6 @@ describe ProblemsController do
20 @problem = Fabricate(:notice, :err => Fabricate(:err, :problem => Fabricate(:problem, :app => app, :environment => "production"))).problem 20 @problem = Fabricate(:notice, :err => Fabricate(:err, :problem => Fabricate(:problem, :app => app, :environment => "production"))).problem
21 end 21 end
22 22
23 - it "should successfully list problems" do  
24 - get :index  
25 - response.should be_success  
26 - response.body.gsub("&#8203;", "").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 context "pagination" do 23 context "pagination" do
36 before(:each) do 24 before(:each) do
37 35.times { Fabricate :err } 25 35.times { Fabricate :err }
@@ -39,13 +27,13 @@ describe ProblemsController do @@ -39,13 +27,13 @@ describe ProblemsController do
39 27
40 it "should have default per_page value for user" do 28 it "should have default per_page value for user" do
41 get :index 29 get :index
42 - assigns(:problems).to_a.size.should == User::PER_PAGE 30 + controller.problems.to_a.size.should == User::PER_PAGE
43 end 31 end
44 32
45 it "should be able to override default per_page value" do 33 it "should be able to override default per_page value" do
46 @user.update_attribute :per_page, 10 34 @user.update_attribute :per_page, 10
47 get :index 35 get :index
48 - assigns(:problems).to_a.size.should == 10 36 + controller.problems.to_a.size.should == 10
49 end 37 end
50 end 38 end
51 39
@@ -60,35 +48,35 @@ describe ProblemsController do @@ -60,35 +48,35 @@ describe ProblemsController do
60 context 'no params' do 48 context 'no params' do
61 it 'shows problems for all environments' do 49 it 'shows problems for all environments' do
62 get :index 50 get :index
63 - assigns(:problems).size.should == 21 51 + controller.problems.size.should == 21
64 end 52 end
65 end 53 end
66 54
67 context 'environment production' do 55 context 'environment production' do
68 it 'shows problems for just production' do 56 it 'shows problems for just production' do
69 get :index, :environment => 'production' 57 get :index, :environment => 'production'
70 - assigns(:problems).size.should == 6 58 + controller.problems.size.should == 6
71 end 59 end
72 end 60 end
73 61
74 context 'environment staging' do 62 context 'environment staging' do
75 it 'shows problems for just staging' do 63 it 'shows problems for just staging' do
76 get :index, :environment => 'staging' 64 get :index, :environment => 'staging'
77 - assigns(:problems).size.should == 5 65 + controller.problems.size.should == 5
78 end 66 end
79 end 67 end
80 68
81 context 'environment development' do 69 context 'environment development' do
82 it 'shows problems for just development' do 70 it 'shows problems for just development' do
83 get :index, :environment => 'development' 71 get :index, :environment => 'development'
84 - assigns(:problems).size.should == 5 72 + controller.problems.size.should == 5
85 end 73 end
86 end 74 end
87 75
88 context 'environment test' do 76 context 'environment test' do
89 it 'shows problems for just test' do 77 it 'shows problems for just test' do
90 get :index, :environment => 'test' 78 get :index, :environment => 'test'
91 - assigns(:problems).size.should == 5 79 + controller.problems.size.should == 5
92 end 80 end
93 end 81 end
94 end 82 end
@@ -101,8 +89,8 @@ describe ProblemsController do @@ -101,8 +89,8 @@ describe ProblemsController do
101 watched_unresolved_err = Fabricate(:err, :problem => Fabricate(:problem, :app => Fabricate(:user_watcher, :user => user).app, :resolved => false)) 89 watched_unresolved_err = Fabricate(:err, :problem => Fabricate(:problem, :app => Fabricate(:user_watcher, :user => user).app, :resolved => false))
102 watched_resolved_err = Fabricate(:err, :problem => Fabricate(:problem, :app => Fabricate(:user_watcher, :user => user).app, :resolved => true)) 90 watched_resolved_err = Fabricate(:err, :problem => Fabricate(:problem, :app => Fabricate(:user_watcher, :user => user).app, :resolved => true))
103 get :index 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 end 94 end
107 end 95 end
108 end 96 end
@@ -115,10 +103,10 @@ describe ProblemsController do @@ -115,10 +103,10 @@ describe ProblemsController do
115 3.times { problems << Fabricate(:err).problem } 103 3.times { problems << Fabricate(:err).problem }
116 3.times { problems << Fabricate(:err, :problem => Fabricate(:problem, :resolved => true)).problem } 104 3.times { problems << Fabricate(:err, :problem => Fabricate(:problem, :resolved => true)).problem }
117 Problem.should_receive(:ordered_by).and_return( 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 get :index, :all_errs => true 108 get :index, :all_errs => true
121 - assigns(:problems).should == problems 109 + controller.problems.should == problems
122 end 110 end
123 end 111 end
124 112
@@ -129,14 +117,14 @@ describe ProblemsController do @@ -129,14 +117,14 @@ describe ProblemsController do
129 watched_unresolved_problem = Fabricate(:problem, :app => Fabricate(:user_watcher, :user => user).app, :resolved => false) 117 watched_unresolved_problem = Fabricate(:problem, :app => Fabricate(:user_watcher, :user => user).app, :resolved => false)
130 watched_resolved_problem = Fabricate(:problem, :app => Fabricate(:user_watcher, :user => user).app, :resolved => true) 118 watched_resolved_problem = Fabricate(:problem, :app => Fabricate(:user_watcher, :user => user).app, :resolved => true)
131 get :index, :all_errs => true 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 end 122 end
135 end 123 end
136 end 124 end
137 125
138 describe "GET /apps/:app_id/problems/:id" do 126 describe "GET /apps/:app_id/problems/:id" do
139 - render_views 127 + #render_views
140 128
141 context 'when logged in as an admin' do 129 context 'when logged in as an admin' do
142 before do 130 before do
@@ -145,12 +133,12 @@ describe ProblemsController do @@ -145,12 +133,12 @@ describe ProblemsController do
145 133
146 it "finds the app" do 134 it "finds the app" do
147 get :show, :app_id => app.id, :id => err.problem.id 135 get :show, :app_id => app.id, :id => err.problem.id
148 - assigns(:app).should == app 136 + controller.app.should == app
149 end 137 end
150 138
151 it "finds the problem" do 139 it "finds the problem" do
152 get :show, :app_id => app.id, :id => err.problem.id 140 get :show, :app_id => app.id, :id => err.problem.id
153 - assigns(:problem).should == err.problem 141 + controller.problem.should == err.problem
154 end 142 end
155 143
156 it "successfully render page" do 144 it "successfully render page" do
@@ -178,32 +166,6 @@ describe ProblemsController do @@ -178,32 +166,6 @@ describe ProblemsController do
178 end 166 end
179 end 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 end 169 end
208 170
209 context 'when logged in as a user' do 171 context 'when logged in as a user' do
@@ -217,7 +179,7 @@ describe ProblemsController do @@ -217,7 +179,7 @@ describe ProblemsController do
217 179
218 it 'finds the problem if the user is watching the app' do 180 it 'finds the problem if the user is watching the app' do
219 get :show, :app_id => @watched_app.to_param, :id => @watched_err.problem.id 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 end 183 end
222 184
223 it 'raises a DocumentNotFound error if the user is not watching the app' do 185 it 'raises a DocumentNotFound error if the user is not watching the app' do
@@ -242,8 +204,8 @@ describe ProblemsController do @@ -242,8 +204,8 @@ describe ProblemsController do
242 App.should_receive(:find).with(@problem.app.id).and_return(@problem.app) 204 App.should_receive(:find).with(@problem.app.id).and_return(@problem.app)
243 @problem.app.problems.should_receive(:find).and_return(@problem.problem) 205 @problem.app.problems.should_receive(:find).and_return(@problem.problem)
244 put :resolve, :app_id => @problem.app.id, :id => @problem.problem.id 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 end 209 end
248 210
249 it "should resolve the issue" do 211 it "should resolve the issue" do
@@ -269,7 +231,7 @@ describe ProblemsController do @@ -269,7 +231,7 @@ describe ProblemsController do
269 end 231 end
270 232
271 describe "POST /apps/:app_id/problems/:id/create_issue" do 233 describe "POST /apps/:app_id/problems/:id/create_issue" do
272 - render_views 234 + #render_views
273 235
274 before(:each) do 236 before(:each) do
275 sign_in Fabricate(:admin) 237 sign_in Fabricate(:admin)
@@ -379,31 +341,25 @@ describe ProblemsController do @@ -379,31 +341,25 @@ describe ProblemsController do
379 @problem2 = Fabricate(:err, :problem => Fabricate(:problem, :resolved => false)).problem 341 @problem2 = Fabricate(:err, :problem => Fabricate(:problem, :resolved => false)).problem
380 end 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 context "POST /problems/merge_several" do 344 context "POST /problems/merge_several" do
393 it "should require at least two problems" do 345 it "should require at least two problems" do
394 post :merge_several, :problems => [@problem1.id.to_s] 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 end 348 end
397 349
398 it "should merge the problems" do 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 end 353 end
404 end 354 end
405 355
406 context "POST /problems/unmerge_several" do 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 it "should unmerge a merged problem" do 363 it "should unmerge a merged problem" do
408 merged_problem = Problem.merge!(@problem1, @problem2) 364 merged_problem = Problem.merge!(@problem1, @problem2)
409 merged_problem.errs.length.should == 2 365 merged_problem.errs.length.should == 2
@@ -412,9 +368,16 @@ describe ProblemsController do @@ -412,9 +368,16 @@ describe ProblemsController do
412 merged_problem.reload.errs.length.should == 1 368 merged_problem.reload.errs.length.should == 1
413 }.should change(Problem, :count).by(1) 369 }.should change(Problem, :count).by(1)
414 end 370 end
  371 +
415 end 372 end
416 373
417 context "POST /problems/resolve_several" do 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 it "should resolve the issue" do 381 it "should resolve the issue" do
419 post :resolve_several, :problems => [@problem2.id.to_s] 382 post :resolve_several, :problems => [@problem2.id.to_s]
420 @problem2.reload.resolved?.should == true 383 @problem2.reload.resolved?.should == true
@@ -428,10 +391,17 @@ describe ProblemsController do @@ -428,10 +391,17 @@ describe ProblemsController do
428 it "should display a message about 2 errs" do 391 it "should display a message about 2 errs" do
429 post :resolve_several, :problems => [@problem1.id.to_s, @problem2.id.to_s] 392 post :resolve_several, :problems => [@problem1.id.to_s, @problem2.id.to_s]
430 flash[:success].should match(/2 errs have been resolved/) 393 flash[:success].should match(/2 errs have been resolved/)
  394 + controller.selected_problems.should == [@problem1, @problem2]
431 end 395 end
432 end 396 end
433 397
434 context "POST /problems/unresolve_several" do 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 it "should unresolve the issue" do 405 it "should unresolve the issue" do
436 post :unresolve_several, :problems => [@problem1.id.to_s] 406 post :unresolve_several, :problems => [@problem1.id.to_s]
437 @problem1.reload.resolved?.should == false 407 @problem1.reload.resolved?.should == false
spec/controllers/users/omniauth_callbacks_controller_spec.rb
@@ -12,7 +12,7 @@ describe Users::OmniauthCallbacksController do @@ -12,7 +12,7 @@ describe Users::OmniauthCallbacksController do
12 :credentials => { :token => token } 12 :credentials => { :token => token }
13 ) 13 )
14 } 14 }
15 - @controller.stub!(:env).and_return(env) 15 + @controller.stub(:env).and_return(env)
16 end 16 end
17 17
18 context 'Linking a GitHub account to a signed in user' do 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,7 +207,7 @@ describe UsersController do
207 context "DELETE /users/:id" do 207 context "DELETE /users/:id" do
208 208
209 context "with a destroy success" do 209 context "with a destroy success" do
210 - let(:user_destroy) { mock(:destroy => true) } 210 + let(:user_destroy) { double(:destroy => true) }
211 211
212 before { 212 before {
213 UserDestroy.should_receive(:new).with(user).and_return(user_destroy) 213 UserDestroy.should_receive(:new).with(user).and_return(user_destroy)
spec/fabricators_spec.rb
@@ -1,18 +0,0 @@ @@ -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 +8,8 @@ describe ProblemDestroy do
8 context "in unit way" do 8 context "in unit way" do
9 let(:problem) { 9 let(:problem) {
10 problem = Problem.new 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 problem.stub(:delete) 13 problem.stub(:delete)
14 problem 14 problem
15 } 15 }
spec/interactors/problem_merge_spec.rb
@@ -42,7 +42,7 @@ describe ProblemMerge do @@ -42,7 +42,7 @@ describe ProblemMerge do
42 end 42 end
43 43
44 it 'update problem cache' do 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 problem_merge.merge 46 problem_merge.merge
47 end 47 end
48 48
spec/models/backtrace_spec.rb
@@ -22,8 +22,8 @@ describe Backtrace do @@ -22,8 +22,8 @@ describe Backtrace do
22 22
23 describe "find_or_create" do 23 describe "find_or_create" do
24 subject { described_class.find_or_create(attributes) } 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 before { described_class.stub(:new => backtrace) } 28 before { described_class.stub(:new => backtrace) }
29 29
@@ -37,7 +37,7 @@ describe Backtrace do @@ -37,7 +37,7 @@ describe Backtrace do
37 end 37 end
38 38
39 context "similar backtrace exist" do 39 context "similar backtrace exist" do
40 - let(:similar_backtrace) { mock :similar_backtrace } 40 + let(:similar_backtrace) { double :similar_backtrace }
41 before { backtrace.stub(:similar => similar_backtrace) } 41 before { backtrace.stub(:similar => similar_backtrace) }
42 42
43 it { should == similar_backtrace } 43 it { should == similar_backtrace }
spec/models/comment_observer_spec.rb
@@ -10,7 +10,7 @@ describe CommentObserver do @@ -10,7 +10,7 @@ describe CommentObserver do
10 it 'should send an email notification' do 10 it 'should send an email notification' do
11 Mailer.should_receive(:comment_notification). 11 Mailer.should_receive(:comment_notification).
12 with(comment). 12 with(comment).
13 - and_return(mock('email', :deliver => true)) 13 + and_return(double('email', :deliver => true))
14 comment.save 14 comment.save
15 end 15 end
16 end 16 end
spec/models/deploy_observer_spec.rb
@@ -5,7 +5,7 @@ describe DeployObserver do @@ -5,7 +5,7 @@ describe DeployObserver do
5 context 'and the app should notify on deploys' do 5 context 'and the app should notify on deploys' do
6 it 'should send an email notification' do 6 it 'should send an email notification' do
7 Mailer.should_receive(:deploy_notification). 7 Mailer.should_receive(:deploy_notification).
8 - and_return(mock('email', :deliver => true)) 8 + and_return(double('email', :deliver => true))
9 Fabricate(:deploy, :app => Fabricate(:app_with_watcher, :notify_on_deploys => true)) 9 Fabricate(:deploy, :app => Fabricate(:app_with_watcher, :notify_on_deploys => true))
10 end 10 end
11 end 11 end
spec/models/fabricators_spec.rb 0 → 100644
@@ -0,0 +1,18 @@ @@ -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,7 +9,7 @@ describe IssueTrackers::FogbugzTracker do
9 number = 123 9 number = 123
10 @issue_link = "https://#{tracker.account}.fogbugz.com/default.asp?#{number}" 10 @issue_link = "https://#{tracker.account}.fogbugz.com/default.asp?#{number}"
11 response = "<response><token>12345</token><case><ixBug>123</ixBug></case></response>" 11 response = "<response><token>12345</token><case><ixBug>123</ixBug></case></response>"
12 - http_mock = mock 12 + http_mock = double
13 http_mock.should_receive(:new).and_return(http_mock) 13 http_mock.should_receive(:new).and_return(http_mock)
14 http_mock.should_receive(:request).twice.and_return(response) 14 http_mock.should_receive(:request).twice.and_return(response)
15 Fogbugz.adapter[:http] = http_mock 15 Fogbugz.adapter[:http] = http_mock
spec/models/notice_observer_spec.rb
@@ -18,7 +18,7 @@ describe NoticeObserver do @@ -18,7 +18,7 @@ describe NoticeObserver do
18 it "sends an email notification after #{threshold} notice(s)" do 18 it "sends an email notification after #{threshold} notice(s)" do
19 @err.problem.stub(:notices_count).and_return(threshold) 19 @err.problem.stub(:notices_count).and_return(threshold)
20 Mailer.should_receive(:err_notification). 20 Mailer.should_receive(:err_notification).
21 - and_return(mock('email', :deliver => true)) 21 + and_return(double('email', :deliver => true))
22 Fabricate(:notice, :err => @err) 22 Fabricate(:notice, :err => @err)
23 end 23 end
24 end 24 end
@@ -38,7 +38,7 @@ describe NoticeObserver do @@ -38,7 +38,7 @@ describe NoticeObserver do
38 it "should send email notification after 1 notice since an error has been resolved" do 38 it "should send email notification after 1 notice since an error has been resolved" do
39 @err.problem.resolve! 39 @err.problem.resolve!
40 Mailer.should_receive(:err_notification). 40 Mailer.should_receive(:err_notification).
41 - and_return(mock('email', :deliver => true)) 41 + and_return(double('email', :deliver => true))
42 Fabricate(:notice, :err => @err) 42 Fabricate(:notice, :err => @err)
43 end 43 end
44 end 44 end
@@ -103,7 +103,7 @@ describe NoticeObserver do @@ -103,7 +103,7 @@ describe NoticeObserver do
103 Fabricate(:notice, :err => err) 103 Fabricate(:notice, :err => err)
104 end 104 end
105 end 105 end
106 - 106 +
107 describe "should send a notification at desired intervals" do 107 describe "should send a notification at desired intervals" do
108 let(:app) { Fabricate(:app, :email_at_notices => [1], :notification_service => Fabricate(:campfire_notification_service, :notify_at_notices => [1,2]))} 108 let(:app) { Fabricate(:app, :email_at_notices => [1], :notification_service => Fabricate(:campfire_notification_service, :notify_at_notices => [1,2]))}
109 let(:backtrace) { Fabricate(:backtrace) } 109 let(:backtrace) { Fabricate(:backtrace) }
spec/models/notification_service/campfire_service_spec.rb
@@ -8,7 +8,7 @@ describe NotificationService::CampfireService do @@ -8,7 +8,7 @@ describe NotificationService::CampfireService do
8 problem = notice.problem 8 problem = notice.problem
9 9
10 #campy stubbing 10 #campy stubbing
11 - campy = mock('CampfireService') 11 + campy = double('CampfireService')
12 Campy::Room.stub(:new).and_return(campy) 12 Campy::Room.stub(:new).and_return(campy)
13 campy.stub(:speak) { true } 13 campy.stub(:speak) { true }
14 14
spec/models/notification_service/gtalk_service_spec.rb
@@ -9,7 +9,7 @@ describe NotificationService::GtalkService do @@ -9,7 +9,7 @@ describe NotificationService::GtalkService do
9 problem = notice.problem 9 problem = notice.problem
10 10
11 #gtalk stubbing 11 #gtalk stubbing
12 - gtalk = mock('GtalkService') 12 + gtalk = double('GtalkService')
13 jid = double("jid") 13 jid = double("jid")
14 message = double("message") 14 message = double("message")
15 Jabber::JID.should_receive(:new).with(notification_service.subdomain).and_return(jid) 15 Jabber::JID.should_receive(:new).with(notification_service.subdomain).and_return(jid)
@@ -19,13 +19,13 @@ describe NotificationService::GtalkService do @@ -19,13 +19,13 @@ describe NotificationService::GtalkService do
19 message_value = """#{problem.app.name.to_s} 19 message_value = """#{problem.app.name.to_s}
20 http://#{Errbit::Config.host}/apps/#{problem.app.id.to_s} 20 http://#{Errbit::Config.host}/apps/#{problem.app.id.to_s}
21 #{notification_service.notification_description problem}""" 21 #{notification_service.notification_description problem}"""
22 - 22 +
23 Jabber::Message.should_receive(:new).with(notification_service.user_id, message_value).and_return(message) 23 Jabber::Message.should_receive(:new).with(notification_service.user_id, message_value).and_return(message)
24 Jabber::Message.should_receive(:new).with(notification_service.room_id, message_value).and_return(message) 24 Jabber::Message.should_receive(:new).with(notification_service.room_id, message_value).and_return(message)
25 - 25 +
26 Jabber::MUC::SimpleMUCClient.should_receive(:new).and_return(gtalk) 26 Jabber::MUC::SimpleMUCClient.should_receive(:new).and_return(gtalk)
27 gtalk.should_receive(:join).with(notification_service.room_id + "/errbit") 27 gtalk.should_receive(:join).with(notification_service.room_id + "/errbit")
28 - 28 +
29 #assert 29 #assert
30 gtalk.should_receive(:send).exactly(2).times.with(message) 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,7 +43,7 @@ http://#{Errbit::Config.host}/apps/#{@problem.app.id.to_s}
43 #{@notification_service.notification_description @problem}""" 43 #{@notification_service.notification_description @problem}"""
44 44
45 # gtalk stubbing 45 # gtalk stubbing
46 - @gtalk = mock('GtalkService') 46 + @gtalk = double('GtalkService')
47 @gtalk.should_receive(:connect) 47 @gtalk.should_receive(:connect)
48 @gtalk.should_receive(:auth) 48 @gtalk.should_receive(:auth)
49 jid = double("jid") 49 jid = double("jid")
@@ -86,7 +86,7 @@ http://#{Errbit::Config.host}/apps/#{@problem.app.id.to_s} @@ -86,7 +86,7 @@ http://#{Errbit::Config.host}/apps/#{@problem.app.id.to_s}
86 @notification_service.room_id = "" 86 @notification_service.room_id = ""
87 @notification_service.create_notification(@problem) 87 @notification_service.create_notification(@problem)
88 end 88 end
89 - 89 +
90 end 90 end
91 91
92 it "it should send a notification to room only" do 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,7 +97,7 @@ http://#{Errbit::Config.host}/apps/#{@problem.app.id.to_s}
97 problem = notice.problem 97 problem = notice.problem
98 98
99 #gtalk stubbing 99 #gtalk stubbing
100 - gtalk = mock('GtalkService') 100 + gtalk = double('GtalkService')
101 jid = double("jid") 101 jid = double("jid")
102 message = double("message") 102 message = double("message")
103 Jabber::JID.should_receive(:new).with(notification_service.subdomain).and_return(jid) 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,11 +107,11 @@ http://#{Errbit::Config.host}/apps/#{@problem.app.id.to_s}
107 message_value = """#{problem.app.name.to_s} 107 message_value = """#{problem.app.name.to_s}
108 http://#{Errbit::Config.host}/apps/#{problem.app.id.to_s} 108 http://#{Errbit::Config.host}/apps/#{problem.app.id.to_s}
109 #{notification_service.notification_description problem}""" 109 #{notification_service.notification_description problem}"""
110 - 110 +
111 Jabber::Message.should_receive(:new).with(notification_service.room_id, message_value).and_return(message) 111 Jabber::Message.should_receive(:new).with(notification_service.room_id, message_value).and_return(message)
112 - 112 +
113 Jabber::MUC::SimpleMUCClient.should_receive(:new).and_return(gtalk) 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 notification_service.user_id = "" 116 notification_service.user_id = ""
117 117
spec/models/notification_service/hoiio_service_spec.rb
@@ -8,7 +8,7 @@ describe NotificationService::HoiioService do @@ -8,7 +8,7 @@ describe NotificationService::HoiioService do
8 problem = notice.problem 8 problem = notice.problem
9 9
10 # hoi stubbing 10 # hoi stubbing
11 - sms = mock('HoiioService') 11 + sms = double('HoiioService')
12 Hoi::SMS.stub(:new).and_return(sms) 12 Hoi::SMS.stub(:new).and_return(sms)
13 sms.stub(:send) { true } 13 sms.stub(:send) { true }
14 14
spec/models/notification_service/pushover_service_spec.rb
@@ -8,7 +8,7 @@ describe NotificationService::PushoverService do @@ -8,7 +8,7 @@ describe NotificationService::PushoverService do
8 problem = notice.problem 8 problem = notice.problem
9 9
10 # hoi stubbing 10 # hoi stubbing
11 - notification = mock('PushoverService') 11 + notification = double('PushoverService')
12 Rushover::Client.stub(:new).and_return(notification) 12 Rushover::Client.stub(:new).and_return(notification)
13 notification.stub(:notify) { true } 13 notification.stub(:notify) { true }
14 14
spec/models/problem_spec.rb
@@ -123,12 +123,12 @@ describe Problem do @@ -123,12 +123,12 @@ describe Problem do
123 it "should throw an err if it's not successful" do 123 it "should throw an err if it's not successful" do
124 problem = Fabricate(:problem) 124 problem = Fabricate(:problem)
125 problem.should_not be_resolved 125 problem.should_not be_resolved
126 - problem.stub!(:valid?).and_return(false) 126 + problem.stub(:valid?).and_return(false)
127 ## update_attributes not test #valid? but #errors.any? 127 ## update_attributes not test #valid? but #errors.any?
128 # https://github.com/mongoid/mongoid/blob/master/lib/mongoid/persistence.rb#L137 128 # https://github.com/mongoid/mongoid/blob/master/lib/mongoid/persistence.rb#L137
129 er = ActiveModel::Errors.new(problem) 129 er = ActiveModel::Errors.new(problem)
130 er.add_on_blank(:resolved) 130 er.add_on_blank(:resolved)
131 - problem.stub!(:errors).and_return(er) 131 + problem.stub(:errors).and_return(er)
132 problem.should_not be_valid 132 problem.should_not be_valid
133 lambda { 133 lambda {
134 problem.resolve! 134 problem.resolve!
@@ -152,7 +152,6 @@ describe Problem do @@ -152,7 +152,6 @@ describe Problem do
152 end 152 end
153 end 153 end
154 154
155 -  
156 context "Scopes" do 155 context "Scopes" do
157 context "resolved" do 156 context "resolved" do
158 it 'only finds resolved Problems' do 157 it 'only finds resolved Problems' do
spec/views/apps/edit.html.haml_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 describe "apps/edit.html.haml" do 3 describe "apps/edit.html.haml" do
  4 + let(:app) { stub_model(App) }
4 before do 5 before do
5 - app = stub_model(App)  
6 - assign :app, app 6 + view.stub(:app).and_return(app)
7 controller.stub(:current_user) { stub_model(User) } 7 controller.stub(:current_user) { stub_model(User) }
8 end 8 end
9 9
@@ -19,5 +19,19 @@ describe &quot;apps/edit.html.haml&quot; do @@ -19,5 +19,19 @@ describe &quot;apps/edit.html.haml&quot; do
19 end 19 end
20 20
21 end 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 end 36 end
23 37
spec/views/apps/index.html.haml_spec.rb
@@ -3,7 +3,7 @@ require &#39;spec_helper&#39; @@ -3,7 +3,7 @@ require &#39;spec_helper&#39;
3 describe "apps/index.html.haml" do 3 describe "apps/index.html.haml" do
4 before do 4 before do
5 app = stub_model(App, :deploys => [stub_model(Deploy, :created_at => Time.now, :revision => "123456789abcdef")]) 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 controller.stub(:current_user) { stub_model(User) } 7 controller.stub(:current_user) { stub_model(User) }
8 end 8 end
9 9
spec/views/apps/new.html.haml_spec.rb 0 → 100644
@@ -0,0 +1,37 @@ @@ -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 +
spec/views/apps/show.atom.builder_spec.rb 0 → 100644
@@ -0,0 +1,21 @@ @@ -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
spec/views/apps/show.html.haml_spec.rb 0 → 100644
@@ -0,0 +1,32 @@ @@ -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 +
spec/views/problems/index.atom.builder_spec.rb 0 → 100644
@@ -0,0 +1,14 @@ @@ -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
spec/views/problems/index.html.haml_spec.rb 0 → 100644
@@ -0,0 +1,25 @@ @@ -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 require 'spec_helper' 1 require 'spec_helper'
2 2
3 describe "problems/show.html.haml" do 3 describe "problems/show.html.haml" do
  4 + let(:problem) { Fabricate(:problem) }
  5 + let(:comment) { Fabricate(:comment) }
  6 +
4 before do 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 assign :comment, comment 11 assign :comment, comment
9 - assign :app, problem.app  
10 assign :notices, problem.notices.page(1).per(1) 12 assign :notices, problem.notices.page(1).per(1)
11 assign :notice, problem.notices.first 13 assign :notice, problem.notices.first
  14 +
12 controller.stub(:current_user) { Fabricate(:user) } 15 controller.stub(:current_user) { Fabricate(:user) }
13 end 16 end
14 17
15 def with_issue_tracker(tracker, problem) 18 def with_issue_tracker(tracker, problem)
16 problem.app.issue_tracker = tracker.new :api_token => "token token token", :project_id => "1234" 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 end 22 end
20 23
21 describe "content_for :action_bar" do 24 describe "content_for :action_bar" do
@@ -54,8 +57,8 @@ describe &quot;problems/show.html.haml&quot; do @@ -54,8 +57,8 @@ describe &quot;problems/show.html.haml&quot; do
54 it "should link 'up' to app_problems_path if HTTP_REFERER isn't set'" do 57 it "should link 'up' to app_problems_path if HTTP_REFERER isn't set'" do
55 controller.request.env['HTTP_REFERER'] = nil 58 controller.request.env['HTTP_REFERER'] = nil
56 problem = Fabricate(:problem_with_comments) 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 render 62 render
60 63
61 action_bar.should have_selector("span a.up[href='#{app_problems_path(problem.app)}']", :text => 'up') 64 action_bar.should have_selector("span a.up[href='#{app_problems_path(problem.app)}']", :text => 'up')
@@ -67,8 +70,8 @@ describe &quot;problems/show.html.haml&quot; do @@ -67,8 +70,8 @@ describe &quot;problems/show.html.haml&quot; do
67 controller.stub(:current_user) { user } 70 controller.stub(:current_user) { user }
68 71
69 problem = Fabricate(:problem_with_comments, :app => Fabricate(:app, :github_repo => "test_user/test_repo")) 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 render 75 render
73 76
74 action_bar.should have_selector("span a.github_create.create-issue", :text => 'create issue') 77 action_bar.should have_selector("span a.github_create.create-issue", :text => 'create issue')
@@ -77,12 +80,54 @@ describe &quot;problems/show.html.haml&quot; do @@ -77,12 +80,54 @@ describe &quot;problems/show.html.haml&quot; do
77 it 'should allow creating issue for github if application has a github tracker' do 80 it 'should allow creating issue for github if application has a github tracker' do
78 problem = Fabricate(:problem_with_comments, :app => Fabricate(:app, :github_repo => "test_user/test_repo")) 81 problem = Fabricate(:problem_with_comments, :app => Fabricate(:app, :github_repo => "test_user/test_repo"))
79 with_issue_tracker(GithubIssuesTracker, problem) 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 render 85 render
83 86
84 action_bar.should have_selector("span a.github_create.create-issue", :text => 'create issue') 87 action_bar.should have_selector("span a.github_create.create-issue", :text => 'create issue')
85 end 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 end 131 end
87 end 132 end
88 133
@@ -94,8 +139,8 @@ describe &quot;problems/show.html.haml&quot; do @@ -94,8 +139,8 @@ describe &quot;problems/show.html.haml&quot; do
94 139
95 it 'should display comments and new comment form when no issue tracker' do 140 it 'should display comments and new comment form when no issue tracker' do
96 problem = Fabricate(:problem_with_comments) 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 render 144 render
100 145
101 view.content_for(:comments).should include('Test comment') 146 view.content_for(:comments).should include('Test comment')
spec/views/problems/show.ics.haml_spec.rb 0 → 100644
@@ -0,0 +1,14 @@ @@ -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