Commit 76db16871d2e726d4bf1e31b935adb6ef98a5a2f

Authored by Stephen Crosby
2 parents 72a3058f aaf30eff
Exists in master and in 1 other branch production

Merge branch 'master' into features/extract_issue_tracker

Conflicts:
	Gemfile
	Gemfile.lock
	app/controllers/apps_controller.rb
	app/models/issue_trackers/github_issues_tracker.rb
	app/views/apps/_fields.html.haml
	app/views/apps/edit.html.haml
	spec/models/issue_trackers/github_issues_tracker_spec.rb
@@ -13,12 +13,30 @@ @@ -13,12 +13,30 @@
13 use ([@shingara][]) 13 use ([@shingara][])
14 - [#588][] Change documentation to see Airbrake gem instead of 14 - [#588][] Change documentation to see Airbrake gem instead of
15 hoptoad_nofier ([@ugisozols][]) 15 hoptoad_nofier ([@ugisozols][])
  16 +- [#592][] Avoid taskmapper dependencies ([@arthurnn][])
  17 +- [#597][] Improve fingerprinting if only change by object memory adress
  18 + ([@1st8][])
  19 +- [#603][] Improve the page rendering of App list ([@arthurnn][])
  20 +- [#606][] Add ability to deploy on Cloud 66 ([@pkallberg][])
  21 +- [#613][] Improve text about delete thing ([@mildavw][])
  22 +- [#616][] Improvement about our test suite ([@durrantm][])
  23 +- [#620][] Add App version information in email notification
  24 + ([@soberstadt][])
  25 +- [#624][] Allow configuration of port if errbit not on 80
  26 + ([@rsutphin][])
  27 +- [#617][] Allow GET request on notice API ([@soberstadt][])
  28 +- [#618][] Improve the stack information in JS notifier ([@soberstadt][])
  29 +- [#619][] Handle the App version information in the API ([@soberstadt][])
16 30
17 ### Bug Fixes 31 ### Bug Fixes
18 32
19 - [#565][] Treat request URL as CDATA so query string params don't 33 - [#565][] Treat request URL as CDATA so query string params don't
20 result in invalid XML ([@mildavw][]) 34 result in invalid XML ([@mildavw][])
21 - [#594][] Dont use ^ and $ on email_regexp([@arthurnn][]) 35 - [#594][] Dont use ^ and $ on email_regexp([@arthurnn][])
  36 +- [#598][] Fix issue about gitlab integration ([@jozefvaclavik][])
  37 +- [#609][] Fix issue about page system on search error ([@zhekanax][])
  38 +- [#611][] Fix issue about select all error in search page ([@zhekanax][])
  39 +- [#615][] Fix some LDAP information in README ([@felixbuenemann][])
22 40
23 41
24 [@arthurnn]: https://github.com/arthurnn 42 [@arthurnn]: https://github.com/arthurnn
@@ -26,13 +44,36 @@ @@ -26,13 +44,36 @@
26 [@numbata]: https://github.com/numbata 44 [@numbata]: https://github.com/numbata
27 [@shingara]: https://github.com/shingara 45 [@shingara]: https://github.com/shingara
28 [@ugisozols]: https://github.com/ugisozols 46 [@ugisozols]: https://github.com/ugisozols
  47 +[@1st8]: https://github.com/1st8
  48 +[@jozefvaclavik]: https://github.com/jozefvaclavik
  49 +[@zhekanax]: https://github.com/zhekanax
  50 +[@pkallberg]: https://github.com/pkallberg
  51 +[@durrantm]: https://github.com/durrantm
  52 +[@felixbuenemann]: https://github.com/felixbuenemann
  53 +[@soberstadt]: https://github.com/soberstadt
  54 +[@rsutphin]: https://github.com/rsutphin
29 55
30 [#289]: https://github.com/errbit/errbit/issues/289 56 [#289]: https://github.com/errbit/errbit/issues/289
31 [#515]: https://github.com/errbit/errbit/issues/515 57 [#515]: https://github.com/errbit/errbit/issues/515
32 [#547]: https://github.com/errbit/errbit/issues/547 58 [#547]: https://github.com/errbit/errbit/issues/547
33 [#565]: https://github.com/errbit/errbit/issues/565 59 [#565]: https://github.com/errbit/errbit/issues/565
34 [#588]: https://github.com/errbit/errbit/issues/588 60 [#588]: https://github.com/errbit/errbit/issues/588
  61 +[#592]: https://github.com/errbit/errbit/pull/592
35 [#594]: https://github.com/errbit/errbit/pull/594 62 [#594]: https://github.com/errbit/errbit/pull/594
  63 +[#597]: https://github.com/errbit/errbit/pull/597
  64 +[#598]: https://github.com/errbit/errbit/pull/598
  65 +[#603]: https://github.com/errbit/errbit/pull/603
  66 +[#606]: https://github.com/errbit/errbit/pull/606
  67 +[#609]: https://github.com/errbit/errbit/pull/609
  68 +[#611]: https://github.com/errbit/errbit/pull/611
  69 +[#613]: https://github.com/errbit/errbit/pull/613
  70 +[#616]: https://github.com/errbit/errbit/pull/616
  71 +[#615]: https://github.com/errbit/errbit/pull/615
  72 +[#617]: https://github.com/errbit/errbit/pull/617
  73 +[#618]: https://github.com/errbit/errbit/pull/618
  74 +[#619]: https://github.com/errbit/errbit/pull/619
  75 +[#620]: https://github.com/errbit/errbit/pull/620
  76 +[#624]: https://github.com/errbit/errbit/pull/624
36 77
37 ## 0.2.1 - Not released Yet 78 ## 0.2.1 - Not released Yet
38 79
CONTRIBUTORS.md
@@ -9,6 +9,14 @@ @@ -9,6 +9,14 @@
9 - [@shingara][] 9 - [@shingara][]
10 - [@simi][] 10 - [@simi][]
11 - [@ugisozols][] 11 - [@ugisozols][]
  12 +- [@1st8][]
  13 +- [@jozefvaclavik][]
  14 +- [@zhekanax][]
  15 +- [@pkallberg][]
  16 +- [@durrantm][]
  17 +- [@felixbuenemann][]
  18 +- [@soberstadt][]
  19 +- [@rsutphin][]
12 20
13 [@arthurnn]: https://github.com/arthurnn 21 [@arthurnn]: https://github.com/arthurnn
14 [@emgiezet]: https://github.com/emgiezet 22 [@emgiezet]: https://github.com/emgiezet
@@ -19,6 +27,13 @@ @@ -19,6 +27,13 @@
19 [@shingara]: https://github.com/shingara 27 [@shingara]: https://github.com/shingara
20 [@simi]: https://github.com/simi 28 [@simi]: https://github.com/simi
21 [@ugisozols]: https://github.com/ugisozols 29 [@ugisozols]: https://github.com/ugisozols
  30 +[@1st8]: https://github.com/1st8
  31 +[@jozefvaclavik]: https://github.com/jozefvaclavik
  32 +[@zhekanax]: https://github.com/zhekanax
  33 +[@pkallberg]: https://github.com/pkallberg
  34 +[@durrantm]: https://github.com/durrantm
  35 +[@felixbuenemann]: https://github.com/felixbuenemann
  36 +
22 37
23 38
24 ## 0.2.1 - Not released yet 39 ## 0.2.1 - Not released yet
@@ -92,3 +107,5 @@ @@ -92,3 +107,5 @@
92 [@tvdeyen]: https://github.com/tvdeyen 107 [@tvdeyen]: https://github.com/tvdeyen
93 [@williamn]: https://github.com/williamn 108 [@williamn]: https://github.com/williamn
94 [@xenji]: https://github.com/xenji 109 [@xenji]: https://github.com/xenji
  110 +[@soberstadt]: https://github.com/soberstadt
  111 +[@rsutphin]: https://github.com/rsutphin
1 source 'https://rubygems.org' 1 source 'https://rubygems.org'
2 2
3 -RAILS_VERSION = '~> 3.2.15' 3 +RAILS_VERSION = '~> 3.2.18'
4 4
5 gem 'actionmailer', RAILS_VERSION 5 gem 'actionmailer', RAILS_VERSION
6 gem 'actionpack', RAILS_VERSION 6 gem 'actionpack', RAILS_VERSION
@@ -25,7 +25,6 @@ gem 'rails_autolink' @@ -25,7 +25,6 @@ gem 'rails_autolink'
25 # Please don't update hoptoad_notifier to airbrake. 25 # Please don't update hoptoad_notifier to airbrake.
26 # It's for internal use only, and we monkeypatch certain methods 26 # It's for internal use only, and we monkeypatch certain methods
27 gem 'hoptoad_notifier', "~> 2.4" 27 gem 'hoptoad_notifier', "~> 2.4"
28 -gem 'draper', :require => false  
29 28
30 gem 'errbit_plugin', 29 gem 'errbit_plugin',
31 :git => 'https://github.com/errbit/errbit_plugin.git' 30 :git => 'https://github.com/errbit/errbit_plugin.git'
@@ -62,7 +61,6 @@ group :development, :test do @@ -62,7 +61,6 @@ group :development, :test do
62 gem 'rspec-rails' 61 gem 'rspec-rails'
63 gem 'webmock', :require => false 62 gem 'webmock', :require => false
64 gem 'airbrake', :require => false 63 gem 'airbrake', :require => false
65 - gem 'debugger', :platform => :mri_19  
66 gem 'pry-rails' 64 gem 'pry-rails'
67 # gem 'rpm_contrib' 65 # gem 'rpm_contrib'
68 # gem 'newrelic_rpm' 66 # gem 'newrelic_rpm'
@@ -84,10 +82,9 @@ group :development do @@ -84,10 +82,9 @@ group :development do
84 end 82 end
85 83
86 group :test do 84 group :test do
87 - gem 'capybara', :require => false  
88 - gem 'poltergeist', :require => false  
89 - gem 'launchy', :require => false  
90 - gem 'database_cleaner', :require => false 85 + gem 'capybara'
  86 + gem 'launchy'
  87 + gem 'database_cleaner'
91 gem 'email_spec' 88 gem 'email_spec'
92 gem 'timecop' 89 gem 'timecop'
93 gem 'coveralls', :require => false 90 gem 'coveralls', :require => false
@@ -98,6 +95,7 @@ group :heroku, :production do @@ -98,6 +95,7 @@ group :heroku, :production do
98 gem 'unicorn', :require => false 95 gem 'unicorn', :require => false
99 end 96 end
100 97
  98 +
101 # Gems used only for assets and not required 99 # Gems used only for assets and not required
102 # in production environments by default. 100 # in production environments by default.
103 group :assets do 101 group :assets do
@@ -15,16 +15,16 @@ GIT @@ -15,16 +15,16 @@ GIT
15 GEM 15 GEM
16 remote: https://rubygems.org/ 16 remote: https://rubygems.org/
17 specs: 17 specs:
18 - actionmailer (3.2.16)  
19 - actionpack (= 3.2.16) 18 + actionmailer (3.2.18)
  19 + actionpack (= 3.2.18)
20 mail (~> 2.5.4) 20 mail (~> 2.5.4)
21 actionmailer_inline_css (1.5.3) 21 actionmailer_inline_css (1.5.3)
22 actionmailer (>= 3.0.0) 22 actionmailer (>= 3.0.0)
23 nokogiri (>= 1.4.4) 23 nokogiri (>= 1.4.4)
24 premailer (>= 1.7.1) 24 premailer (>= 1.7.1)
25 - actionpack (3.2.16)  
26 - activemodel (= 3.2.16)  
27 - activesupport (= 3.2.16) 25 + actionpack (3.2.18)
  26 + activemodel (= 3.2.18)
  27 + activesupport (= 3.2.18)
28 builder (~> 3.0.0) 28 builder (~> 3.0.0)
29 erubis (~> 2.7.0) 29 erubis (~> 2.7.0)
30 journey (~> 1.0.4) 30 journey (~> 1.0.4)
@@ -32,18 +32,18 @@ GEM @@ -32,18 +32,18 @@ GEM
32 rack-cache (~> 1.2) 32 rack-cache (~> 1.2)
33 rack-test (~> 0.6.1) 33 rack-test (~> 0.6.1)
34 sprockets (~> 2.2.1) 34 sprockets (~> 2.2.1)
35 - activemodel (3.2.16)  
36 - activesupport (= 3.2.16) 35 + activemodel (3.2.18)
  36 + activesupport (= 3.2.18)
37 builder (~> 3.0.0) 37 builder (~> 3.0.0)
38 - activerecord (3.2.16)  
39 - activemodel (= 3.2.16)  
40 - activesupport (= 3.2.16) 38 + activerecord (3.2.18)
  39 + activemodel (= 3.2.18)
  40 + activesupport (= 3.2.18)
41 arel (~> 3.0.2) 41 arel (~> 3.0.2)
42 tzinfo (~> 0.3.29) 42 tzinfo (~> 0.3.29)
43 - activeresource (3.2.16)  
44 - activemodel (= 3.2.16)  
45 - activesupport (= 3.2.16)  
46 - activesupport (3.2.16) 43 + activeresource (3.2.18)
  44 + activemodel (= 3.2.18)
  45 + activesupport (= 3.2.18)
  46 + activesupport (3.2.18)
47 i18n (~> 0.6, >= 0.6.4) 47 i18n (~> 0.6, >= 0.6.4)
48 multi_json (~> 1.0) 48 multi_json (~> 1.0)
49 addressable (2.3.5) 49 addressable (2.3.5)
@@ -73,9 +73,7 @@ GEM @@ -73,9 +73,7 @@ GEM
73 rack (>= 1.0.0) 73 rack (>= 1.0.0)
74 rack-test (>= 0.5.4) 74 rack-test (>= 0.5.4)
75 xpath (~> 2.0) 75 xpath (~> 2.0)
76 - cliver (0.2.2)  
77 coderay (1.0.9) 76 coderay (1.0.9)
78 - columnize (0.3.6)  
79 coveralls (0.7.0) 77 coveralls (0.7.0)
80 multi_json (~> 1.3) 78 multi_json (~> 1.3)
81 rest-client 79 rest-client
@@ -88,12 +86,6 @@ GEM @@ -88,12 +86,6 @@ GEM
88 addressable 86 addressable
89 database_cleaner (1.2.0) 87 database_cleaner (1.2.0)
90 debug_inspector (0.0.2) 88 debug_inspector (0.0.2)
91 - debugger (1.6.3)  
92 - columnize (>= 0.3.1)  
93 - debugger-linecache (~> 1.2.0)  
94 - debugger-ruby_core_source (~> 1.2.4)  
95 - debugger-linecache (1.2.0)  
96 - debugger-ruby_core_source (1.2.4)  
97 decent_exposure (2.3.0) 89 decent_exposure (2.3.0)
98 devise (3.1.1) 90 devise (3.1.1)
99 bcrypt-ruby (~> 3.0) 91 bcrypt-ruby (~> 3.0)
@@ -103,11 +95,6 @@ GEM @@ -103,11 +95,6 @@ GEM
103 warden (~> 1.2.3) 95 warden (~> 1.2.3)
104 diff-lcs (1.2.4) 96 diff-lcs (1.2.4)
105 dotenv (0.9.0) 97 dotenv (0.9.0)
106 - draper (1.3.0)  
107 - actionpack (>= 3.0)  
108 - activemodel (>= 3.0)  
109 - activesupport (>= 3.0)  
110 - request_store (~> 1.0.3)  
111 email_spec (1.5.0) 98 email_spec (1.5.0)
112 launchy (~> 2.1) 99 launchy (~> 2.1)
113 mail (~> 2.2) 100 mail (~> 2.2)
@@ -164,6 +151,7 @@ GEM @@ -164,6 +151,7 @@ GEM
164 railties 151 railties
165 method_source (0.8.2) 152 method_source (0.8.2)
166 mime-types (1.25.1) 153 mime-types (1.25.1)
  154 + mini_portile (0.5.3)
167 mongoid (3.1.5) 155 mongoid (3.1.5)
168 activemodel (~> 3.2) 156 activemodel (~> 3.2)
169 moped (~> 1.4) 157 moped (~> 1.4)
@@ -179,7 +167,7 @@ GEM @@ -179,7 +167,7 @@ GEM
179 rails (>= 3.2.0) 167 rails (>= 3.2.0)
180 railties (>= 3.2.0) 168 railties (>= 3.2.0)
181 moped (1.5.1) 169 moped (1.5.1)
182 - multi_json (1.8.2) 170 + multi_json (1.10.0)
183 multi_xml (0.5.5) 171 multi_xml (0.5.5)
184 multipart-post (1.2.0) 172 multipart-post (1.2.0)
185 net-scp (1.1.2) 173 net-scp (1.1.2)
@@ -189,7 +177,8 @@ GEM @@ -189,7 +177,8 @@ GEM
189 net-ssh (2.7.0) 177 net-ssh (2.7.0)
190 net-ssh-gateway (1.2.0) 178 net-ssh-gateway (1.2.0)
191 net-ssh (>= 2.6.5) 179 net-ssh (>= 2.6.5)
192 - nokogiri (1.5.10) 180 + nokogiri (1.6.1)
  181 + mini_portile (~> 0.5.0)
193 oauth2 (0.8.1) 182 oauth2 (0.8.1)
194 faraday (~> 0.8) 183 faraday (~> 0.8)
195 httpauth (~> 0.1) 184 httpauth (~> 0.1)
@@ -211,13 +200,8 @@ GEM @@ -211,13 +200,8 @@ GEM
211 orm_adapter (0.4.0) 200 orm_adapter (0.4.0)
212 pjax_rails (0.3.4) 201 pjax_rails (0.3.4)
213 jquery-rails 202 jquery-rails
214 - poltergeist (1.4.1)  
215 - capybara (~> 2.1.0)  
216 - cliver (~> 0.2.1)  
217 - multi_json (~> 1.0)  
218 - websocket-driver (>= 0.2.0)  
219 - polyglot (0.3.3)  
220 - premailer (1.7.9) 203 + polyglot (0.3.4)
  204 + premailer (1.7.3)
221 css_parser (>= 1.1.9) 205 css_parser (>= 1.1.9)
222 htmlentities (>= 4.0.0) 206 htmlentities (>= 4.0.0)
223 pry (0.9.12.2) 207 pry (0.9.12.2)
@@ -235,33 +219,33 @@ GEM @@ -235,33 +219,33 @@ GEM
235 rack (>= 0.4) 219 rack (>= 0.4)
236 rack-contrib (1.1.0) 220 rack-contrib (1.1.0)
237 rack (>= 0.9.1) 221 rack (>= 0.9.1)
238 - rack-ssl (1.3.3) 222 + rack-ssl (1.3.4)
239 rack 223 rack
240 rack-ssl-enforcer (0.2.6) 224 rack-ssl-enforcer (0.2.6)
241 rack-test (0.6.2) 225 rack-test (0.6.2)
242 rack (>= 1.0) 226 rack (>= 1.0)
243 - rails (3.2.16)  
244 - actionmailer (= 3.2.16)  
245 - actionpack (= 3.2.16)  
246 - activerecord (= 3.2.16)  
247 - activeresource (= 3.2.16)  
248 - activesupport (= 3.2.16) 227 + rails (3.2.18)
  228 + actionmailer (= 3.2.18)
  229 + actionpack (= 3.2.18)
  230 + activerecord (= 3.2.18)
  231 + activeresource (= 3.2.18)
  232 + activesupport (= 3.2.18)
249 bundler (~> 1.0) 233 bundler (~> 1.0)
250 - rails_autolink (1.1.5) 234 + railties (= 3.2.18)
  235 + rails_autolink (1.1.4)
251 rails (> 3.1) 236 rails (> 3.1)
252 - railties (3.2.16)  
253 - actionpack (= 3.2.16)  
254 - activesupport (= 3.2.16) 237 + railties (3.2.18)
  238 + actionpack (= 3.2.18)
  239 + activesupport (= 3.2.18)
255 rack-ssl (~> 1.3.2) 240 rack-ssl (~> 1.3.2)
256 rake (>= 0.8.7) 241 rake (>= 0.8.7)
257 rdoc (~> 3.4) 242 rdoc (~> 3.4)
258 thor (>= 0.14.6, < 2.0) 243 thor (>= 0.14.6, < 2.0)
259 raindrops (0.12.0) 244 raindrops (0.12.0)
260 - rake (10.1.0) 245 + rake (10.3.1)
261 rdoc (3.12.2) 246 rdoc (3.12.2)
262 json (~> 1.4) 247 json (~> 1.4)
263 ref (1.0.5) 248 ref (1.0.5)
264 - request_store (1.0.5)  
265 rest-client (1.6.7) 249 rest-client (1.6.7)
266 mime-types (>= 1.16) 250 mime-types (>= 1.16)
267 ri_cal (0.8.8) 251 ri_cal (0.8.8)
@@ -306,7 +290,7 @@ GEM @@ -306,7 +290,7 @@ GEM
306 therubyracer (0.12.0) 290 therubyracer (0.12.0)
307 libv8 (~> 3.16.14.0) 291 libv8 (~> 3.16.14.0)
308 ref 292 ref
309 - thor (0.18.1) 293 + thor (0.19.1)
310 thread_safe (0.1.3) 294 thread_safe (0.1.3)
311 atomic 295 atomic
312 tilt (1.4.1) 296 tilt (1.4.1)
@@ -318,8 +302,8 @@ GEM @@ -318,8 +302,8 @@ GEM
318 turbo-sprockets-rails3 (0.3.10) 302 turbo-sprockets-rails3 (0.3.10)
319 railties (> 3.2.8, < 4.0.0) 303 railties (> 3.2.8, < 4.0.0)
320 sprockets (>= 2.0.0) 304 sprockets (>= 2.0.0)
321 - tzinfo (0.3.38)  
322 - uglifier (2.3.0) 305 + tzinfo (0.3.39)
  306 + uglifier (2.2.1)
323 execjs (>= 0.3.0) 307 execjs (>= 0.3.0)
324 json (>= 1.8.0) 308 json (>= 1.8.0)
325 underscore-rails (1.5.2) 309 underscore-rails (1.5.2)
@@ -333,7 +317,6 @@ GEM @@ -333,7 +317,6 @@ GEM
333 webmock (1.15.0) 317 webmock (1.15.0)
334 addressable (>= 2.2.7) 318 addressable (>= 2.2.7)
335 crack (>= 0.3.2) 319 crack (>= 0.3.2)
336 - websocket-driver (0.3.0)  
337 xmpp4r (0.5.5) 320 xmpp4r (0.5.5)
338 xpath (2.0.0) 321 xpath (2.0.0)
339 nokogiri (~> 1.3) 322 nokogiri (~> 1.3)
@@ -343,9 +326,9 @@ PLATFORMS @@ -343,9 +326,9 @@ PLATFORMS
343 ruby 326 ruby
344 327
345 DEPENDENCIES 328 DEPENDENCIES
346 - actionmailer (~> 3.2.15) 329 + actionmailer (~> 3.2.18)
347 actionmailer_inline_css 330 actionmailer_inline_css
348 - actionpack (~> 3.2.15) 331 + actionpack (~> 3.2.18)
349 airbrake 332 airbrake
350 better_errors 333 better_errors
351 binding_of_caller 334 binding_of_caller
@@ -354,10 +337,8 @@ DEPENDENCIES @@ -354,10 +337,8 @@ DEPENDENCIES
354 capybara 337 capybara
355 coveralls 338 coveralls
356 database_cleaner 339 database_cleaner
357 - debugger  
358 decent_exposure 340 decent_exposure
359 devise 341 devise
360 - draper  
361 email_spec 342 email_spec
362 errbit_github_plugin! 343 errbit_github_plugin!
363 errbit_plugin! 344 errbit_plugin!
@@ -380,14 +361,13 @@ DEPENDENCIES @@ -380,14 +361,13 @@ DEPENDENCIES
380 mongoid_rails_migrations 361 mongoid_rails_migrations
381 omniauth-github 362 omniauth-github
382 pjax_rails 363 pjax_rails
383 - poltergeist  
384 pry-rails 364 pry-rails
385 puma 365 puma
386 quiet_assets 366 quiet_assets
387 rack-ssl 367 rack-ssl
388 rack-ssl-enforcer 368 rack-ssl-enforcer
389 rails_autolink 369 rails_autolink
390 - railties (~> 3.2.15) 370 + railties (~> 3.2.18)
391 ri_cal 371 ri_cal
392 rspec-rails 372 rspec-rails
393 rushover 373 rushover
1 # Errbit [![TravisCI][travis-img-url]][travis-ci-url] [![Code Climate][codeclimate-img-url]][codeclimate-url] [![Coveralls][coveralls-img-url]][coveralls-url] [![Dependency Status][gemnasium-img-url]][gemnasium-url] 1 # Errbit [![TravisCI][travis-img-url]][travis-ci-url] [![Code Climate][codeclimate-img-url]][codeclimate-url] [![Coveralls][coveralls-img-url]][coveralls-url] [![Dependency Status][gemnasium-img-url]][gemnasium-url]
2 2
3 -[travis-img-url]: https://secure.travis-ci.org/errbit/errbit.png?branch=master 3 +[travis-img-url]: https://travis-ci.org/errbit/errbit.svg?branch=master
4 [travis-ci-url]: http://travis-ci.org/errbit/errbit 4 [travis-ci-url]: http://travis-ci.org/errbit/errbit
5 [codeclimate-img-url]: https://codeclimate.com/github/errbit/errbit.png 5 [codeclimate-img-url]: https://codeclimate.com/github/errbit/errbit.png
6 [codeclimate-url]: https://codeclimate.com/github/errbit/errbit 6 [codeclimate-url]: https://codeclimate.com/github/errbit/errbit
@@ -409,6 +409,19 @@ end @@ -409,6 +409,19 @@ end
409 409
410 Then get the `notifier.js` from `errbit/public/javascript/notifier.js` and add to `application.js` on your rails app or include `http://YOUR-ERRBIT-HOST/javascripts/notifier.js` on your `application.html.erb.` 410 Then get the `notifier.js` from `errbit/public/javascript/notifier.js` and add to `application.js` on your rails app or include `http://YOUR-ERRBIT-HOST/javascripts/notifier.js` on your `application.html.erb.`
411 411
  412 +Using custom fingerprinting methods
  413 +-----------------------------------
  414 +
  415 +Errbit now allows you to easily use your own Fingerprint Strategy if that's what you'd like to do. If you are upgrading from a very old version of errbit, you can use the `LegacyFingerprint` to provide yourself
  416 +with compatibility. The fingerprint strategy can be changed by adding an initializer to errbit:
  417 +
  418 +```ruby
  419 +# config/fingerprint.rb
  420 +ErrorReport.fingerprint_strategy = LegacyFingerprint
  421 +```
  422 +
  423 +The easiest way to add custom fingerprint methods is to simply subclass `Fingerprint`
  424 +
412 Issue Trackers 425 Issue Trackers
413 -------------- 426 --------------
414 427
@@ -597,7 +610,3 @@ Copyright @@ -597,7 +610,3 @@ Copyright
597 610
598 Copyright (c) 2010-2013 Errbit Team. See LICENSE for details. 611 Copyright (c) 2010-2013 Errbit Team. See LICENSE for details.
599 612
600 -  
601 -  
602 -[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/errbit/errbit/trend.png)](https://bitdeli.com/free "Bitdeli Badge")  
603 -  
app/assets/images/slack_create.png 0 → 100644

5.89 KB

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

5.89 KB

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

5.68 KB

app/controllers/api/v1/notices_controller.rb
@@ -16,8 +16,7 @@ class Api::V1::NoticesController &lt; ApplicationController @@ -16,8 +16,7 @@ class Api::V1::NoticesController &lt; ApplicationController
16 end 16 end
17 17
18 respond_to do |format| 18 respond_to do |format|
19 - format.html { render :json => Yajl.dump(results) } # render JSON if no extension specified on path  
20 - format.json { render :json => Yajl.dump(results) } 19 + format.any(:html, :json) { render :json => Yajl.dump(results) } # render JSON if no extension specified on path
21 format.xml { render :xml => results } 20 format.xml { render :xml => results }
22 end 21 end
23 end 22 end
app/controllers/api/v1/problems_controller.rb
@@ -16,8 +16,7 @@ class Api::V1::ProblemsController &lt; ApplicationController @@ -16,8 +16,7 @@ class Api::V1::ProblemsController &lt; ApplicationController
16 end 16 end
17 17
18 respond_to do |format| 18 respond_to do |format|
19 - format.html { render :json => Yajl.dump(results) } # render JSON if no extension specified on path  
20 - format.json { render :json => Yajl.dump(results) } 19 + format.any(:html, :json) { render :json => Yajl.dump(results) } # render JSON if no extension specified on path
21 format.xml { render :xml => results } 20 format.xml { render :xml => results }
22 end 21 end
23 end 22 end
app/controllers/api/v1/stats_controller.rb
@@ -12,13 +12,13 @@ class Api::V1::StatsController &lt; ApplicationController @@ -12,13 +12,13 @@ class Api::V1::StatsController &lt; ApplicationController
12 12
13 stats = { 13 stats = {
14 :name => @app.name, 14 :name => @app.name,
  15 + :id => @app.id,
15 :last_error_time => @last_error_time, 16 :last_error_time => @last_error_time,
16 :unresolved_errors => @app.unresolved_count 17 :unresolved_errors => @app.unresolved_count
17 } 18 }
18 19
19 respond_to do |format| 20 respond_to do |format|
20 - format.html { render :json => Yajl.dump(stats) } # render JSON if no extension specified on path  
21 - format.json { render :json => Yajl.dump(stats) } 21 + format.any(:html, :json) { render :json => Yajl.dump(stats) } # render JSON if no extension specified on path
22 format.xml { render :xml => stats } 22 format.xml { render :xml => stats }
23 end 23 end
24 end 24 end
@@ -37,5 +37,3 @@ class Api::V1::StatsController &lt; ApplicationController @@ -37,5 +37,3 @@ class Api::V1::StatsController &lt; ApplicationController
37 end 37 end
38 38
39 end 39 end
40 -  
41 -  
app/controllers/apps_controller.rb
@@ -39,6 +39,10 @@ class AppsController &lt; ApplicationController @@ -39,6 +39,10 @@ class AppsController &lt; ApplicationController
39 app.deploys.order_by(:created_at.desc).limit(5) 39 app.deploys.order_by(:created_at.desc).limit(5)
40 } 40 }
41 41
  42 + expose(:users) {
  43 + User.all.sort_by {|u| u.name.downcase }
  44 + }
  45 +
42 def index; end 46 def index; end
43 def show 47 def show
44 app 48 app
@@ -91,8 +95,10 @@ class AppsController &lt; ApplicationController @@ -91,8 +95,10 @@ class AppsController &lt; ApplicationController
91 def initialize_subclassed_notification_service 95 def initialize_subclassed_notification_service
92 # set the app's notification service 96 # set the app's notification service
93 if params[:app][:notification_service_attributes] && notification_type = params[:app][:notification_service_attributes][:type] 97 if params[:app][:notification_service_attributes] && notification_type = params[:app][:notification_service_attributes][:type]
94 - if NotificationService.subclasses.map(&:name).concat(["NotificationService"]).include?(notification_type)  
95 - app.notification_service = notification_type.constantize.new(params[:app][:notification_service_attributes]) 98 + available_notification_classes = [NotificationService] + NotificationService.subclasses
  99 + notification_class = available_notification_classes.detect{|c| c.name == notification_type}
  100 + if !notification_class.nil?
  101 + app.notification_service = notification_class.new(params[:app][:notification_service_attributes])
96 end 102 end
97 end 103 end
98 end 104 end
app/controllers/problems_controller.rb
@@ -123,6 +123,14 @@ class ProblemsController &lt; ApplicationController @@ -123,6 +123,14 @@ class ProblemsController &lt; ApplicationController
123 redirect_to :back 123 redirect_to :back
124 end 124 end
125 125
  126 + def destroy_all
  127 + nb_problem_destroy = ProblemDestroy.execute(app.problems)
  128 + flash[:success] = "#{I18n.t(:n_errs_have, :count => nb_problem_destroy)} been deleted."
  129 + redirect_to :back
  130 + rescue ActionController::RedirectBackError
  131 + redirect_to app_path(app)
  132 + end
  133 +
126 def search 134 def search
127 ps = Problem.search(params[:search]).for_apps(app_scope).in_env(params[:environment]).all_else_unresolved(params[:all_errs]).ordered_by(params_sort, params_order) 135 ps = Problem.search(params[:search]).for_apps(app_scope).in_env(params[:environment]).all_else_unresolved(params[:all_errs]).ordered_by(params_sort, params_order)
128 selected_problems = params[:problems] || [] 136 selected_problems = params[:problems] || []
app/models/error_report.rb
@@ -17,6 +17,10 @@ require &#39;hoptoad_notifier&#39; @@ -17,6 +17,10 @@ require &#39;hoptoad_notifier&#39;
17 class ErrorReport 17 class ErrorReport
18 attr_reader :error_class, :message, :request, :server_environment, :api_key, :notifier, :user_attributes, :framework 18 attr_reader :error_class, :message, :request, :server_environment, :api_key, :notifier, :user_attributes, :framework
19 19
  20 + cattr_accessor :fingerprint_strategy do
  21 + Fingerprint
  22 + end
  23 +
20 def initialize(xml_or_attributes) 24 def initialize(xml_or_attributes)
21 @attributes = (xml_or_attributes.is_a?(String) ? Hoptoad.parse_xml!(xml_or_attributes) : xml_or_attributes).with_indifferent_access 25 @attributes = (xml_or_attributes.is_a?(String) ? Hoptoad.parse_xml!(xml_or_attributes) : xml_or_attributes).with_indifferent_access
22 @attributes.each{|k, v| instance_variable_set(:"@#{k}", v) } 26 @attributes.each{|k, v| instance_variable_set(:"@#{k}", v) }
@@ -84,7 +88,7 @@ class ErrorReport @@ -84,7 +88,7 @@ class ErrorReport
84 private 88 private
85 89
86 def fingerprint 90 def fingerprint
87 - @fingerprint ||= Fingerprint.generate(notice, api_key) 91 + @fingerprint ||= fingerprint_strategy.generate(notice, api_key)
88 end 92 end
89 93
90 end 94 end
app/models/fingerprints/legacy_fingerprint.rb 0 → 100644
@@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
  1 +require 'digest/md5'
  2 +
  3 +class LegacyFingerprint < Fingerprint
  4 + def to_s
  5 + Digest::MD5.hexdigest(fingerprint_source)
  6 + end
  7 +
  8 + def fingerprint_source
  9 + location['method'] &&= sanitized_method_signature
  10 + end
  11 +
  12 + private
  13 + def sanitized_method_signature
  14 + location['method'].gsub(/[0-9]+|FRAGMENT/, '#').gsub(/_+#/, '_#')
  15 + end
  16 +
  17 + def location
  18 + notice.backtrace.lines.first
  19 + end
  20 +end
app/models/notice.rb
@@ -78,6 +78,17 @@ class Notice @@ -78,6 +78,17 @@ class Notice
78 "N/A" 78 "N/A"
79 end 79 end
80 80
  81 + def to_curl
  82 + return "N/A" if url.blank?
  83 + headers = %w(Accept Accept-Encoding Accept-Language Cookie Referer User-Agent).each_with_object([]) do |name, h|
  84 + if value = env_vars["HTTP_#{name.underscore.upcase}"]
  85 + h << "-H '#{name}: #{value}'"
  86 + end
  87 + end
  88 +
  89 + "curl -X #{env_vars['REQUEST_METHOD'] || 'GET'} #{headers.join(' ')} #{url}"
  90 + end
  91 +
81 def env_vars 92 def env_vars
82 request['cgi-data'] || {} 93 request['cgi-data'] || {}
83 end 94 end
app/models/notification_services/slack_service.rb 0 → 100644
@@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
  1 +class NotificationServices::SlackService < NotificationService
  2 + Label = "slack"
  3 + Fields += [
  4 + [:subdomain, {
  5 + :placeholder => 'subdomain',
  6 + :label => 'Subdomain portion for Slack service'
  7 + }],
  8 + [:api_token, {
  9 + :placeholder => 'Slack Integration Token',
  10 + :label => 'Token'
  11 + }],
  12 + [:room_id, {
  13 + :placeholder => '#general',
  14 + :label => 'Room where Slack should notify'
  15 + }]
  16 + ]
  17 +
  18 + def check_params
  19 + if Fields.detect {|f| self[f[0]].blank? unless f[0] == :room_id }
  20 + errors.add :base, "You must specify your Slack subdomain and token."
  21 + end
  22 + end
  23 +
  24 + def url
  25 + "https://#{subdomain}.slack.com/services/hooks/incoming-webhook?token=#{api_token}"
  26 + end
  27 +
  28 + def message_for_slack(problem)
  29 + "[#{problem.app.name}][#{problem.environment}][#{problem.where}]: #{problem.error_class} #{problem_url(problem)}"
  30 + end
  31 +
  32 + def post_payload(problem)
  33 + payload = {:text => message_for_slack(problem) }
  34 + payload[:channel] = room_id unless room_id.empty?
  35 + payload.to_json
  36 + end
  37 +
  38 + def create_notification(problem)
  39 + HTTParty.post(url, :body => post_payload(problem), :headers => { 'Content-Type' => 'application/json' })
  40 + end
  41 +end
app/views/apps/_fields.html.haml
@@ -53,9 +53,9 @@ @@ -53,9 +53,9 @@
53 = w.radio_button :watcher_type, :user 53 = w.radio_button :watcher_type, :user
54 = w.label :watcher_type_user, 'User' 54 = w.label :watcher_type_user, 'User'
55 = w.radio_button :watcher_type, :email 55 = w.radio_button :watcher_type, :email
56 - = w.label :watcher_type_email, 'Email Address'  
57 - %div.watcher_params.user{:class => w.object.email_choosen}  
58 - = w.select :user_id, User.all.map{|u| [u.name,u.id.to_s]}, :include_blank => '-- Select a User --' 56 + = label_tag :watcher_type_email, 'Email Address', :for => label_for_attr(w, 'watcher_type_email')
  57 + %div.watcher_params.user{:class => w.object.email.blank? ? 'chosen' : nil}
  58 + = w.select :user_id, users.map{|u| [u.name,u.id.to_s]}, :include_blank => '-- Select a User --'
59 %div.watcher_params.email{:class => w.object.email.present? ? 'chosen' : nil} 59 %div.watcher_params.email{:class => w.object.email.present? ? 'chosen' : nil}
60 = w.text_field :email 60 = w.text_field :email
61 61
@@ -65,4 +65,3 @@ @@ -65,4 +65,3 @@
65 65
66 = render "issue_tracker_fields", :f => f 66 = render "issue_tracker_fields", :f => f
67 = render "service_notification_fields", :f => f 67 = render "service_notification_fields", :f => f
68 -  
app/views/apps/edit.html.haml
1 - content_for :title, t('.title') 1 - content_for :title, t('.title')
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 t('.destroy'), app_path(app), :method => :delete, 4 + = link_to 'delete all errs', destroy_all_app_problems_path(app), :method => :post,
  5 + :data => { :confirm => t('apps.confirm_destroy_all_problems') }, :class => 'button'
  6 + = link_to 'delete application', app_path(app), :method => :delete,
5 :data => { :confirm => t('apps.confirm_delete') }, :class => 'button' 7 :data => { :confirm => t('apps.confirm_delete') }, :class => 'button'
6 = link_to(t('.cancel'), app_path(app), :class => 'button') 8 = link_to(t('.cancel'), app_path(app), :class => 'button')
7 9
app/views/devise/mailer/reset_password_instructions.html.haml
@@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
10 %p 10 %p
11 We hear you'd like to change your password. You can do that by visiting the link below: 11 We hear you'd like to change your password. You can do that by visiting the link below:
12 %p 12 %p
13 - = edit_password_url @resource, :reset_password_token => @resource.reset_password_token 13 + = edit_password_url @resource, :reset_password_token => @token
14 %tr 14 %tr
15 %td.content(valign="top") 15 %td.content(valign="top")
16 %div 16 %div
app/views/devise/mailer/reset_password_instructions.text.erb
@@ -2,7 +2,7 @@ Hello, @@ -2,7 +2,7 @@ Hello,
2 2
3 We hear you'd like to change your password. You can do that by visiting the link below: 3 We hear you'd like to change your password. You can do that by visiting the link below:
4 4
5 - <%= edit_password_url(@resource, :reset_password_token => @resource.reset_password_token) %> 5 + <%= edit_password_url(@resource, :reset_password_token => @token) %>
6 6
7 If you didn't request this, please ignore this email. Your password won't change unless you access the link above and create a new one. 7 If you didn't request this, please ignore this email. Your password won't change unless you access the link above and create a new one.
8 8
app/views/notices/_summary.html.haml
@@ -10,6 +10,9 @@ @@ -10,6 +10,9 @@
10 %tr 10 %tr
11 %th URL 11 %th URL
12 %td.nowrap= link_to notice.request['url'], notice.request['url'] 12 %td.nowrap= link_to notice.request['url'], notice.request['url']
  13 + %tr
  14 + %th &nbsp;
  15 + %td= notice.to_curl
13 %tr 16 %tr
14 %th Where 17 %th Where
15 %td= notice.where 18 %td= notice.where
config/locales/en.yml
@@ -79,11 +79,12 @@ en: @@ -79,11 +79,12 @@ en:
79 unresolve: "Unresolve selected issues? They can be resolved again later." 79 unresolve: "Unresolve selected issues? They can be resolved again later."
80 80
81 comments: 81 comments:
82 - confirm_delete: "Premanently delete this comment?" 82 + confirm_delete: "Permanently delete this comment?"
83 users: 83 users:
84 confirm_delete: "Permanently delete this user?" 84 confirm_delete: "Permanently delete this user?"
85 apps: 85 apps:
86 confirm_delete: "Permanently delete this app?" 86 confirm_delete: "Permanently delete this app?"
  87 + confirm_destroy_all_problems: "Permanently delete all of this app's errors?"
87 index: 88 index:
88 notify: Notification Service 89 notify: Notification Service
89 tracker: Tracker 90 tracker: Tracker
config/routes.rb
@@ -30,6 +30,10 @@ Errbit::Application.routes.draw do @@ -30,6 +30,10 @@ Errbit::Application.routes.draw do
30 resources :notices 30 resources :notices
31 resources :comments, :only => [:create, :destroy] 31 resources :comments, :only => [:create, :destroy]
32 32
  33 + collection do
  34 + post :destroy_all
  35 + end
  36 +
33 member do 37 member do
34 put :resolve 38 put :resolve
35 put :unresolve 39 put :unresolve
db/migrate/20121003223358_extract_backtraces.rb
1 class ExtractBacktraces < Mongoid::Migration 1 class ExtractBacktraces < Mongoid::Migration
2 def self.up 2 def self.up
3 say "It could take long time (hours if you have many Notices)" 3 say "It could take long time (hours if you have many Notices)"
4 - Notice.unscoped.all.each do |notice| 4 + Notice.unscoped.where(backtrace_id: nil).each do |notice|
5 next if notice.backtrace.present? || notice['backtrace'].nil? 5 next if notice.backtrace.present? || notice['backtrace'].nil?
6 backtrace = Backtrace.find_or_create(:raw => notice['backtrace'] || []) 6 backtrace = Backtrace.find_or_create(:raw => notice['backtrace'] || [])
  7 + notice.unset(:backtrace)
7 notice.backtrace = backtrace 8 notice.backtrace = backtrace
8 - notice['backtrace'] = nil  
9 notice.save! 9 notice.save!
10 end 10 end
11 say "run `db.repairDatabase()` (in mongodb console) to recover deleted space" 11 say "run `db.repairDatabase()` (in mongodb console) to recover deleted space"
@@ -13,4 +13,4 @@ class ExtractBacktraces &lt; Mongoid::Migration @@ -13,4 +13,4 @@ class ExtractBacktraces &lt; Mongoid::Migration
13 13
14 def self.down 14 def self.down
15 end 15 end
16 -end  
17 \ No newline at end of file 16 \ No newline at end of file
  17 +end
db/migrate/20121005142110_regenerate_err_fingerprints.rb
1 class RegenerateErrFingerprints < Mongoid::Migration 1 class RegenerateErrFingerprints < Mongoid::Migration
2 def self.up 2 def self.up
3 Err.all.each do |err| 3 Err.all.each do |err|
4 - if err.notices.any?  
5 - fingerprint_source = {  
6 - :backtrace => err.notices.first.backtrace_id,  
7 - :error_class => err.error_class,  
8 - :component => err.component,  
9 - :action => err.action,  
10 - :environment => err.environment,  
11 - :api_key => err.app.api_key  
12 - } 4 + if err.notices.any? && err.problem
13 err.update_attribute( 5 err.update_attribute(
14 :fingerprint, 6 :fingerprint,
15 Fingerprint.generate(err.notices.first, err.app.api_key) 7 Fingerprint.generate(err.notices.first, err.app.api_key)
lib/tasks/errbit/demo.rake
@@ -49,7 +49,8 @@ namespace :errbit do @@ -49,7 +49,8 @@ namespace :errbit do
49 :backtrace => random_backtrace, 49 :backtrace => random_backtrace,
50 :request => { 50 :request => {
51 'component' => 'main', 51 'component' => 'main',
52 - 'action' => 'error' 52 + 'action' => 'error',
  53 + 'url' => "http://example.com/post/#{[111, 222, 333].sample}",
53 }, 54 },
54 :server_environment => {'environment-name' => Rails.env.to_s}, 55 :server_environment => {'environment-name' => Rails.env.to_s},
55 :notifier => {:name => "seeds.rb"}, 56 :notifier => {:name => "seeds.rb"},
public/javascripts/notifier.js
@@ -870,6 +870,12 @@ printStackTrace.implementation.prototype = { @@ -870,6 +870,12 @@ printStackTrace.implementation.prototype = {
870 // Share to global scope as Airbrake ("window.Hoptoad" for backward compatibility) 870 // Share to global scope as Airbrake ("window.Hoptoad" for backward compatibility)
871 Global = window.Airbrake = window.Hoptoad = Util.generatePublicAPI(_publicAPI, Config); 871 Global = window.Airbrake = window.Hoptoad = Util.generatePublicAPI(_publicAPI, Config);
872 872
  873 + Global._filters = [];
  874 +
  875 + Global.addFilter = function (cb) {
  876 + Global._filters.push(cb);
  877 + };
  878 +
873 function Notifier() { 879 function Notifier() {
874 this.options = Util.merge({}, Config.options); 880 this.options = Util.merge({}, Config.options);
875 this.xmlData = Util.merge(this.DEF_XML_DATA, Config.xmlData); 881 this.xmlData = Util.merge(this.DEF_XML_DATA, Config.xmlData);
@@ -921,7 +927,7 @@ printStackTrace.implementation.prototype = { @@ -921,7 +927,7 @@ printStackTrace.implementation.prototype = {
921 } 927 }
922 928
923 return function (error) { 929 return function (error) {
924 - var outputData = '', 930 + var outputData = '', jsonData,
925 url = ''; 931 url = '';
926 // 932 //
927 933
@@ -934,9 +940,12 @@ printStackTrace.implementation.prototype = { @@ -934,9 +940,12 @@ printStackTrace.implementation.prototype = {
934 940
935 switch (this.options['outputFormat']) { 941 switch (this.options['outputFormat']) {
936 case 'XML': 942 case 'XML':
937 - outputData = encodeURIComponent(this.generateXML(this.generateDataJSON(error)));  
938 - url = ('https:' == document.location.protocol ? 'https://' : 'http://') + this.options.host + '/notifier_api/v2/notices';  
939 - _sendGETRequest(url, outputData); 943 + jsonData = this.generateDataJSON(error);
  944 + if (this.shouldSendData(jsonData)){
  945 + outputData = encodeURIComponent(this.generateXML(jsonData));
  946 + url = ('https:' == document.location.protocol ? 'https://' : 'http://') + this.options.host + '/notifier_api/v2/notices';
  947 + _sendGETRequest(url, outputData);
  948 + }
940 break; 949 break;
941 950
942 case 'JSON': 951 case 'JSON':
@@ -945,9 +954,12 @@ printStackTrace.implementation.prototype = { @@ -945,9 +954,12 @@ printStackTrace.implementation.prototype = {
945 * http://collect.airbrake.io/api/v3/projects/[PROJECT_ID]/notices?key=[API_KEY] 954 * http://collect.airbrake.io/api/v3/projects/[PROJECT_ID]/notices?key=[API_KEY]
946 * url = window.location.protocol + '://' + this.options.host + '/api/v3/projects' + this.options.projectId + '/notices?key=' + this.options.key; 955 * url = window.location.protocol + '://' + this.options.host + '/api/v3/projects' + this.options.projectId + '/notices?key=' + this.options.key;
947 */ 956 */
948 - outputData = JSON.stringify(this.generateJSON(this.generateDataJSON(error)));  
949 - url = ('https:' == document.location.protocol ? 'https://' : 'http://') + this.options.host + '/api/v3/projects/' + this.options.projectId + '/notices?key=' + this.xmlData.key;  
950 - _sendPOSTRequest(url, outputData); 957 + jsonData = this.generateDataJSON(error);
  958 + if (this.shouldSendData(jsonData)){
  959 + outputData = JSON.stringify(this.generateJSON(jsonData));
  960 + url = ('https:' == document.location.protocol ? 'https://' : 'http://') + this.options.host + '/api/v3/projects/' + this.options.projectId + '/notices?key=' + this.xmlData.key;
  961 + _sendPOSTRequest(url, outputData);
  962 + }
951 break; 963 break;
952 964
953 default: 965 default:
@@ -1150,6 +1162,13 @@ printStackTrace.implementation.prototype = { @@ -1150,6 +1162,13 @@ printStackTrace.implementation.prototype = {
1150 continue; 1162 continue;
1151 } 1163 }
1152 1164
  1165 + // Special case for sprocket coffee stacktrace:
  1166 + // "Function.foo (http://host/file.js?body=1:666:42)" becomes "Function.foo @http://host/file.js?body=1:666"
  1167 + if (stacktrace[i].match(/\([^\s]+:(\d+):(\d+)\)$/)) {
  1168 + stacktrace[i] = stacktrace[i].replace(/\((.+):(\d+):(\d+)\)$/, '@$1:$2')
  1169 + continue;
  1170 + }
  1171 +
1153 if (stacktrace[i].indexOf('@') === -1) { 1172 if (stacktrace[i].indexOf('@') === -1) {
1154 stacktrace[i] += '@unsupported.js'; 1173 stacktrace[i] += '@unsupported.js';
1155 } 1174 }
@@ -1168,16 +1187,31 @@ printStackTrace.implementation.prototype = { @@ -1168,16 +1187,31 @@ printStackTrace.implementation.prototype = {
1168 } 1187 }
1169 1188
1170 return true; 1189 return true;
  1190 + },
  1191 +
  1192 + shouldSendData: function (jsonData) {
  1193 + var shouldSend = true, i;
  1194 +
  1195 + for ( i = 0; i < Global._filters.length; i++ ) {
  1196 + if ( ! Global._filters[i](jsonData) ){
  1197 + shouldSend = false;
  1198 + }
  1199 + }
  1200 +
  1201 + return shouldSend;
1171 } 1202 }
1172 }; 1203 };
1173 1204
  1205 + var oldOnerror = window.onerror;
1174 window.onerror = function (message, file, line, code, error) { 1206 window.onerror = function (message, file, line, code, error) {
1175 setTimeout(function () { 1207 setTimeout(function () {
1176 var e = error || {stack: '()@' + file + ':' + line} 1208 var e = error || {stack: '()@' + file + ':' + line}
1177 e.message = message 1209 e.message = message
1178 new Notifier().notify(e); 1210 new Notifier().notify(e);
1179 }, 0); 1211 }, 0);
1180 - 1212 + if (oldOnerror) {
  1213 + return oldOnerror(message, file, line, code, error);
  1214 + }
1181 return true; 1215 return true;
1182 }; 1216 };
1183 })(); 1217 })();
spec/acceptance/reset_password_token.rb 0 → 100644
@@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
  1 +require 'acceptance/acceptance_helper'
  2 +
  3 +feature 'password reset token' do
  4 + let(:user) { Fabricate :user }
  5 +
  6 + scenario 'receives correct password reset token' do
  7 + host = ActionMailer::Base.default_url_options.values_at(:host).first
  8 + port = ActionMailer::Base.default_url_options.values_at(:port).first
  9 + port = port.blank? ? '' : ':' + port
  10 + regex = %r{http://#{host}#{port}/users/password/edit\?reset_password_token=([A-Za-z0-9\-_]+)}
  11 +
  12 + visit 'https://brighterr.herokuapp.com/users/password/new'
  13 + fill_in 'Email', with: user.email
  14 + click_button 'Send me reset password instructions'
  15 + expect(page).to have_content I18n.t('devise.passwords.send_instructions')
  16 +
  17 + mail = ActionMailer::Base.deliveries.last
  18 + expect(mail.subject).to match(/Reset password instructions/)
  19 + expect(mail.body.encoded).to_not be_empty
  20 + expect(mail.body.encoded).to match(/change your password/)
  21 + expect(mail.body.encoded).to match(regex)
  22 + if mail.body.encoded =~ regex
  23 + visit "/users/password/edit?reset_password_token=#{$1}"
  24 + expect(page).to have_content 'Change your password'
  25 + fill_in 'New password', with: 'test12345'
  26 + fill_in 'Type your new password again', with: 'test12345'
  27 + click_button 'Change my password'
  28 + expect(page).to_not have_content 'Reset password token is invalid'
  29 + end
  30 + end
  31 +end
spec/controllers/apps_controller_spec.rb
@@ -73,6 +73,16 @@ describe AppsController do @@ -73,6 +73,16 @@ describe AppsController do
73 expect(response).to be_success 73 expect(response).to be_success
74 end 74 end
75 75
  76 + it "should list available watchers by name" do
  77 + Fabricate(:user, :name => "Carol")
  78 + Fabricate(:user, :name => "Alice")
  79 + Fabricate(:user, :name => "Betty")
  80 +
  81 + get :show, :id => app.id
  82 +
  83 + expect(controller.users.to_a).to eq(User.all.to_a.sort_by(&:name))
  84 + end
  85 +
76 context "pagination" do 86 context "pagination" do
77 before(:each) do 87 before(:each) do
78 35.times { Fabricate(:err, :problem => Fabricate(:problem, :app => app)) } 88 35.times { Fabricate(:err, :problem => Fabricate(:problem, :app => app)) }
@@ -395,4 +405,3 @@ describe AppsController do @@ -395,4 +405,3 @@ describe AppsController do
395 end 405 end
396 406
397 end 407 end
398 -  
spec/controllers/problems_controller_spec.rb
@@ -390,6 +390,34 @@ describe ProblemsController do @@ -390,6 +390,34 @@ describe ProblemsController do
390 }.to change(Problem, :count).by(-1) 390 }.to change(Problem, :count).by(-1)
391 end 391 end
392 end 392 end
  393 +
  394 + describe "POST /apps/:app_id/problems/destroy_all" do
  395 + before do
  396 + sign_in Fabricate(:admin)
  397 + @app = Fabricate(:app)
  398 + @problem1 = Fabricate(:problem, :app=>@app)
  399 + @problem2 = Fabricate(:problem, :app=>@app)
  400 + end
  401 +
  402 + it "destroys all problems" do
  403 + expect {
  404 + post :destroy_all, :app_id => @app.id
  405 + }.to change(Problem, :count).by(-2)
  406 + expect(controller.app).to eq @app
  407 + end
  408 +
  409 + it "should display a message" do
  410 + put :destroy_all, :app_id => @app.id
  411 + expect(request.flash[:success]).to match(/been deleted/)
  412 + end
  413 +
  414 + it "should redirect back to the app page" do
  415 + request.env["Referer"] = edit_app_path(@app)
  416 + put :destroy_all, :app_id => @app.id
  417 + expect(response).to redirect_to(edit_app_path(@app))
  418 + end
  419 + end
  420 +
393 end 421 end
394 422
395 end 423 end
spec/fabricators/notification_service_fabricator.rb
@@ -12,6 +12,6 @@ Fabricator :gtalk_notification_service, :from =&gt; :notification_service, :class_n @@ -12,6 +12,6 @@ Fabricator :gtalk_notification_service, :from =&gt; :notification_service, :class_n
12 service { sequence :word } 12 service { sequence :word }
13 end 13 end
14 14
15 -%w(campfire flowdock hipchat hoiio hubot pushover webhook).each do |t| 15 +%w(campfire flowdock hipchat hoiio hubot pushover slack webhook).each do |t|
16 Fabricator "#{t}_notification_service".to_sym, :from => :notification_service, :class_name => "NotificationService::#{t.camelcase}Service" 16 Fabricator "#{t}_notification_service".to_sym, :from => :notification_service, :class_name => "NotificationService::#{t.camelcase}Service"
17 end 17 end
spec/models/error_report_spec.rb
@@ -47,6 +47,19 @@ describe ErrorReport do @@ -47,6 +47,19 @@ describe ErrorReport do
47 end 47 end
48 end 48 end
49 49
  50 + describe "#fingerprint_strategy" do
  51 + after(:all) {
  52 + error_report.fingerprint_strategy = Fingerprint
  53 + }
  54 +
  55 + it "should be possible to change how fingerprints are generated" do
  56 + strategy = double()
  57 + strategy.should_receive(:generate){ 'fingerprints' }
  58 + error_report.fingerprint_strategy = strategy
  59 + error_report.generate_notice!
  60 + end
  61 + end
  62 +
50 describe "#generate_notice!" do 63 describe "#generate_notice!" do
51 it "save a notice" do 64 it "save a notice" do
52 expect { 65 expect {
spec/models/fingerprints/legacy_fingerprint_spec.rb 0 → 100644
@@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
  1 +require 'spec_helper'
  2 +
  3 +describe LegacyFingerprint do
  4 + context 'being created' do
  5 + let(:backtrace) do
  6 + Backtrace.create(:raw => [
  7 + {
  8 + "number"=>"17",
  9 + "file"=>"[GEM_ROOT]/gems/activesupport/lib/active_support/callbacks.rb",
  10 + "method"=>"_run__2497084960985961383__process_action__2062871603614456254__callbacks"
  11 + }
  12 + ])
  13 + end
  14 + let(:notice1) { Fabricate.build(:notice, :backtrace => backtrace) }
  15 + let(:notice2) { Fabricate.build(:notice, :backtrace => backtrace_2) }
  16 +
  17 + context "with same backtrace" do
  18 + let(:backtrace_2) do
  19 + backtrace
  20 + backtrace.lines.last.method = '_run__FRAGMENT__process_action__FRAGMENT__callbacks'
  21 + backtrace.save
  22 + backtrace
  23 + end
  24 +
  25 + it "normalizes the fingerprint of generated methods" do
  26 + expect(LegacyFingerprint.generate(notice1, "api key")).to eql LegacyFingerprint.generate(notice2, "api key")
  27 + end
  28 + end
  29 +
  30 + context "with same backtrace where FRAGMENT has not been extracted" do
  31 + let(:backtrace_2) do
  32 + backtrace
  33 + backtrace.lines.last.method = '_run__998857585768765__process_action__1231231312321313__callbacks'
  34 + backtrace.save
  35 + backtrace
  36 + end
  37 +
  38 + it "normalizes the fingerprint of generated methods" do
  39 + expect(LegacyFingerprint.generate(notice1, "api key")).to eql LegacyFingerprint.generate(notice2, "api key")
  40 + end
  41 + end
  42 + end
  43 +end
spec/models/notice_spec.rb
@@ -35,6 +35,28 @@ describe Notice do @@ -35,6 +35,28 @@ describe Notice do
35 end 35 end
36 end 36 end
37 37
  38 + describe "to_curl" do
  39 + let(:notice) { Fabricate.build(:notice, request: request) }
  40 +
  41 + context "when it has a request url" do
  42 + let(:request) { {'url' => "http://example.com/resource/12", 'cgi-data' => {'HTTP_USER_AGENT' => 'Mozilla/5.0'}} }
  43 +
  44 + it 'has a curl representation' do
  45 + cmd = notice.to_curl
  46 + expect(cmd).to eq(%q[curl -X GET -H 'User-Agent: Mozilla/5.0' http://example.com/resource/12])
  47 + end
  48 + end
  49 +
  50 + context "when it has not a request url" do
  51 + let(:request) { {'cgi-data' => {'HTTP_USER_AGENT' => 'Mozilla/5.0'}} }
  52 +
  53 + it 'has a curl representation' do
  54 + cmd = notice.to_curl
  55 + expect(cmd).to eq "N/A"
  56 + end
  57 + end
  58 + end
  59 +
38 describe "user agent" do 60 describe "user agent" do
39 it "should be parsed and human-readable" do 61 it "should be parsed and human-readable" do
40 notice = Fabricate.build(:notice, :request => {'cgi-data' => { 62 notice = Fabricate.build(:notice, :request => {'cgi-data' => {
spec/models/notification_service/slack_service_spec.rb 0 → 100644
@@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
  1 +require 'spec_helper'
  2 +
  3 +describe NotificationService::SlackService do
  4 + it "it should send a notification to Slack with channel" do
  5 + # setup
  6 + notice = Fabricate :notice
  7 + notification_service = Fabricate :slack_notification_service, :app => notice.app
  8 + problem = notice.problem
  9 +
  10 + # faraday stubbing
  11 + payload = {:text => notification_service.message_for_slack(problem), :channel => notification_service.room_id}.to_json
  12 + expect(HTTParty).to receive(:post).with(notification_service.url, :body => payload, :headers => {"Content-Type" => "application/json"}).and_return(true)
  13 +
  14 + notification_service.create_notification(problem)
  15 + end
  16 +
  17 + it "it should send a notification to Slack without a channel" do
  18 + # setup
  19 + notice = Fabricate :notice
  20 + notification_service = Fabricate :slack_notification_service, :app => notice.app, :room_id => ""
  21 + problem = notice.problem
  22 +
  23 + # faraday stubbing
  24 + payload = {:text => notification_service.message_for_slack(problem)}.to_json
  25 + expect(HTTParty).to receive(:post).with(notification_service.url, :body => payload, :headers => {"Content-Type" => "application/json"}).and_return(true)
  26 +
  27 + notification_service.create_notification(problem)
  28 + end
  29 +end
spec/views/apps/edit.html.haml_spec.rb
@@ -14,6 +14,11 @@ describe &quot;apps/edit.html.haml&quot; do @@ -14,6 +14,11 @@ describe &quot;apps/edit.html.haml&quot; do
14 view.content_for(:action_bar) 14 view.content_for(:action_bar)
15 end 15 end
16 16
  17 + it "should confirm the 'reset' link" do
  18 + render
  19 + expect(action_bar).to have_selector('a.button[data-confirm="%s"]' % I18n.t('apps.confirm_destroy_all_problems'))
  20 + end
  21 +
17 it "should confirm the 'destroy' link" do 22 it "should confirm the 'destroy' link" do
18 render 23 render
19 expect(action_bar).to have_selector('a.button[data-confirm="%s"]' % I18n.t('apps.confirm_delete')) 24 expect(action_bar).to have_selector('a.button[data-confirm="%s"]' % I18n.t('apps.confirm_delete'))