Commit a0af55302055ad839e6bd2cd838b0f5c16092780

Authored by Andrey Subbota
2 parents 729b2c1b 30b7e5b6
Exists in master and in 1 other branch production

Merge branch 'errbit' into feature/upstream_merge

Showing 135 changed files with 571 additions and 292 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 135 files displayed.

@@ -4,7 +4,6 @@ gem 'rails', '3.2.8' @@ -4,7 +4,6 @@ gem 'rails', '3.2.8'
4 gem 'mongoid', '~> 2.4.10' 4 gem 'mongoid', '~> 2.4.10'
5 gem 'mongoid_rails_migrations' 5 gem 'mongoid_rails_migrations'
6 gem 'devise', '~> 1.5.3' 6 gem 'devise', '~> 1.5.3'
7 -gem 'nokogiri'  
8 gem 'haml' 7 gem 'haml'
9 gem 'htmlentities', "~> 4.3.0" 8 gem 'htmlentities', "~> 4.3.0"
10 gem 'rack-ssl', :require => 'rack/ssl' # force SSL 9 gem 'rack-ssl', :require => 'rack/ssl' # force SSL
@@ -37,6 +36,8 @@ gem 'pivotal-tracker' @@ -37,6 +36,8 @@ gem 'pivotal-tracker'
37 gem 'ruby-fogbugz', :require => 'fogbugz' 36 gem 'ruby-fogbugz', :require => 'fogbugz'
38 # Github Issues 37 # Github Issues
39 gem 'octokit', '~> 1.0.0' 38 gem 'octokit', '~> 1.0.0'
  39 +# Gitlab
  40 +gem 'gitlab'
40 41
41 # Bitbucket Issues 42 # Bitbucket Issues
42 gem 'bitbucket_rest_api' 43 gem 'bitbucket_rest_api'
@@ -66,8 +67,6 @@ platform :ruby do @@ -66,8 +67,6 @@ platform :ruby do
66 gem 'bson_ext', '= 1.6.2' 67 gem 'bson_ext', '= 1.6.2'
67 end 68 end
68 69
69 -gem 'omniauth'  
70 -gem 'oa-core'  
71 gem 'ri_cal' 70 gem 'ri_cal'
72 gem 'yajl-ruby', :require => "yajl" 71 gem 'yajl-ruby', :require => "yajl"
73 72
@@ -77,25 +76,22 @@ group :development, :test do @@ -77,25 +76,22 @@ group :development, :test do
77 unless ENV["CI"] 76 unless ENV["CI"]
78 gem 'ruby-debug', :platform => :mri_18 77 gem 'ruby-debug', :platform => :mri_18
79 gem 'debugger', :platform => :mri_19 78 gem 'debugger', :platform => :mri_19
80 - gem 'pry'  
81 gem 'pry-rails' 79 gem 'pry-rails'
82 end 80 end
83 # gem 'rpm_contrib' 81 # gem 'rpm_contrib'
84 # gem 'newrelic_rpm' 82 # gem 'newrelic_rpm'
85 gem 'capistrano' 83 gem 'capistrano'
86 - gem 'capistrano_colors'  
87 end 84 end
88 85
89 group :test do 86 group :test do
90 gem 'capybara' 87 gem 'capybara'
91 gem 'launchy' 88 gem 'launchy'
92 - gem 'rspec', '~> 2.6'  
93 gem 'database_cleaner', '~> 0.6.0' 89 gem 'database_cleaner', '~> 0.6.0'
94 gem 'email_spec' 90 gem 'email_spec'
95 gem 'timecop' 91 gem 'timecop'
96 end 92 end
97 93
98 -group :heroku do 94 +group :heroku, :production do
99 gem 'unicorn' 95 gem 'unicorn'
100 end 96 end
101 97
@@ -108,6 +104,7 @@ group :assets do @@ -108,6 +104,7 @@ group :assets do
108 gem 'execjs' 104 gem 'execjs'
109 gem 'therubyracer', :platform => :ruby # C Ruby (MRI) or Rubinius, but NOT Windows 105 gem 'therubyracer', :platform => :ruby # C Ruby (MRI) or Rubinius, but NOT Windows
110 gem 'uglifier', '>= 1.0.3' 106 gem 'uglifier', '>= 1.0.3'
  107 + gem 'underscore-rails'
111 end 108 end
112 109
113 gem 'turbo-sprockets-rails3' 110 gem 'turbo-sprockets-rails3'
@@ -49,13 +49,12 @@ GEM @@ -49,13 +49,12 @@ GEM
49 builder (3.0.4) 49 builder (3.0.4)
50 campy (0.1.3) 50 campy (0.1.3)
51 multi_json (~> 1.0) 51 multi_json (~> 1.0)
52 - capistrano (2.13.4) 52 + capistrano (2.13.5)
53 highline 53 highline
54 net-scp (>= 1.0.0) 54 net-scp (>= 1.0.0)
55 net-sftp (>= 2.0.0) 55 net-sftp (>= 2.0.0)
56 net-ssh (>= 2.0.14) 56 net-ssh (>= 2.0.14)
57 net-ssh-gateway (>= 1.1.0) 57 net-ssh-gateway (>= 1.1.0)
58 - capistrano_colors (0.5.5)  
59 capybara (1.1.2) 58 capybara (1.1.2)
60 mime-types (>= 1.16) 59 mime-types (>= 1.16)
61 nokogiri (>= 1.3.3) 60 nokogiri (>= 1.3.3)
@@ -73,13 +72,13 @@ GEM @@ -73,13 +72,13 @@ GEM
73 rdoc 72 rdoc
74 daemons (1.1.8) 73 daemons (1.1.8)
75 database_cleaner (0.6.7) 74 database_cleaner (0.6.7)
76 - debugger (1.2.0) 75 + debugger (1.2.1)
77 columnize (>= 0.3.1) 76 columnize (>= 0.3.1)
78 debugger-linecache (~> 1.1.1) 77 debugger-linecache (~> 1.1.1)
79 - debugger-ruby_core_source (~> 1.1.3) 78 + debugger-ruby_core_source (~> 1.1.4)
80 debugger-linecache (1.1.2) 79 debugger-linecache (1.1.2)
81 debugger-ruby_core_source (>= 1.1.1) 80 debugger-ruby_core_source (>= 1.1.1)
82 - debugger-ruby_core_source (1.1.3) 81 + debugger-ruby_core_source (1.1.4)
83 devise (1.5.3) 82 devise (1.5.3)
84 bcrypt-ruby (~> 3.0) 83 bcrypt-ruby (~> 3.0)
85 orm_adapter (~> 0.0.3) 84 orm_adapter (~> 0.0.3)
@@ -169,7 +168,6 @@ GEM @@ -169,7 +168,6 @@ GEM
169 net-ssh-gateway (1.1.0) 168 net-ssh-gateway (1.1.0)
170 net-ssh (>= 1.99.1) 169 net-ssh (>= 1.99.1)
171 nokogiri (1.5.5) 170 nokogiri (1.5.5)
172 - oa-core (0.3.2)  
173 oauth2 (0.8.0) 171 oauth2 (0.8.0)
174 faraday (~> 0.8) 172 faraday (~> 0.8)
175 httpauth (~> 0.1) 173 httpauth (~> 0.1)
@@ -295,13 +293,14 @@ GEM @@ -295,13 +293,14 @@ GEM
295 treetop (1.4.10) 293 treetop (1.4.10)
296 polyglot 294 polyglot
297 polyglot (>= 0.3.1) 295 polyglot (>= 0.3.1)
298 - turbo-sprockets-rails3 (0.1.10)  
299 - railties (>= 3.1.0) 296 + turbo-sprockets-rails3 (0.2.12)
  297 + railties (>= 3.1.0, < 3.2.9)
300 sprockets (>= 2.0.0) 298 sprockets (>= 2.0.0)
301 tzinfo (0.3.33) 299 tzinfo (0.3.33)
302 uglifier (1.2.7) 300 uglifier (1.2.7)
303 execjs (>= 0.3.0) 301 execjs (>= 0.3.0)
304 multi_json (~> 1.3) 302 multi_json (~> 1.3)
  303 + underscore-rails (1.4.2.1)
305 unicorn (4.3.1) 304 unicorn (4.3.1)
306 kgio (~> 2.6) 305 kgio (~> 2.6)
307 rack 306 rack
@@ -328,7 +327,6 @@ DEPENDENCIES @@ -328,7 +327,6 @@ DEPENDENCIES
328 bson_ext (= 1.6.2) 327 bson_ext (= 1.6.2)
329 campy 328 campy
330 capistrano 329 capistrano
331 - capistrano_colors  
332 capybara 330 capybara
333 database_cleaner (~> 0.6.0) 331 database_cleaner (~> 0.6.0)
334 debugger 332 debugger
@@ -348,21 +346,16 @@ DEPENDENCIES @@ -348,21 +346,16 @@ DEPENDENCIES
348 mongo (= 1.6.2) 346 mongo (= 1.6.2)
349 mongoid (~> 2.4.10) 347 mongoid (~> 2.4.10)
350 mongoid_rails_migrations 348 mongoid_rails_migrations
351 - nokogiri  
352 - oa-core  
353 octokit (~> 1.0.0) 349 octokit (~> 1.0.0)
354 - omniauth  
355 omniauth-github 350 omniauth-github
356 oruen_redmine_client 351 oruen_redmine_client
357 pivotal-tracker 352 pivotal-tracker
358 - pry  
359 pry-rails 353 pry-rails
360 rack-ssl 354 rack-ssl
361 rack-ssl-enforcer 355 rack-ssl-enforcer
362 rails (= 3.2.8) 356 rails (= 3.2.8)
363 rails_autolink (~> 1.0.9) 357 rails_autolink (~> 1.0.9)
364 ri_cal 358 ri_cal
365 - rspec (~> 2.6)  
366 rspec-rails (~> 2.6) 359 rspec-rails (~> 2.6)
367 ruby-debug 360 ruby-debug
368 ruby-fogbugz 361 ruby-fogbugz
@@ -372,6 +365,7 @@ DEPENDENCIES @@ -372,6 +365,7 @@ DEPENDENCIES
372 timecop 365 timecop
373 turbo-sprockets-rails3 366 turbo-sprockets-rails3
374 uglifier (>= 1.0.3) 367 uglifier (>= 1.0.3)
  368 + underscore-rails
375 unicorn 369 unicorn
376 useragent (~> 0.3.1) 370 useragent (~> 0.3.1)
377 webmock 371 webmock
@@ -311,6 +311,7 @@ When upgrading Errbit, please run: @@ -311,6 +311,7 @@ When upgrading Errbit, please run:
311 311
312 ```bash 312 ```bash
313 git pull origin master # assuming origin is the github.com/errbit/errbit repo 313 git pull origin master # assuming origin is the github.com/errbit/errbit repo
  314 +bundle install
314 rake db:migrate 315 rake db:migrate
315 ``` 316 ```
316 317
@@ -376,12 +377,19 @@ card_type = Defect, status = Open, priority = Essential @@ -376,12 +377,19 @@ card_type = Defect, status = Open, priority = Essential
376 * For 'Account/Repository', the account will either be a username or organization. i.e. **errbit/errbit** 377 * For 'Account/Repository', the account will either be a username or organization. i.e. **errbit/errbit**
377 * You will also need to provide your username and password for your GitHub account. 378 * You will also need to provide your username and password for your GitHub account.
378 * (We'd really appreciate it if you wanted to help us implement OAuth instead!) 379 * (We'd really appreciate it if you wanted to help us implement OAuth instead!)
379 - 380 +
380 **Bitbucket Issues Integration** 381 **Bitbucket Issues Integration**
381 382
382 * For 'BITBUCKET REPO' field, the account will either be a username or organization. i.e. **errbit/errbit** 383 * For 'BITBUCKET REPO' field, the account will either be a username or organization. i.e. **errbit/errbit**
383 * You will also need to provide your username and password for your Bitbucket account. 384 * You will also need to provide your username and password for your Bitbucket account.
384 385
  386 +**Gitlab Issues Integration**
  387 +
  388 +* Account is the host of your gitlab installation. i.e. **http://gitlab.example.com**
  389 +* To authenticate, Errbit uses token-based authentication. Get your API Key in your user settings (or create special user for this purpose)
  390 +* You also need to provide project name (shortname) or ID (number) for issues to be created
  391 +* **Currently (as of 3.0), Gitlab has 2000 character limit for issue description.** It is necessary to turn it off at your instance, because Errbit issues body is much longer. Please comment validation line in issue model in models folder https://github.com/gitlabhq/gitlabhq/blob/master/app/models/issue.rb#L10
  392 +
385 393
386 394
387 What if Errbit has an error? 395 What if Errbit has an error?
@@ -436,10 +444,15 @@ Special Thanks @@ -436,10 +444,15 @@ Special Thanks
436 -------------- 444 --------------
437 445
438 * [Michael Parenteau](http://michaelparenteau.com) - For rocking the Errbit design and providing a great user experience. 446 * [Michael Parenteau](http://michaelparenteau.com) - For rocking the Errbit design and providing a great user experience.
439 -* [Nick Recobra aka oruen](https://github.com/oruen) - Nick is Errbit's first core contributor. He's been working hard at making Errbit more awesome. 447 +* [Nick Recobra (@oruen)](https://github.com/oruen) - Nick is Errbit's first core contributor. He's been working hard at making Errbit more awesome.
  448 +* [Nathan Broadbent (@ndbroadbent)](https://github.com/ndbroadbent) - Maintaining Errbit and contributing many features
  449 +* [Vasiliy Ermolovich (@nashby)](https://github.com/nashby) - Contributing and helping to resolve issues and pull requests
  450 +* [Marcin Ciunelis (@martinciu)](https://github.com/martinciu) - Helping to improve Errbit's architecture
440 * [Relevance](http://thinkrelevance.com) - For giving me Open-source Fridays to work on Errbit and all my awesome co-workers for giving feedback and inspiration. 451 * [Relevance](http://thinkrelevance.com) - For giving me Open-source Fridays to work on Errbit and all my awesome co-workers for giving feedback and inspiration.
441 * [Thoughtbot](http://thoughtbot.com) - For being great open-source advocates and setting the bar with [Airbrake](http://airbrakeapp.com). 452 * [Thoughtbot](http://thoughtbot.com) - For being great open-source advocates and setting the bar with [Airbrake](http://airbrakeapp.com).
442 453
  454 +See the [contributors graph](https://github.com/errbit/errbit/graphs/contributors) for further details.
  455 +
443 456
444 Contributing 457 Contributing
445 ------------ 458 ------------
app/assets/images/alerts/help.gif

1.54 KB | W: | H:

1.53 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/alerts/important.gif

1.46 KB | W: | H:

1.45 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/alerts/info.gif

1.45 KB | W: | H:

1.44 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/alerts/title.gif

317 Bytes | W: | H:

309 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/bitbucket_create.png

2.54 KB | W: | H:

2.43 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/bitbucket_goto.png

3.61 KB | W: | H:

2.45 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/bitbucket_inactive.png

1.95 KB | W: | H:

1.23 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/campfire_create.png

3.19 KB | W: | H:

2.03 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/campfire_goto.png

3.19 KB | W: | H:

2.03 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/campfire_inactive.png

2.8 KB | W: | H:

1.58 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/fogbugz_create.png

4.78 KB | W: | H:

1.89 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/fogbugz_goto.png

4.78 KB | W: | H:

1.89 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/fogbugz_inactive.png

4.58 KB | W: | H:

1.04 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/github_create.png

2.3 KB | W: | H:

2.14 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/github_goto.png

2.3 KB | W: | H:

2.14 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/github_inactive.png

2.03 KB | W: | H:

1.05 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/gitlab_create.png 0 → 100644

4.47 KB

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

4.47 KB

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

4.35 KB

app/assets/images/gtalk_create.png

4.62 KB | W: | H:

1.71 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/gtalk_goto.png

4.62 KB | W: | H:

1.71 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/gtalk_inactive.png

4.07 KB | W: | H:

1.28 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/hipchat_create.png

2.05 KB | W: | H:

1.59 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/hipchat_goto.png

2.05 KB | W: | H:

1.59 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/hipchat_inactive.png

1.16 KB | W: | H:

904 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/hoiio_create.png

1.72 KB | W: | H:

1.52 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/hoiio_goto.png

1.72 KB | W: | H:

1.52 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/hoiio_inactive.png

874 Bytes | W: | H:

783 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/lighthouseapp_create.png

1.57 KB | W: | H:

1.38 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/lighthouseapp_goto.png

1.61 KB | W: | H:

1.4 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/lighthouseapp_inactive.png

1.57 KB | W: | H:

1.16 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/loader.gif

1.7 KB | W: | H:

1.6 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/mingle_create.png

2.41 KB | W: | H:

2.13 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/mingle_goto.png

2.41 KB | W: | H:

2.13 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/mingle_inactive.png

1.96 KB | W: | H:

1.38 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/none_create.png

799 Bytes | W: | H:

548 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/none_inactive.png

818 Bytes | W: | H:

558 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/pivotal_create.png

1.5 KB | W: | H:

1.26 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/pivotal_goto.png

1.52 KB | W: | H:

1.29 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/pivotal_inactive.png

1.56 KB | W: | H:

1.1 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/pushover_create.png

1.91 KB | W: | H:

1.56 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/pushover_goto.png

1.91 KB | W: | H:

1.56 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/pushover_inactive.png

1.02 KB | W: | H:

898 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/redmine_create.png

1.47 KB | W: | H:

1.33 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/redmine_goto.png

1.52 KB | W: | H:

1.35 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/redmine_inactive.png

1.52 KB | W: | H:

1.13 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/images/thumbs-up.png

1.42 KB | W: | H:

1.28 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/javascripts/application.js.erb
1 //= require jquery 1 //= require jquery
2 -//= require underscore-1.1.6 2 +//= require underscore
3 //= require rails 3 //= require rails
4 //= require form 4 //= require form
5 //= require jquery.pjax 5 //= require jquery.pjax
app/assets/javascripts/underscore-1.1.6.js
@@ -1,26 +0,0 @@ @@ -1,26 +0,0 @@
1 -// Underscore.js 1.1.6  
2 -// (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.  
3 -// Underscore is freely distributable under the MIT license.  
4 -// Portions of Underscore are inspired or borrowed from Prototype,  
5 -// Oliver Steele's Functional, and John Resig's Micro-Templating.  
6 -// For all details and documentation:  
7 -// http://documentcloud.github.com/underscore  
8 -(function(){var p=this,C=p._,m={},i=Array.prototype,n=Object.prototype,f=i.slice,D=i.unshift,E=n.toString,l=n.hasOwnProperty,s=i.forEach,t=i.map,u=i.reduce,v=i.reduceRight,w=i.filter,x=i.every,y=i.some,o=i.indexOf,z=i.lastIndexOf;n=Array.isArray;var F=Object.keys,q=Function.prototype.bind,b=function(a){return new j(a)};typeof module!=="undefined"&&module.exports?(module.exports=b,b._=b):p._=b;b.VERSION="1.1.6";var h=b.each=b.forEach=function(a,c,d){if(a!=null)if(s&&a.forEach===s)a.forEach(c,d);else if(b.isNumber(a.length))for(var e=  
9 -0,k=a.length;e<k;e++){if(c.call(d,a[e],e,a)===m)break}else for(e in a)if(l.call(a,e)&&c.call(d,a[e],e,a)===m)break};b.map=function(a,c,b){var e=[];if(a==null)return e;if(t&&a.map===t)return a.map(c,b);h(a,function(a,g,G){e[e.length]=c.call(b,a,g,G)});return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var k=d!==void 0;a==null&&(a=[]);if(u&&a.reduce===u)return e&&(c=b.bind(c,e)),k?a.reduce(c,d):a.reduce(c);h(a,function(a,b,f){!k&&b===0?(d=a,k=!0):d=c.call(e,d,a,b,f)});if(!k)throw new TypeError("Reduce of empty array with no initial value");  
10 -return d};b.reduceRight=b.foldr=function(a,c,d,e){a==null&&(a=[]);if(v&&a.reduceRight===v)return e&&(c=b.bind(c,e)),d!==void 0?a.reduceRight(c,d):a.reduceRight(c);a=(b.isArray(a)?a.slice():b.toArray(a)).reverse();return b.reduce(a,c,d,e)};b.find=b.detect=function(a,c,b){var e;A(a,function(a,g,f){if(c.call(b,a,g,f))return e=a,!0});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(w&&a.filter===w)return a.filter(c,b);h(a,function(a,g,f){c.call(b,a,g,f)&&(e[e.length]=a)});return e};  
11 -b.reject=function(a,c,b){var e=[];if(a==null)return e;h(a,function(a,g,f){c.call(b,a,g,f)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=!0;if(a==null)return e;if(x&&a.every===x)return a.every(c,b);h(a,function(a,g,f){if(!(e=e&&c.call(b,a,g,f)))return m});return e};var A=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=!1;if(a==null)return e;if(y&&a.some===y)return a.some(c,d);h(a,function(a,b,f){if(e=c.call(d,a,b,f))return m});return e};b.include=b.contains=function(a,c){var b=  
12 -!1;if(a==null)return b;if(o&&a.indexOf===o)return a.indexOf(c)!=-1;A(a,function(a){if(b=a===c)return!0});return b};b.invoke=function(a,c){var d=f.call(arguments,2);return b.map(a,function(a){return(c.call?c||a:a[c]).apply(a,d)})};b.pluck=function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);var e={computed:-Infinity};h(a,function(a,b,f){b=c?c.call(d,a,b,f):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,  
13 -c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};h(a,function(a,b,f){b=c?c.call(d,a,b,f):a;b<e.computed&&(e={value:a,computed:b})});return e.value};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,f){return{value:a,criteria:c.call(d,a,b,f)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c<d?-1:c>d?1:0}),"value")};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=  
14 -function(a){if(!a)return[];if(a.toArray)return a.toArray();if(b.isArray(a))return a;if(b.isArguments(a))return f.call(a);return b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?f.call(a,0,b):a[0]};b.rest=b.tail=function(a,b,d){return f.call(a,b==null||d?1:b)};b.last=function(a){return a[a.length-1]};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a){return b.reduce(a,function(a,d){if(b.isArray(d))return a.concat(b.flatten(d));  
15 -a[a.length]=d;return a},[])};b.without=function(a){var c=f.call(arguments,1);return b.filter(a,function(a){return!b.include(c,a)})};b.uniq=b.unique=function(a,c){return b.reduce(a,function(a,e,f){if(0==f||(c===!0?b.last(a)!=e:!b.include(a,e)))a[a.length]=e;return a},[])};b.intersect=function(a){var c=f.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.zip=function(){for(var a=f.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),  
16 -e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(o&&a.indexOf===o)return a.indexOf(c);d=0;for(e=a.length;d<e;d++)if(a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(z&&a.lastIndexOf===z)return a.lastIndexOf(b);for(var d=a.length;d--;)if(a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);d=arguments[2]||1;for(var e=Math.max(Math.ceil((b-a)/  
17 -d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g};b.bind=function(a,b){if(a.bind===q&&q)return q.apply(a,f.call(arguments,1));var d=f.call(arguments,2);return function(){return a.apply(b,d.concat(f.call(arguments)))}};b.bindAll=function(a){var c=f.call(arguments,1);c.length==0&&(c=b.functions(a));h(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var b=c.apply(this,arguments);return l.call(d,b)?d[b]:d[b]=a.apply(this,arguments)}};b.delay=  
18 -function(a,b){var d=f.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(f.call(arguments,1)))};var B=function(a,b,d){var e;return function(){var f=this,g=arguments,h=function(){e=null;a.apply(f,g)};d&&clearTimeout(e);if(d||!e)e=setTimeout(h,b)}};b.throttle=function(a,b){return B(a,b,!1)};b.debounce=function(a,b){return B(a,b,!0)};b.once=function(a){var b=!1,d;return function(){if(b)return d;b=!0;return d=a.apply(this,arguments)}};  
19 -b.wrap=function(a,b){return function(){var d=[a].concat(f.call(arguments));return b.apply(this,d)}};b.compose=function(){var a=f.call(arguments);return function(){for(var b=f.call(arguments),d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return function(){if(--a<1)return b.apply(this,arguments)}};b.keys=F||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var b=[],d;for(d in a)l.call(a,d)&&(b[b.length]=d);return b};b.values=function(a){return b.map(a,  
20 -b.identity)};b.functions=b.methods=function(a){return b.filter(b.keys(a),function(c){return b.isFunction(a[c])}).sort()};b.extend=function(a){h(f.call(arguments,1),function(b){for(var d in b)b[d]!==void 0&&(a[d]=b[d])});return a};b.defaults=function(a){h(f.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,c){if(a===c)return!0;var d=typeof a;if(d!=  
21 -typeof c)return!1;if(a==c)return!0;if(!a&&c||a&&!c)return!1;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual)return a.isEqual(c);if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(b.isNaN(a)&&b.isNaN(c))return!1;if(b.isRegExp(a)&&b.isRegExp(c))return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;if(d!=="object")return!1;if(a.length&&a.length!==c.length)return!1;d=b.keys(a);var e=b.keys(c);if(d.length!=e.length)return!1;  
22 -for(var f in a)if(!(f in c)||!b.isEqual(a[f],c[f]))return!1;return!0};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(l.call(a,c))return!1;return!0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=n||function(a){return E.call(a)==="[object Array]"};b.isArguments=function(a){return!(!a||!l.call(a,"callee"))};b.isFunction=function(a){return!(!a||!a.constructor||!a.call||!a.apply)};b.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)};  
23 -b.isNumber=function(a){return!!(a===0||a&&a.toExponential&&a.toFixed)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===!0||a===!1};b.isDate=function(a){return!(!a||!a.getTimezoneOffset||!a.setUTCFullYear)};b.isRegExp=function(a){return!(!a||!a.test||!a.exec||!(a.ignoreCase||a.ignoreCase===!1))};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.noConflict=function(){p._=C;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=  
24 -0;e<a;e++)b.call(d,e)};b.mixin=function(a){h(b.functions(a),function(c){H(c,b[c]=a[c])})};var I=0;b.uniqueId=function(a){var b=I++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g};b.template=function(a,c){var d=b.templateSettings;d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.interpolate,function(a,b){return"',"+b.replace(/\\'/g,"'")+",'"}).replace(d.evaluate||  
25 -null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+"__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');";d=new Function("obj",d);return c?d(c):d};var j=function(a){this._wrapped=a};b.prototype=j.prototype;var r=function(a,c){return c?b(a).chain():a},H=function(a,c){j.prototype[a]=function(){var a=f.call(arguments);D.call(a,this._wrapped);return r(c.apply(b,a),this._chain)}};b.mixin(b);h(["pop","push","reverse","shift","sort",  
26 -"splice","unshift"],function(a){var b=i[a];j.prototype[a]=function(){b.apply(this._wrapped,arguments);return r(this._wrapped,this._chain)}});h(["concat","join","slice"],function(a){var b=i[a];j.prototype[a]=function(){return r(b.apply(this._wrapped,arguments),this._chain)}});j.prototype.chain=function(){this._chain=!0;return this};j.prototype.value=function(){return this._wrapped}})();  
27 \ No newline at end of file 0 \ No newline at end of file
app/assets/stylesheets/errbit.css
@@ -586,8 +586,8 @@ div.issue_tracker.nested label.r_on, div.issue_tracker.nested label.r_on:hover, @@ -586,8 +586,8 @@ div.issue_tracker.nested label.r_on, div.issue_tracker.nested label.r_on:hover,
586 table.apps tbody tr:hover td ,table.errs tbody tr:hover td { background-color: #F2F2F2;} 586 table.apps tbody tr:hover td ,table.errs tbody tr:hover td { background-color: #F2F2F2;}
587 587
588 table.apps td.name, table.errs td.message { width: 100%; } 588 table.apps td.name, table.errs td.message { width: 100%; }
589 -table.apps td { padding: 16px 25px; }  
590 -table.apps th { padding: 10px 25px; } 589 +table.apps td { padding: 16px 20px; }
  590 +table.apps th { padding: 10px 20px; }
591 591
592 table.apps td.issue_tracker, table.apps td.count, table.apps td.deploy { 592 table.apps td.issue_tracker, table.apps td.count, table.apps td.deploy {
593 text-align: center; 593 text-align: center;
@@ -894,6 +894,11 @@ table.errs tr td.message .inline_comment em.commenter { @@ -894,6 +894,11 @@ table.errs tr td.message .inline_comment em.commenter {
894 color: #777; 894 color: #777;
895 } 895 }
896 896
  897 +textarea#comment_body {
  898 + width: 420px;
  899 + height: 80px;
  900 +}
  901 +
897 .current.asc:after { content: ' ↑'; } 902 .current.asc:after { content: ' ↑'; }
898 .current.desc:after { content: ' ↓'; } 903 .current.desc:after { content: ' ↓'; }
899 904
app/assets/stylesheets/images/button-bg.png

148 Bytes | W: | H:

108 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/content-fade.png

174 Bytes | W: | H:

134 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/error-badge-bg.png

119 Bytes | W: | H:

75 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/header.png

196 Bytes | W: | H:

109 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/icons/add.png

1.04 KB | W: | H:

908 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/icons/briefcase.png

675 Bytes | W: | H:

635 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/icons/bullet-red-sm.png

417 Bytes | W: | H:

338 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/icons/cross.png

473 Bytes | W: | H:

285 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/icons/edit.png

1.22 KB | W: | H:

1.13 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/icons/error.png

1.05 KB | W: | H:

993 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/icons/github.png

1.94 KB | W: | H:

1.57 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/icons/ical.png

1.86 KB | W: | H:

1.59 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/icons/notice.png

157 Bytes | W: | H:

112 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/icons/required.png

250 Bytes | W: | H:

198 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/icons/right-arrow.png

1.11 KB | W: | H:

847 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/icons/success.png

1.06 KB | W: | H:

912 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/icons/thumbs-up.png

1.42 KB | W: | H:

1.28 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/icons/trash.png

1.65 KB | W: | H:

1.59 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/icons/unlink_github.png

2.02 KB | W: | H:

1.87 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/icons/up.png

1.11 KB | W: | H:

837 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/icons/user.png

877 Bytes | W: | H:

822 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/icons/warning.png

674 Bytes | W: | H:

569 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/logo.png

3.21 KB | W: | H:

2.96 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/notebook.png

133 Bytes | W: | H:

91 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/images/resolved-badge-bg.png

119 Bytes | W: | H:

75 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
app/assets/stylesheets/issue_tracker_icons.css.erb
@@ -3,16 +3,16 @@ @@ -3,16 +3,16 @@
3 3
4 <% trackers.each do |tracker| %> 4 <% trackers.each do |tracker| %>
5 div.issue_tracker.nested label.<%= tracker %> { 5 div.issue_tracker.nested label.<%= tracker %> {
6 - background: url(/assets/<%= tracker %>_inactive.png) no-repeat; 6 + background: url(<%= tracker %>_inactive.png) no-repeat;
7 } 7 }
8 div.issue_tracker.nested label.r_on.<%= tracker %> { 8 div.issue_tracker.nested label.r_on.<%= tracker %> {
9 - background: url(/assets/<%= tracker %>_create.png) no-repeat; 9 + background: url(<%= tracker %>_create.png) no-repeat;
10 } 10 }
11 #action-bar a.<%= tracker %>_create { 11 #action-bar a.<%= tracker %>_create {
12 - background: transparent url(/assets/<%= tracker %>_create.png) 6px 5px no-repeat; 12 + background: transparent url(<%= tracker %>_create.png) 6px 5px no-repeat;
13 } 13 }
14 #action-bar a.<%= tracker %>_goto { 14 #action-bar a.<%= tracker %>_goto {
15 - background: transparent url(/assets/<%= tracker %>_goto.png) 6px 5px no-repeat; 15 + background: transparent url(<%= tracker %>_goto.png) 6px 5px no-repeat;
16 } 16 }
17 <% end %> 17 <% end %>
18 18
app/assets/stylesheets/jquery.alerts.css
@@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
17 text-align: center; 17 text-align: center;
18 line-height: 1.75em; 18 line-height: 1.75em;
19 color: #666; 19 color: #666;
20 - background: #CCC url(/images/alerts/title.gif) top repeat-x; 20 + background: #CCC url(alerts/title.gif) top repeat-x;
21 border: solid 1px #FFF; 21 border: solid 1px #FFF;
22 border-bottom: solid 1px #999; 22 border-bottom: solid 1px #999;
23 cursor: default; 23 cursor: default;
@@ -26,21 +26,21 @@ @@ -26,21 +26,21 @@
26 } 26 }
27 27
28 #popup_content { 28 #popup_content {
29 - background: 16px 16px no-repeat url(/images/alerts/info.gif); 29 + background: 16px 16px no-repeat url(alerts/info.gif);
30 padding: 1em 1.75em; 30 padding: 1em 1.75em;
31 margin: 0em; 31 margin: 0em;
32 } 32 }
33 33
34 #popup_content.alert { 34 #popup_content.alert {
35 - background-image: url(/images/alerts/info.gif); 35 + background-image: url(alerts/info.gif);
36 } 36 }
37 37
38 #popup_content.confirm { 38 #popup_content.confirm {
39 - background-image: url(/images/alerts/important.gif); 39 + background-image: url(alerts/important.gif);
40 } 40 }
41 41
42 #popup_content.prompt { 42 #popup_content.prompt {
43 - background-image: url(/images/alerts/help.gif); 43 + background-image: url(alerts/help.gif);
44 } 44 }
45 45
46 #popup_message { 46 #popup_message {
app/assets/stylesheets/notification_service_icons.css.erb
@@ -3,16 +3,16 @@ @@ -3,16 +3,16 @@
3 3
4 <% notification_services.each do |notification_service| %> 4 <% notification_services.each do |notification_service| %>
5 div.notification_service.nested label.<%= notification_service %> { 5 div.notification_service.nested label.<%= notification_service %> {
6 - background: url(/assets/<%= notification_service %>_inactive.png) no-repeat; 6 + background: url(<%= notification_service %>_inactive.png) no-repeat;
7 } 7 }
8 div.notification_service.nested label.r_on.<%= notification_service %> { 8 div.notification_service.nested label.r_on.<%= notification_service %> {
9 - background: url(/assets/<%= notification_service %>_create.png) no-repeat; 9 + background: url(<%= notification_service %>_create.png) no-repeat;
10 } 10 }
11 #action-bar a.<%= notification_service %>_create { 11 #action-bar a.<%= notification_service %>_create {
12 - background: transparent url(/assets/<%= notification_service %>_create.png) 6px 5px no-repeat; 12 + background: transparent url(<%= notification_service %>_create.png) 6px 5px no-repeat;
13 } 13 }
14 #action-bar a.<%= notification_service %>_goto { 14 #action-bar a.<%= notification_service %>_goto {
15 - background: transparent url(/assets/<%= notification_service %>_goto.png) 6px 5px no-repeat; 15 + background: transparent url(<%= notification_service %>_goto.png) 6px 5px no-repeat;
16 } 16 }
17 <% end %> 17 <% end %>
18 18
app/controllers/apps_controller.rb
@@ -52,23 +52,7 @@ class AppsController &lt; InheritedResources::Base @@ -52,23 +52,7 @@ class AppsController &lt; InheritedResources::Base
52 52
53 protected 53 protected
54 def collection 54 def collection
55 - @unresolved_counts, @problem_counts = {}, {}  
56 - @apps ||= begin  
57 - apps = end_of_association_chain.all  
58 -  
59 - # Cache counts for unresolved errs and problems  
60 - apps.each do |app|  
61 - @unresolved_counts[app.id] ||= app.problems.unresolved.count  
62 - @problem_counts[app.id] ||= app.problems.count  
63 - end  
64 -  
65 - # Sort apps by number of unresolved errs, then problem counts.  
66 - apps.sort do |a,b|  
67 - (@unresolved_counts[b.id] <=> @unresolved_counts[a.id]).nonzero? ||  
68 - (@problem_counts[b.id] <=> @problem_counts[a.id]).nonzero? ||  
69 - a.name <=> b.name  
70 - end  
71 - end 55 + @apps ||= end_of_association_chain.all.sort
72 end 56 end
73 57
74 def initialize_subclassed_issue_tracker 58 def initialize_subclassed_issue_tracker
@@ -80,7 +64,7 @@ class AppsController &lt; InheritedResources::Base @@ -80,7 +64,7 @@ class AppsController &lt; InheritedResources::Base
80 end 64 end
81 end 65 end
82 66
83 - def initialize_subclassed_notification_service 67 + def initialize_subclassed_notification_service
84 # set the app's notification service 68 # set the app's notification service
85 if params[:app][:notification_service_attributes] && notification_type = params[:app][:notification_service_attributes][:type] 69 if params[:app][:notification_service_attributes] && notification_type = params[:app][:notification_service_attributes][:type]
86 if NotificationService.subclasses.map(&:name).concat(["NotificationService"]).include?(notification_type) 70 if NotificationService.subclasses.map(&:name).concat(["NotificationService"]).include?(notification_type)
app/controllers/problems_controller.rb
1 class ProblemsController < ApplicationController 1 class ProblemsController < ApplicationController
2 - include ActionView::Helpers::TextHelper  
3 -  
4 before_filter :find_app, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several] 2 before_filter :find_app, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several]
5 before_filter :find_problem, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several] 3 before_filter :find_problem, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several]
6 before_filter :find_selected_problems, :only => [:destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several] 4 before_filter :find_selected_problems, :only => [:destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several]
@@ -37,36 +35,10 @@ class ProblemsController &lt; ApplicationController @@ -37,36 +35,10 @@ class ProblemsController &lt; ApplicationController
37 end 35 end
38 36
39 def create_issue 37 def create_issue
40 - # Create an issue on GitHub using user's github token  
41 - if params[:tracker] == 'user_github'  
42 - if !@app.github_repo?  
43 - flash[:error] = "This app doesn't have a GitHub repo set up."  
44 - elsif !current_user.github_account?  
45 - flash[:error] = "You haven't linked your Github account."  
46 - else  
47 - @tracker = GithubIssuesTracker.new(  
48 - :app => @app,  
49 - :username => current_user.github_login,  
50 - :oauth_token => current_user.github_oauth_token  
51 - )  
52 - end  
53 -  
54 - # Or, create an issue using the App's issue tracker  
55 - elsif @app.issue_tracker_configured?  
56 - @tracker = @app.issue_tracker 38 + issue_creation = IssueCreation.new(@problem, current_user, params[:tracker])
57 39
58 - # Otherwise, display error about missing tracker configuration.  
59 - else  
60 - flash[:error] = "This app has no issue tracker setup."  
61 - end  
62 -  
63 - if flash[:error].blank? && @tracker  
64 - begin  
65 - @tracker.create_issue @problem, current_user  
66 - rescue Exception => ex  
67 - Rails.logger.error "Error during issue creation: " << ex.message  
68 - flash[:error] = "There was an error during issue creation: #{ex.message}"  
69 - end 40 + unless issue_creation.execute
  41 + flash[:error] = issue_creation.errors[:base].first
70 end 42 end
71 43
72 redirect_to app_problem_path(@app, @problem) 44 redirect_to app_problem_path(@app, @problem)
@@ -87,13 +59,13 @@ class ProblemsController &lt; ApplicationController @@ -87,13 +59,13 @@ class ProblemsController &lt; ApplicationController
87 59
88 def resolve_several 60 def resolve_several
89 @selected_problems.each(&:resolve!) 61 @selected_problems.each(&:resolve!)
90 - flash[:success] = "Great news everyone! #{pluralize(@selected_problems.count, 'err has', 'errs have')} been resolved." 62 + flash[:success] = "Great news everyone! #{I18n.t(:n_errs_have, :count => @selected_problems.count)} been resolved."
91 redirect_to :back 63 redirect_to :back
92 end 64 end
93 65
94 def unresolve_several 66 def unresolve_several
95 @selected_problems.each(&:unresolve!) 67 @selected_problems.each(&:unresolve!)
96 - flash[:success] = "#{pluralize(@selected_problems.count, 'err has', 'errs have')} been unresolved." 68 + flash[:success] = "#{I18n.t(:n_errs_have, :count => @selected_problems.count)} been unresolved."
97 redirect_to :back 69 redirect_to :back
98 end 70 end
99 71
@@ -109,13 +81,13 @@ class ProblemsController &lt; ApplicationController @@ -109,13 +81,13 @@ class ProblemsController &lt; ApplicationController
109 81
110 def unmerge_several 82 def unmerge_several
111 all = @selected_problems.map(&:unmerge!).flatten 83 all = @selected_problems.map(&:unmerge!).flatten
112 - flash[:success] = "#{pluralize(all.length, 'err has', 'errs have')} been unmerged." 84 + flash[:success] = "#{I18n.t(:n_errs_have, :count => all.length)} been unmerged."
113 redirect_to :back 85 redirect_to :back
114 end 86 end
115 87
116 def destroy_several 88 def destroy_several
117 nb_problem_destroy = ProblemDestroy.execute(@selected_problems) 89 nb_problem_destroy = ProblemDestroy.execute(@selected_problems)
118 - flash[:notice] = "#{pluralize(nb_problem_destroy, 'err has', 'errs have')} been deleted." 90 + flash[:notice] = "#{I18n.t(:n_errs_have, :count => nb_problem_destroy)} been deleted."
119 redirect_to :back 91 redirect_to :back
120 end 92 end
121 93
app/helpers/backtrace_line_helper.rb
@@ -19,13 +19,13 @@ module BacktraceLineHelper @@ -19,13 +19,13 @@ module BacktraceLineHelper
19 19
20 def link_to_github(line, text = nil) 20 def link_to_github(line, text = nil)
21 return unless line.app.github_repo? 21 return unless line.app.github_repo?
22 - href = "%s#L%s" % [line.app.github_url_to_file(line.file), line.number] 22 + href = "%s#L%s" % [line.app.github_url_to_file(line.decorated_path + line.file_name), line.number]
23 link_to(text || line.file_name, href, :target => '_blank') 23 link_to(text || line.file_name, href, :target => '_blank')
24 end 24 end
25 25
26 def link_to_bitbucket(line, text = nil) 26 def link_to_bitbucket(line, text = nil)
27 return unless line.app.bitbucket_repo? 27 return unless line.app.bitbucket_repo?
28 - href = "%s#cl-%s" % [line.app.bitbucket_url_to_file(line.file), line.number] 28 + href = "%s#cl-%s" % [line.app.bitbucket_url_to_file(line.decorated_path + line.file_name), line.number]
29 link_to(text || line.file_name, href, :target => '_blank') 29 link_to(text || line.file_name, href, :target => '_blank')
30 end 30 end
31 31
app/helpers/notices_helper.rb
1 # encoding: utf-8 1 # encoding: utf-8
2 module NoticesHelper 2 module NoticesHelper
3 def notice_atom_summary(notice) 3 def notice_atom_summary(notice)
4 - render "notices/atom_entry.html.haml", :notice => notice 4 + render "notices/atom_entry", :notice => notice
5 end 5 end
6 end 6 end
7 -  
app/interactors/issue_creation.rb 0 → 100644
@@ -0,0 +1,55 @@ @@ -0,0 +1,55 @@
  1 +class IssueCreation
  2 + include ActiveModel::Validations
  3 +
  4 + attr_reader :problem, :user, :tracker_name
  5 +
  6 + delegate :app, :to => :problem
  7 +
  8 + def initialize(problem, user, tracker_name)
  9 + @problem = problem
  10 + @user = user
  11 + @tracker_name = tracker_name
  12 + end
  13 +
  14 + def tracker
  15 + return @tracker if @tracker
  16 +
  17 + # Create an issue on GitHub using user's github token
  18 + if tracker_name == 'user_github'
  19 + if !app.github_repo?
  20 + errors.add :base, "This app doesn't have a GitHub repo set up."
  21 + elsif !user.github_account?
  22 + errors.add :base, "You haven't linked your Github account."
  23 + else
  24 + @tracker = GithubIssuesTracker.new(
  25 + :app => app,
  26 + :username => user.github_login,
  27 + :oauth_token => user.github_oauth_token
  28 + )
  29 + end
  30 +
  31 + # Or, create an issue using the App's issue tracker
  32 + elsif app.issue_tracker_configured?
  33 + @tracker = app.issue_tracker
  34 +
  35 + # Otherwise, display error about missing tracker configuration.
  36 + else
  37 + errors.add :base, "This app has no issue tracker setup."
  38 + end
  39 +
  40 + @tracker
  41 + end
  42 +
  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 +
  53 + errors.empty?
  54 + end
  55 +end
app/models/app.rb
1 class App 1 class App
2 include Mongoid::Document 2 include Mongoid::Document
3 include Mongoid::Timestamps 3 include Mongoid::Timestamps
  4 + include Comparable
4 5
5 field :name, :type => String 6 field :name, :type => String
6 field :api_key 7 field :api_key
@@ -98,12 +99,16 @@ class App @@ -98,12 +99,16 @@ class App
98 99
99 # Legacy apps don't have notify_on_errs and notify_on_deploys params 100 # Legacy apps don't have notify_on_errs and notify_on_deploys params
100 def notify_on_errs 101 def notify_on_errs
101 - !(self[:notify_on_errs] == false) 102 + !(super == false)
102 end 103 end
103 alias :notify_on_errs? :notify_on_errs 104 alias :notify_on_errs? :notify_on_errs
104 105
  106 + def notifiable?
  107 + notify_on_errs? && notification_recipients.any?
  108 + end
  109 +
105 def notify_on_deploys 110 def notify_on_deploys
106 - !(self[:notify_on_deploys] == false) 111 + !(super == false)
107 end 112 end
108 alias :notify_on_deploys? :notify_on_deploys 113 alias :notify_on_deploys? :notify_on_deploys
109 114
@@ -137,11 +142,11 @@ class App @@ -137,11 +142,11 @@ class App
137 142
138 143
139 def issue_tracker_configured? 144 def issue_tracker_configured?
140 - !!(issue_tracker && issue_tracker.class < IssueTracker && issue_tracker.project_id.present?) 145 + !!(issue_tracker.class < IssueTracker && issue_tracker.configured?)
141 end 146 end
142 147
143 def notification_service_configured? 148 def notification_service_configured?
144 - !!(notification_service && notification_service.class < NotificationService && notification_service.api_token.present?) 149 + !!(notification_service.class < NotificationService && notification_service.configured?)
145 end 150 end
146 151
147 152
@@ -169,6 +174,25 @@ class App @@ -169,6 +174,25 @@ class App
169 end 174 end
170 end 175 end
171 176
  177 + def unresolved_count
  178 + @unresolved_count ||= problems.unresolved.count
  179 + end
  180 +
  181 + def problem_count
  182 + @problem_count ||= problems.count
  183 + end
  184 +
  185 + # Compare by number of unresolved errs, then problem counts.
  186 + def <=>(other)
  187 + (other.unresolved_count <=> unresolved_count).nonzero? ||
  188 + (other.problem_count <=> problem_count).nonzero? ||
  189 + name <=> other.name
  190 + end
  191 +
  192 + def email_at_notices
  193 + Errbit::Config.per_app_email_at_notices ? super : Errbit::Config.email_at_notices
  194 + end
  195 +
172 protected 196 protected
173 197
174 def store_cached_attributes_on_problems 198 def store_cached_attributes_on_problems
app/models/issue_tracker.rb
@@ -35,5 +35,9 @@ class IssueTracker @@ -35,5 +35,9 @@ class IssueTracker
35 Label = '' 35 Label = ''
36 def self.label; self::Label; end 36 def self.label; self::Label; end
37 def label; self.class.label; end 37 def label; self.class.label; end
  38 +
  39 + def configured?
  40 + project_id.present?
  41 + end
38 end 42 end
39 43
app/models/issue_trackers/github_issues_tracker.rb
@@ -35,7 +35,7 @@ if defined? Octokit @@ -35,7 +35,7 @@ if defined? Octokit
35 end 35 end
36 36
37 begin 37 begin
38 - issue = client.create_issue(project_id, issue_title(problem), body_template.result(binding).unpack('C*').pack('U*'), options = {}) 38 + issue = client.create_issue(project_id, issue_title(problem), body_template.result(binding).unpack('C*').pack('U*'))
39 problem.update_attributes( 39 problem.update_attributes(
40 :issue_link => issue.html_url, 40 :issue_link => issue.html_url,
41 :issue_type => Label 41 :issue_type => Label
@@ -54,4 +54,4 @@ if defined? Octokit @@ -54,4 +54,4 @@ if defined? Octokit
54 "https://github.com/#{project_id}/issues" 54 "https://github.com/#{project_id}/issues"
55 end 55 end
56 end 56 end
57 -end  
58 \ No newline at end of file 57 \ No newline at end of file
  58 +end
app/models/issue_trackers/gitlab_tracker.rb 0 → 100644
@@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
  1 +if defined? Gitlab
  2 + class IssueTrackers::GitlabTracker < IssueTracker
  3 + Label = "gitlab"
  4 + Fields = [
  5 + [:account, {
  6 + :label => "Gitlab URL",
  7 + :placeholder => "e.g. https://example.net"
  8 + }],
  9 + [:api_token, {
  10 + :placeholder => "API Token for your account"
  11 + }],
  12 + [:project_id, {
  13 + :label => "Ticket Project Short Name / ID",
  14 + :placeholder => "Gitlab Project where issues will be created"
  15 + }]
  16 + ]
  17 +
  18 + def check_params
  19 + if Fields.detect {|f| self[f[0]].blank?}
  20 + errors.add :base, 'You must specify your Gitlab URL, API token and Project ID'
  21 + end
  22 + end
  23 +
  24 + def create_issue(problem, reported_by = nil)
  25 + Gitlab.configure do |config|
  26 + config.endpoint = "#{account}/api/v2"
  27 + config.private_token = api_token
  28 + config.user_agent = 'Errbit User Agent'
  29 + end
  30 + title = issue_title problem
  31 + description = body_template.result(binding)
  32 + Gitlab.create_issue(project_id, title, { :description => description, :labels => "errbit" } )
  33 + end
  34 +
  35 + def body_template
  36 + @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/gitlab_body.txt.erb").gsub(/^\s*/, ''))
  37 + end
  38 +
  39 + def url
  40 + "#{account}/#{project_id}/issues"
  41 + end
  42 + end
  43 +end
app/models/issue_trackers/lighthouse_tracker.rb
@@ -3,19 +3,22 @@ if defined? Lighthouse @@ -3,19 +3,22 @@ if defined? Lighthouse
3 Label = "lighthouseapp" 3 Label = "lighthouseapp"
4 Fields = [ 4 Fields = [
5 [:account, { 5 [:account, {
6 - :placeholder => "abc from http://abc.lighthouseapp.com" 6 + :label => "Subdomain",
  7 + :placeholder => "subdomain from http://{{subdomain}}.lighthouseapp.com"
7 }], 8 }],
8 [:api_token, { 9 [:api_token, {
9 - :placeholder => "API Token for your account" 10 + :label => "API Token",
  11 + :placeholder => "123456789abcdef123456789abcdef"
10 }], 12 }],
11 [:project_id, { 13 [:project_id, {
12 - :placeholder => "Lighthouse project" 14 + :label => "Project ID",
  15 + :placeholder => "123456"
13 }] 16 }]
14 ] 17 ]
15 18
16 def check_params 19 def check_params
17 if Fields.detect {|f| self[f[0]].blank? } 20 if Fields.detect {|f| self[f[0]].blank? }
18 - errors.add :base, 'You must specify your Lighthouseapp account, API token and Project ID' 21 + errors.add :base, 'You must specify your Lighthouseapp Subdomain, API token and Project ID'
19 end 22 end
20 end 23 end
21 24
app/models/issue_trackers/redmine_tracker.rb
@@ -4,7 +4,7 @@ if defined? RedmineClient @@ -4,7 +4,7 @@ if defined? RedmineClient
4 Fields = [ 4 Fields = [
5 [:account, { 5 [:account, {
6 :label => "Redmine URL", 6 :label => "Redmine URL",
7 - :placeholder => "e.g. http://www.redmine.org/" 7 + :placeholder => "http://www.redmine.org/"
8 }], 8 }],
9 [:api_token, { 9 [:api_token, {
10 :placeholder => "API Token for your account" 10 :placeholder => "API Token for your account"
app/models/notice.rb
@@ -13,7 +13,7 @@ class Notice @@ -13,7 +13,7 @@ class Notice
13 field :current_user, :type => Hash 13 field :current_user, :type => Hash
14 field :error_class 14 field :error_class
15 delegate :lines, :to => :backtrace, :prefix => true 15 delegate :lines, :to => :backtrace, :prefix => true
16 - delegate :app, :to => :err 16 + delegate :app, :problem, :to => :err
17 17
18 belongs_to :err 18 belongs_to :err
19 belongs_to :backtrace, :index => true 19 belongs_to :backtrace, :index => true
@@ -36,8 +36,6 @@ class Notice @@ -36,8 +36,6 @@ class Notice
36 scope :reverse_ordered, order_by(:created_at.desc) 36 scope :reverse_ordered, order_by(:created_at.desc)
37 scope :for_errs, lambda {|errs| where(:err_id.in => errs.all.map(&:id))} 37 scope :for_errs, lambda {|errs| where(:err_id.in => errs.all.map(&:id))}
38 38
39 - delegate :app, :problem, :to => :err  
40 -  
41 def user_agent 39 def user_agent
42 agent_string = env_vars['HTTP_USER_AGENT'] 40 agent_string = env_vars['HTTP_USER_AGENT']
43 agent_string.blank? ? nil : UserAgent.parse(agent_string) 41 agent_string.blank? ? nil : UserAgent.parse(agent_string)
@@ -66,7 +64,7 @@ class Notice @@ -66,7 +64,7 @@ class Notice
66 end 64 end
67 65
68 def request 66 def request
69 - read_attribute(:request) || {} 67 + super || {}
70 end 68 end
71 69
72 def url 70 def url
@@ -96,6 +94,18 @@ class Notice @@ -96,6 +94,18 @@ class Notice
96 backtrace_lines.in_app 94 backtrace_lines.in_app
97 end 95 end
98 96
  97 + def similar_count
  98 + problem.notices_count
  99 + end
  100 +
  101 + def notifiable?
  102 + app.email_at_notices.include?(similar_count)
  103 + end
  104 +
  105 + def should_notify?
  106 + app.notifiable? && notifiable?
  107 + end
  108 +
99 protected 109 protected
100 110
101 def increase_counter_cache 111 def increase_counter_cache
app/models/notice_observer.rb
@@ -7,16 +7,7 @@ class NoticeObserver &lt; Mongoid::Observer @@ -7,16 +7,7 @@ class NoticeObserver &lt; Mongoid::Observer
7 notice.app.notification_service.create_notification(notice.problem) 7 notice.app.notification_service.create_notification(notice.problem)
8 end 8 end
9 9
10 - if notice.app.notification_recipients.any?  
11 - Mailer.err_notification(notice).deliver  
12 - end 10 + Mailer.err_notification(notice).deliver if notice.should_notify?
13 end 11 end
14 12
15 - private  
16 -  
17 - def should_notify? notice  
18 - app = notice.app  
19 - app.notify_on_errs? and (app.notification_recipients.any? or !app.notification_service.nil?) and  
20 - (app.email_at_notices or Errbit::Config.email_at_notices).include?(notice.problem.notices_count)  
21 - end  
22 end 13 end
app/models/notification_service.rb
@@ -30,4 +30,8 @@ class NotificationService @@ -30,4 +30,8 @@ class NotificationService
30 Label = '' 30 Label = ''
31 def self.label; self::Label; end 31 def self.label; self::Label; end
32 def label; self.class.label; end 32 def label; self.class.label; end
  33 +
  34 + def configured?
  35 + api_token.present?
  36 + end
33 end 37 end
app/models/notification_services/campfire_service.rb
@@ -2,16 +2,18 @@ if defined? Campy @@ -2,16 +2,18 @@ if defined? Campy
2 class NotificationServices::CampfireService < NotificationService 2 class NotificationServices::CampfireService < NotificationService
3 Label = "campfire" 3 Label = "campfire"
4 Fields = [ 4 Fields = [
5 - [:subdomain, {  
6 - :placeholder => "Campfire Subdomain"  
7 - }],  
8 - [:api_token, {  
9 - :placeholder => "API Token"  
10 - }],  
11 - [:room_id, {  
12 - :placeholder => "Room ID",  
13 - :label => "Room ID"  
14 - }], 5 + [:subdomain, {
  6 + :label => "Subdomain",
  7 + :placeholder => "subdomain from http://{{subdomain}}.campfirenow.com"
  8 + }],
  9 + [:api_token, {
  10 + :label => "API Token",
  11 + :placeholder => "123456789abcdef123456789abcdef"
  12 + }],
  13 + [:room_id, {
  14 + :label => "Room ID",
  15 + :placeholder => "123456"
  16 + }]
15 ] 17 ]
16 18
17 def check_params 19 def check_params
@@ -21,7 +23,7 @@ if defined? Campy @@ -21,7 +23,7 @@ if defined? Campy
21 end 23 end
22 24
23 def url 25 def url
24 - "http://campfirenow.com/" 26 + "http://#{subdomain}.campfirenow.com/"
25 end 27 end
26 28
27 def create_notification(problem) 29 def create_notification(problem)
@@ -31,4 +33,4 @@ if defined? Campy @@ -31,4 +33,4 @@ if defined? Campy
31 campy.speak "[errbit] #{problem.app.name} #{notification_description problem} - http://#{Errbit::Config.host}/apps/#{problem.app.id.to_s}/problems/#{problem.id.to_s}" 33 campy.speak "[errbit] #{problem.app.name} #{notification_description problem} - http://#{Errbit::Config.host}/apps/#{problem.app.id.to_s}/problems/#{problem.id.to_s}"
32 end 34 end
33 end 35 end
34 -end 36 -end
  37 +end
35 \ No newline at end of file 38 \ No newline at end of file
app/models/notification_services/hipchat_service.rb
@@ -6,8 +6,8 @@ if defined? HipChat @@ -6,8 +6,8 @@ if defined? HipChat
6 :placeholder => "API Token" 6 :placeholder => "API Token"
7 }], 7 }],
8 [:room_id, { 8 [:room_id, {
9 - :placeholder => "Room ID",  
10 - :label => "Room ID" 9 + :placeholder => "Room name",
  10 + :label => "Room name"
11 }], 11 }],
12 ] 12 ]
13 13
@@ -17,6 +17,10 @@ if defined? HipChat @@ -17,6 +17,10 @@ if defined? HipChat
17 end 17 end
18 end 18 end
19 19
  20 + def url
  21 + "https://www.hipchat.com/sign_in"
  22 + end
  23 +
20 def create_notification(problem) 24 def create_notification(problem)
21 url = app_problem_url problem.app, problem 25 url = app_problem_url problem.app, problem
22 message = <<-MSG.strip_heredoc 26 message = <<-MSG.strip_heredoc
@@ -28,4 +32,4 @@ if defined? HipChat @@ -28,4 +32,4 @@ if defined? HipChat
28 client[room_id].send('Errbit', message, :color => 'red') 32 client[room_id].send('Errbit', message, :color => 'red')
29 end 33 end
30 end 34 end
31 -end  
32 \ No newline at end of file 35 \ No newline at end of file
  36 +end
app/models/notification_services/hoiio_service.rb
@@ -21,6 +21,10 @@ class NotificationServices::HoiioService &lt; NotificationService @@ -21,6 +21,10 @@ class NotificationServices::HoiioService &lt; NotificationService
21 end 21 end
22 end 22 end
23 23
  24 + def url
  25 + "https://secure.hoiio.com/user/"
  26 + end
  27 +
24 def notification_description(problem) 28 def notification_description(problem)
25 "[#{ problem.environment }]#{problem.message.to_s.truncate(50)}" 29 "[#{ problem.environment }]#{problem.message.to_s.truncate(50)}"
26 end 30 end
app/models/notification_services/pushover_service.rb
@@ -17,6 +17,10 @@ class NotificationServices::PushoverService &lt; NotificationService @@ -17,6 +17,10 @@ class NotificationServices::PushoverService &lt; NotificationService
17 end 17 end
18 end 18 end
19 19
  20 + def url
  21 + "https://pushover.net/login"
  22 + end
  23 +
20 def create_notification(problem) 24 def create_notification(problem)
21 # build the hoi client 25 # build the hoi client
22 notification = Rushover::Client.new(subdomain) 26 notification = Rushover::Client.new(subdomain)
app/models/problem.rb
@@ -57,6 +57,10 @@ class Problem @@ -57,6 +57,10 @@ class Problem
57 Notice.for_errs(errs).ordered 57 Notice.for_errs(errs).ordered
58 end 58 end
59 59
  60 + def comments_allowed?
  61 + Errbit::Config.allow_comments_with_issue_tracker || !app.issue_tracker_configured?
  62 + end
  63 +
60 def resolve! 64 def resolve!
61 self.update_attributes!(:resolved => true, :resolved_at => Time.now) 65 self.update_attributes!(:resolved => true, :resolved_at => Time.now)
62 end 66 end