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.

Gemfile
... ... @@ -4,7 +4,6 @@ gem 'rails', '3.2.8'
4 4 gem 'mongoid', '~> 2.4.10'
5 5 gem 'mongoid_rails_migrations'
6 6 gem 'devise', '~> 1.5.3'
7   -gem 'nokogiri'
8 7 gem 'haml'
9 8 gem 'htmlentities', "~> 4.3.0"
10 9 gem 'rack-ssl', :require => 'rack/ssl' # force SSL
... ... @@ -37,6 +36,8 @@ gem 'pivotal-tracker'
37 36 gem 'ruby-fogbugz', :require => 'fogbugz'
38 37 # Github Issues
39 38 gem 'octokit', '~> 1.0.0'
  39 +# Gitlab
  40 +gem 'gitlab'
40 41  
41 42 # Bitbucket Issues
42 43 gem 'bitbucket_rest_api'
... ... @@ -66,8 +67,6 @@ platform :ruby do
66 67 gem 'bson_ext', '= 1.6.2'
67 68 end
68 69  
69   -gem 'omniauth'
70   -gem 'oa-core'
71 70 gem 'ri_cal'
72 71 gem 'yajl-ruby', :require => "yajl"
73 72  
... ... @@ -77,25 +76,22 @@ group :development, :test do
77 76 unless ENV["CI"]
78 77 gem 'ruby-debug', :platform => :mri_18
79 78 gem 'debugger', :platform => :mri_19
80   - gem 'pry'
81 79 gem 'pry-rails'
82 80 end
83 81 # gem 'rpm_contrib'
84 82 # gem 'newrelic_rpm'
85 83 gem 'capistrano'
86   - gem 'capistrano_colors'
87 84 end
88 85  
89 86 group :test do
90 87 gem 'capybara'
91 88 gem 'launchy'
92   - gem 'rspec', '~> 2.6'
93 89 gem 'database_cleaner', '~> 0.6.0'
94 90 gem 'email_spec'
95 91 gem 'timecop'
96 92 end
97 93  
98   -group :heroku do
  94 +group :heroku, :production do
99 95 gem 'unicorn'
100 96 end
101 97  
... ... @@ -108,6 +104,7 @@ group :assets do
108 104 gem 'execjs'
109 105 gem 'therubyracer', :platform => :ruby # C Ruby (MRI) or Rubinius, but NOT Windows
110 106 gem 'uglifier', '>= 1.0.3'
  107 + gem 'underscore-rails'
111 108 end
112 109  
113 110 gem 'turbo-sprockets-rails3'
... ...
Gemfile.lock
... ... @@ -49,13 +49,12 @@ GEM
49 49 builder (3.0.4)
50 50 campy (0.1.3)
51 51 multi_json (~> 1.0)
52   - capistrano (2.13.4)
  52 + capistrano (2.13.5)
53 53 highline
54 54 net-scp (>= 1.0.0)
55 55 net-sftp (>= 2.0.0)
56 56 net-ssh (>= 2.0.14)
57 57 net-ssh-gateway (>= 1.1.0)
58   - capistrano_colors (0.5.5)
59 58 capybara (1.1.2)
60 59 mime-types (>= 1.16)
61 60 nokogiri (>= 1.3.3)
... ... @@ -73,13 +72,13 @@ GEM
73 72 rdoc
74 73 daemons (1.1.8)
75 74 database_cleaner (0.6.7)
76   - debugger (1.2.0)
  75 + debugger (1.2.1)
77 76 columnize (>= 0.3.1)
78 77 debugger-linecache (~> 1.1.1)
79   - debugger-ruby_core_source (~> 1.1.3)
  78 + debugger-ruby_core_source (~> 1.1.4)
80 79 debugger-linecache (1.1.2)
81 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 82 devise (1.5.3)
84 83 bcrypt-ruby (~> 3.0)
85 84 orm_adapter (~> 0.0.3)
... ... @@ -169,7 +168,6 @@ GEM
169 168 net-ssh-gateway (1.1.0)
170 169 net-ssh (>= 1.99.1)
171 170 nokogiri (1.5.5)
172   - oa-core (0.3.2)
173 171 oauth2 (0.8.0)
174 172 faraday (~> 0.8)
175 173 httpauth (~> 0.1)
... ... @@ -295,13 +293,14 @@ GEM
295 293 treetop (1.4.10)
296 294 polyglot
297 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 298 sprockets (>= 2.0.0)
301 299 tzinfo (0.3.33)
302 300 uglifier (1.2.7)
303 301 execjs (>= 0.3.0)
304 302 multi_json (~> 1.3)
  303 + underscore-rails (1.4.2.1)
305 304 unicorn (4.3.1)
306 305 kgio (~> 2.6)
307 306 rack
... ... @@ -328,7 +327,6 @@ DEPENDENCIES
328 327 bson_ext (= 1.6.2)
329 328 campy
330 329 capistrano
331   - capistrano_colors
332 330 capybara
333 331 database_cleaner (~> 0.6.0)
334 332 debugger
... ... @@ -348,21 +346,16 @@ DEPENDENCIES
348 346 mongo (= 1.6.2)
349 347 mongoid (~> 2.4.10)
350 348 mongoid_rails_migrations
351   - nokogiri
352   - oa-core
353 349 octokit (~> 1.0.0)
354   - omniauth
355 350 omniauth-github
356 351 oruen_redmine_client
357 352 pivotal-tracker
358   - pry
359 353 pry-rails
360 354 rack-ssl
361 355 rack-ssl-enforcer
362 356 rails (= 3.2.8)
363 357 rails_autolink (~> 1.0.9)
364 358 ri_cal
365   - rspec (~> 2.6)
366 359 rspec-rails (~> 2.6)
367 360 ruby-debug
368 361 ruby-fogbugz
... ... @@ -372,6 +365,7 @@ DEPENDENCIES
372 365 timecop
373 366 turbo-sprockets-rails3
374 367 uglifier (>= 1.0.3)
  368 + underscore-rails
375 369 unicorn
376 370 useragent (~> 0.3.1)
377 371 webmock
... ...
README.md
... ... @@ -311,6 +311,7 @@ When upgrading Errbit, please run:
311 311  
312 312 ```bash
313 313 git pull origin master # assuming origin is the github.com/errbit/errbit repo
  314 +bundle install
314 315 rake db:migrate
315 316 ```
316 317  
... ... @@ -376,12 +377,19 @@ card_type = Defect, status = Open, priority = Essential
376 377 * For 'Account/Repository', the account will either be a username or organization. i.e. **errbit/errbit**
377 378 * You will also need to provide your username and password for your GitHub account.
378 379 * (We'd really appreciate it if you wanted to help us implement OAuth instead!)
379   -
  380 +
380 381 **Bitbucket Issues Integration**
381 382  
382 383 * For 'BITBUCKET REPO' field, the account will either be a username or organization. i.e. **errbit/errbit**
383 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 395 What if Errbit has an error?
... ... @@ -436,10 +444,15 @@ Special Thanks
436 444 --------------
437 445  
438 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 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 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 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 1 //= require jquery
2   -//= require underscore-1.1.6
  2 +//= require underscore
3 3 //= require rails
4 4 //= require form
5 5 //= require jquery.pjax
... ...
app/assets/javascripts/underscore-1.1.6.js
... ... @@ -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 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 586 table.apps tbody tr:hover td ,table.errs tbody tr:hover td { background-color: #F2F2F2;}
587 587  
588 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 592 table.apps td.issue_tracker, table.apps td.count, table.apps td.deploy {
593 593 text-align: center;
... ... @@ -894,6 +894,11 @@ table.errs tr td.message .inline_comment em.commenter {
894 894 color: #777;
895 895 }
896 896  
  897 +textarea#comment_body {
  898 + width: 420px;
  899 + height: 80px;
  900 +}
  901 +
897 902 .current.asc:after { content: ' ↑'; }
898 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 3  
4 4 <% trackers.each do |tracker| %>
5 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 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 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 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 17 <% end %>
18 18  
... ...
app/assets/stylesheets/jquery.alerts.css
... ... @@ -17,7 +17,7 @@
17 17 text-align: center;
18 18 line-height: 1.75em;
19 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 21 border: solid 1px #FFF;
22 22 border-bottom: solid 1px #999;
23 23 cursor: default;
... ... @@ -26,21 +26,21 @@
26 26 }
27 27  
28 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 30 padding: 1em 1.75em;
31 31 margin: 0em;
32 32 }
33 33  
34 34 #popup_content.alert {
35   - background-image: url(/images/alerts/info.gif);
  35 + background-image: url(alerts/info.gif);
36 36 }
37 37  
38 38 #popup_content.confirm {
39   - background-image: url(/images/alerts/important.gif);
  39 + background-image: url(alerts/important.gif);
40 40 }
41 41  
42 42 #popup_content.prompt {
43   - background-image: url(/images/alerts/help.gif);
  43 + background-image: url(alerts/help.gif);
44 44 }
45 45  
46 46 #popup_message {
... ...
app/assets/stylesheets/notification_service_icons.css.erb
... ... @@ -3,16 +3,16 @@
3 3  
4 4 <% notification_services.each do |notification_service| %>
5 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 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 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 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 17 <% end %>
18 18  
... ...
app/controllers/apps_controller.rb
... ... @@ -52,23 +52,7 @@ class AppsController &lt; InheritedResources::Base
52 52  
53 53 protected
54 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 56 end
73 57  
74 58 def initialize_subclassed_issue_tracker
... ... @@ -80,7 +64,7 @@ class AppsController &lt; InheritedResources::Base
80 64 end
81 65 end
82 66  
83   - def initialize_subclassed_notification_service
  67 + def initialize_subclassed_notification_service
84 68 # set the app's notification service
85 69 if params[:app][:notification_service_attributes] && notification_type = params[:app][:notification_service_attributes][:type]
86 70 if NotificationService.subclasses.map(&:name).concat(["NotificationService"]).include?(notification_type)
... ...
app/controllers/problems_controller.rb
1 1 class ProblemsController < ApplicationController
2   - include ActionView::Helpers::TextHelper
3   -
4 2 before_filter :find_app, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several]
5 3 before_filter :find_problem, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several]
6 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 35 end
38 36  
39 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 42 end
71 43  
72 44 redirect_to app_problem_path(@app, @problem)
... ... @@ -87,13 +59,13 @@ class ProblemsController &lt; ApplicationController
87 59  
88 60 def resolve_several
89 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 63 redirect_to :back
92 64 end
93 65  
94 66 def unresolve_several
95 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 69 redirect_to :back
98 70 end
99 71  
... ... @@ -109,13 +81,13 @@ class ProblemsController &lt; ApplicationController
109 81  
110 82 def unmerge_several
111 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 85 redirect_to :back
114 86 end
115 87  
116 88 def destroy_several
117 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 91 redirect_to :back
120 92 end
121 93  
... ...
app/helpers/backtrace_line_helper.rb
... ... @@ -19,13 +19,13 @@ module BacktraceLineHelper
19 19  
20 20 def link_to_github(line, text = nil)
21 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 23 link_to(text || line.file_name, href, :target => '_blank')
24 24 end
25 25  
26 26 def link_to_bitbucket(line, text = nil)
27 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 29 link_to(text || line.file_name, href, :target => '_blank')
30 30 end
31 31  
... ...
app/helpers/notices_helper.rb
1 1 # encoding: utf-8
2 2 module NoticesHelper
3 3 def notice_atom_summary(notice)
4   - render "notices/atom_entry.html.haml", :notice => notice
  4 + render "notices/atom_entry", :notice => notice
5 5 end
6 6 end
7   -
... ...
app/interactors/issue_creation.rb 0 → 100644
... ... @@ -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 1 class App
2 2 include Mongoid::Document
3 3 include Mongoid::Timestamps
  4 + include Comparable
4 5  
5 6 field :name, :type => String
6 7 field :api_key
... ... @@ -98,12 +99,16 @@ class App
98 99  
99 100 # Legacy apps don't have notify_on_errs and notify_on_deploys params
100 101 def notify_on_errs
101   - !(self[:notify_on_errs] == false)
  102 + !(super == false)
102 103 end
103 104 alias :notify_on_errs? :notify_on_errs
104 105  
  106 + def notifiable?
  107 + notify_on_errs? && notification_recipients.any?
  108 + end
  109 +
105 110 def notify_on_deploys
106   - !(self[:notify_on_deploys] == false)
  111 + !(super == false)
107 112 end
108 113 alias :notify_on_deploys? :notify_on_deploys
109 114  
... ... @@ -137,11 +142,11 @@ class App
137 142  
138 143  
139 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 146 end
142 147  
143 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 150 end
146 151  
147 152  
... ... @@ -169,6 +174,25 @@ class App
169 174 end
170 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 196 protected
173 197  
174 198 def store_cached_attributes_on_problems
... ...
app/models/issue_tracker.rb
... ... @@ -35,5 +35,9 @@ class IssueTracker
35 35 Label = ''
36 36 def self.label; self::Label; end
37 37 def label; self.class.label; end
  38 +
  39 + def configured?
  40 + project_id.present?
  41 + end
38 42 end
39 43  
... ...
app/models/issue_trackers/github_issues_tracker.rb
... ... @@ -35,7 +35,7 @@ if defined? Octokit
35 35 end
36 36  
37 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 39 problem.update_attributes(
40 40 :issue_link => issue.html_url,
41 41 :issue_type => Label
... ... @@ -54,4 +54,4 @@ if defined? Octokit
54 54 "https://github.com/#{project_id}/issues"
55 55 end
56 56 end
57   -end
58 57 \ No newline at end of file
  58 +end
... ...
app/models/issue_trackers/gitlab_tracker.rb 0 → 100644
... ... @@ -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 3 Label = "lighthouseapp"
4 4 Fields = [
5 5 [:account, {
6   - :placeholder => "abc from http://abc.lighthouseapp.com"
  6 + :label => "Subdomain",
  7 + :placeholder => "subdomain from http://{{subdomain}}.lighthouseapp.com"
7 8 }],
8 9 [:api_token, {
9   - :placeholder => "API Token for your account"
  10 + :label => "API Token",
  11 + :placeholder => "123456789abcdef123456789abcdef"
10 12 }],
11 13 [:project_id, {
12   - :placeholder => "Lighthouse project"
  14 + :label => "Project ID",
  15 + :placeholder => "123456"
13 16 }]
14 17 ]
15 18  
16 19 def check_params
17 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 22 end
20 23 end
21 24  
... ...
app/models/issue_trackers/redmine_tracker.rb
... ... @@ -4,7 +4,7 @@ if defined? RedmineClient
4 4 Fields = [
5 5 [:account, {
6 6 :label => "Redmine URL",
7   - :placeholder => "e.g. http://www.redmine.org/"
  7 + :placeholder => "http://www.redmine.org/"
8 8 }],
9 9 [:api_token, {
10 10 :placeholder => "API Token for your account"
... ...
app/models/notice.rb
... ... @@ -13,7 +13,7 @@ class Notice
13 13 field :current_user, :type => Hash
14 14 field :error_class
15 15 delegate :lines, :to => :backtrace, :prefix => true
16   - delegate :app, :to => :err
  16 + delegate :app, :problem, :to => :err
17 17  
18 18 belongs_to :err
19 19 belongs_to :backtrace, :index => true
... ... @@ -36,8 +36,6 @@ class Notice
36 36 scope :reverse_ordered, order_by(:created_at.desc)
37 37 scope :for_errs, lambda {|errs| where(:err_id.in => errs.all.map(&:id))}
38 38  
39   - delegate :app, :problem, :to => :err
40   -
41 39 def user_agent
42 40 agent_string = env_vars['HTTP_USER_AGENT']
43 41 agent_string.blank? ? nil : UserAgent.parse(agent_string)
... ... @@ -66,7 +64,7 @@ class Notice
66 64 end
67 65  
68 66 def request
69   - read_attribute(:request) || {}
  67 + super || {}
70 68 end
71 69  
72 70 def url
... ... @@ -96,6 +94,18 @@ class Notice
96 94 backtrace_lines.in_app
97 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 109 protected
100 110  
101 111 def increase_counter_cache
... ...
app/models/notice_observer.rb
... ... @@ -7,16 +7,7 @@ class NoticeObserver &lt; Mongoid::Observer
7 7 notice.app.notification_service.create_notification(notice.problem)
8 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 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 13 end
... ...
app/models/notification_service.rb
... ... @@ -30,4 +30,8 @@ class NotificationService
30 30 Label = ''
31 31 def self.label; self::Label; end
32 32 def label; self.class.label; end
  33 +
  34 + def configured?
  35 + api_token.present?
  36 + end
33 37 end
... ...
app/models/notification_services/campfire_service.rb
... ... @@ -2,16 +2,18 @@ if defined? Campy
2 2 class NotificationServices::CampfireService < NotificationService
3 3 Label = "campfire"
4 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 19 def check_params
... ... @@ -21,7 +23,7 @@ if defined? Campy
21 23 end
22 24  
23 25 def url
24   - "http://campfirenow.com/"
  26 + "http://#{subdomain}.campfirenow.com/"
25 27 end
26 28  
27 29 def create_notification(problem)
... ... @@ -31,4 +33,4 @@ if defined? Campy
31 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 34 end
33 35 end
34 36 -end
  37 +end
35 38 \ No newline at end of file
... ...
app/models/notification_services/hipchat_service.rb
... ... @@ -6,8 +6,8 @@ if defined? HipChat
6 6 :placeholder => "API Token"
7 7 }],
8 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 17 end
18 18 end
19 19  
  20 + def url
  21 + "https://www.hipchat.com/sign_in"
  22 + end
  23 +
20 24 def create_notification(problem)
21 25 url = app_problem_url problem.app, problem
22 26 message = <<-MSG.strip_heredoc
... ... @@ -28,4 +32,4 @@ if defined? HipChat
28 32 client[room_id].send('Errbit', message, :color => 'red')
29 33 end
30 34 end
31   -end
32 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 21 end
22 22 end
23 23  
  24 + def url
  25 + "https://secure.hoiio.com/user/"
  26 + end
  27 +
24 28 def notification_description(problem)
25 29 "[#{ problem.environment }]#{problem.message.to_s.truncate(50)}"
26 30 end
... ...
app/models/notification_services/pushover_service.rb
... ... @@ -17,6 +17,10 @@ class NotificationServices::PushoverService &lt; NotificationService
17 17 end
18 18 end
19 19  
  20 + def url
  21 + "https://pushover.net/login"
  22 + end
  23 +
20 24 def create_notification(problem)
21 25 # build the hoi client
22 26 notification = Rushover::Client.new(subdomain)
... ...
app/models/problem.rb
... ... @@ -57,6 +57,10 @@ class Problem
57 57 Notice.for_errs(errs).ordered
58 58 end
59 59  
  60 + def comments_allowed?
  61 + Errbit::Config.allow_comments_with_issue_tracker || !app.issue_tracker_configured?
  62 + end
  63 +
60 64 def resolve!
61 65 self.update_attributes!(:resolved => true, :resolved_at => Time.now)
62 66 end
... ...