Commit a0af55302055ad839e6bd2cd838b0f5c16092780
Exists in
master
and in
1 other branch
Merge branch 'errbit' into feature/upstream_merge
Showing
135 changed files
with
571 additions
and
292 deletions
Show diff stats
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
app/assets/images/alerts/important.gif
app/assets/images/alerts/info.gif
app/assets/images/alerts/title.gif
app/assets/images/bitbucket_create.png
app/assets/images/bitbucket_goto.png
app/assets/images/bitbucket_inactive.png
app/assets/images/campfire_create.png
app/assets/images/campfire_goto.png
app/assets/images/campfire_inactive.png
app/assets/images/fogbugz_create.png
app/assets/images/fogbugz_goto.png
app/assets/images/fogbugz_inactive.png
app/assets/images/github_create.png
app/assets/images/github_goto.png
app/assets/images/github_inactive.png
4.47 KB
4.47 KB
4.35 KB
app/assets/images/gtalk_create.png
app/assets/images/gtalk_goto.png
app/assets/images/gtalk_inactive.png
app/assets/images/hipchat_create.png
app/assets/images/hipchat_goto.png
app/assets/images/hipchat_inactive.png
app/assets/images/hoiio_create.png
app/assets/images/hoiio_goto.png
app/assets/images/hoiio_inactive.png
app/assets/images/lighthouseapp_create.png
app/assets/images/lighthouseapp_goto.png
app/assets/images/lighthouseapp_inactive.png
app/assets/images/loader.gif
app/assets/images/mingle_create.png
app/assets/images/mingle_goto.png
app/assets/images/mingle_inactive.png
app/assets/images/none_create.png
app/assets/images/none_inactive.png
app/assets/images/pivotal_create.png
app/assets/images/pivotal_goto.png
app/assets/images/pivotal_inactive.png
app/assets/images/pushover_create.png
app/assets/images/pushover_goto.png
app/assets/images/pushover_inactive.png
app/assets/images/redmine_create.png
app/assets/images/redmine_goto.png
app/assets/images/redmine_inactive.png
app/assets/images/thumbs-up.png
app/assets/javascripts/application.js.erb
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
app/assets/stylesheets/images/content-fade.png
app/assets/stylesheets/images/error-badge-bg.png
app/assets/stylesheets/images/header.png
app/assets/stylesheets/images/icons/add.png
app/assets/stylesheets/images/icons/briefcase.png
app/assets/stylesheets/images/icons/bullet-red-sm.png
app/assets/stylesheets/images/icons/cross.png
app/assets/stylesheets/images/icons/edit.png
app/assets/stylesheets/images/icons/error.png
app/assets/stylesheets/images/icons/github.png
app/assets/stylesheets/images/icons/ical.png
app/assets/stylesheets/images/icons/notice.png
app/assets/stylesheets/images/icons/required.png
app/assets/stylesheets/images/icons/right-arrow.png
app/assets/stylesheets/images/icons/success.png
app/assets/stylesheets/images/icons/thumbs-up.png
app/assets/stylesheets/images/icons/trash.png
app/assets/stylesheets/images/icons/unlink_github.png
app/assets/stylesheets/images/icons/up.png
app/assets/stylesheets/images/icons/user.png
app/assets/stylesheets/images/icons/warning.png
app/assets/stylesheets/images/logo.png
app/assets/stylesheets/images/notebook.png
app/assets/stylesheets/images/resolved-badge-bg.png
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 < 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 < 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 < 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 < 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 < 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
... | ... | @@ -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
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 | ... | ... |
... | ... | @@ -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
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 < 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
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 < 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 < 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 | ... | ... |
app/models/user.rb
app/views/apps/index.html.haml
... | ... | @@ -47,8 +47,8 @@ |
47 | 47 | - revision = app.deploys.last.short_revision |
48 | 48 | = link_to( app.last_deploy_at.to_s(:micro) << (revision.present? ? " (#{revision})" : ""), app_deploys_path(app)) |
49 | 49 | %td.count |
50 | - - if @problem_counts[app.id] > 0 | |
51 | - - unresolved = @unresolved_counts[app.id] | |
50 | + - if app.problem_count > 0 | |
51 | + - unresolved = app.unresolved_count | |
52 | 52 | = link_to unresolved, app_path(app), :class => (unresolved == 0 ? "resolved" : nil) |
53 | 53 | - if @apps.none? |
54 | 54 | %tr | ... | ... |
... | ... | @@ -0,0 +1,45 @@ |
1 | +[See this exception on Errbit](<%= app_problem_url problem.app, problem %> "See this exception on Errbit") | |
2 | +<% if notice = problem.notices.first %> | |
3 | +# <%= notice.message %> # | |
4 | +## Summary ## | |
5 | +<% if notice.request['url'].present? %> | |
6 | + ### URL ### | |
7 | + [<%= notice.request['url'] %>](<%= notice.request['url'] %>)" | |
8 | +<% end %> | |
9 | +### Where ### | |
10 | +<%= notice.where %> | |
11 | + | |
12 | +### Occured ### | |
13 | +<%= notice.created_at.to_s(:micro) %> | |
14 | + | |
15 | +### Similar ### | |
16 | +<%= (notice.problem.notices_count - 1).to_s %> | |
17 | + | |
18 | +## Params ## | |
19 | +``` | |
20 | +<%= pretty_hash(notice.params) %> | |
21 | +``` | |
22 | + | |
23 | +## Session ## | |
24 | +``` | |
25 | +<%= pretty_hash(notice.session) %> | |
26 | +``` | |
27 | + | |
28 | +## Backtrace ## | |
29 | +``` | |
30 | +<% notice.backtrace_lines.each do |line| %><%= line.number %>: <%= line.file_relative %> -> **<%= line.method %>** | |
31 | +<% end %> | |
32 | +``` | |
33 | + | |
34 | +## Environment ## | |
35 | + | |
36 | +<table> | |
37 | +<% for key, val in notice.env_vars %> | |
38 | + <tr> | |
39 | + <td><%= key %>:</td> | |
40 | + <td><%= val %></td> | |
41 | + </tr> | |
42 | +<% end %> | |
43 | +</table> | |
44 | +<% end %> | |
45 | + | ... | ... |
app/views/layouts/application.html.haml
... | ... | @@ -30,7 +30,5 @@ |
30 | 30 | - if content_for?(:comments) |
31 | 31 | #content-comments |
32 | 32 | = yield :comments |
33 | - #footer= "Powered by #{link_to 'Errbit', 'http://github.com/errbit/errbit', :target => '_blank'}: the open source error catcher.".html_safe | |
33 | + #footer Powered by #{link_to 'Errbit', 'http://github.com/errbit/errbit', :target => '_blank'}: the open source error catcher. | |
34 | 34 | = yield :scripts |
35 | - | |
36 | -= yield :before_title | |
37 | 35 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,41 @@ |
1 | +%h2= notice.message | |
2 | +%h3 Summary | |
3 | +- if notice.request['url'].present? | |
4 | + %p | |
5 | + %strong URL: | |
6 | + = link_to(notice.request['url'], notice.request['url']) | |
7 | +%p | |
8 | + %strong Where: | |
9 | + = notice.where | |
10 | +%p | |
11 | + %strong Occured: | |
12 | + = notice.created_at.to_s(:micro) | |
13 | +%p | |
14 | + %strong Similar: | |
15 | + = notice.problem.notices_count - 1 | |
16 | + | |
17 | +%h3 Params | |
18 | +%p= pretty_hash(notice.params) | |
19 | + | |
20 | +%h3 Session | |
21 | +%p= pretty_hash(notice.session) | |
22 | + | |
23 | +%h3 Backtrace | |
24 | +%table | |
25 | + - for line in notice.backtrace_lines | |
26 | + %tr | |
27 | + %td | |
28 | + = "#{line.number}:" | |
29 | + | |
30 | + %td | |
31 | + = raw "#{h line.file_relative} -> #{content_tag :strong, h(line.method)}" | |
32 | + | |
33 | +%h3 Environment | |
34 | +%table | |
35 | + - for key, val in notice.env_vars | |
36 | + %tr | |
37 | + %td | |
38 | + = h key | |
39 | + %td | |
40 | + = h val | |
41 | + | ... | ... |
app/views/notices/_atom_entry.html.haml
... | ... | @@ -1,41 +0,0 @@ |
1 | -%h2= notice.message | |
2 | -%h3 Summary | |
3 | -- if notice.request['url'].present? | |
4 | - %p | |
5 | - %strong URL: | |
6 | - = link_to(notice.request['url'], notice.request['url']) | |
7 | -%p | |
8 | - %strong Where: | |
9 | - = notice.where | |
10 | -%p | |
11 | - %strong Occured: | |
12 | - = notice.created_at.to_s(:micro) | |
13 | -%p | |
14 | - %strong Similar: | |
15 | - = notice.problem.notices_count - 1 | |
16 | - | |
17 | -%h3 Params | |
18 | -%p= pretty_hash(notice.params) | |
19 | - | |
20 | -%h3 Session | |
21 | -%p= pretty_hash(notice.session) | |
22 | - | |
23 | -%h3 Backtrace | |
24 | -%table | |
25 | - - for line in notice.backtrace_lines | |
26 | - %tr | |
27 | - %td | |
28 | - = "#{line.number}:" | |
29 | - | |
30 | - %td | |
31 | - = raw "#{h line.file_relative} -> #{content_tag :strong, h(line.method)}" | |
32 | - | |
33 | -%h3 Environment | |
34 | -%table | |
35 | - - for key, val in notice.env_vars | |
36 | - %tr | |
37 | - %td | |
38 | - = h key | |
39 | - %td | |
40 | - = h val | |
41 | - |
app/views/problems/show.html.haml
... | ... | @@ -3,7 +3,7 @@ |
3 | 3 | - content_for :title, @problem.error_class || truncate(@problem.message, :length => 32) |
4 | 4 | - content_for :meta do |
5 | 5 | %strong App: |
6 | - = link_to @app.name, app_path(@app) | |
6 | + = link_to @app.name, @app | |
7 | 7 | %strong Where: |
8 | 8 | = @problem.where |
9 | 9 | %br |
... | ... | @@ -13,14 +13,14 @@ |
13 | 13 | = @problem.last_notice_at.to_s(:precise) |
14 | 14 | - content_for :action_bar do |
15 | 15 | - if @problem.unresolved? |
16 | - %span= link_to 'resolve', resolve_app_problem_path(@app, @problem), :method => :put, :data => { :confirm => problem_confirm }, :class => 'resolve' | |
16 | + %span= link_to 'resolve', [:resolve, @app, @problem], :method => :put, :data => { :confirm => problem_confirm }, :class => 'resolve' | |
17 | 17 | - if current_user.authentication_token |
18 | - %span= link_to 'iCal', app_problem_path(:app_id => @app.id, :id => @problem.id, :format => "ics", :auth_token => current_user.authentication_token), :class => "calendar_link" | |
18 | + %span= link_to 'iCal', polymorphic_path([@app, @problem], :format => "ics", :auth_token => current_user.authentication_token), :class => "calendar_link" | |
19 | 19 | %span>= link_to 'up', (request.env['HTTP_REFERER'] ? :back : app_problems_path(@app)), :class => 'up' |
20 | 20 | %br |
21 | 21 | = render "issue_tracker_links" |
22 | 22 | |
23 | -- if Errbit::Config.allow_comments_with_issue_tracker || !@app.issue_tracker_configured? || @problem.comments.any? | |
23 | +- if @problem.comments_allowed? || @problem.comments.any? | |
24 | 24 | - content_for :comments do |
25 | 25 | %h3 Comments |
26 | 26 | - @problem.comments.each do |comment| |
... | ... | @@ -33,18 +33,17 @@ |
33 | 33 | = gravatar_tag comment.user.email, :s => 24 |
34 | 34 | %span.comment-info |
35 | 35 | = time_ago_in_words(comment.created_at, true) << " ago by " |
36 | - = link_to comment.user.email, user_path(comment.user) | |
37 | - %span.delete= link_to '✘'.html_safe, app_problem_comment_path(@app, @problem, comment), :method => :delete, :data => { :confirm => "Are sure you don't need this comment?" }, :class => "destroy-comment" | |
36 | + = link_to comment.user.email, comment.user | |
38 | 37 | - else |
39 | 38 | %span.comment-info |
40 | 39 | = time_ago_in_words(comment.created_at, true) << " ago by [Unknown User]" |
41 | - %span.delete= link_to '✘'.html_safe, app_problem_comment_path(@app, @problem, comment), :method => :delete, :data => { :confirm => "Are sure you don't need this comment?" }, :class => "destroy-comment" | |
40 | + %span.delete= link_to '✘'.html_safe, [@app, @problem, comment], :method => :delete, :data => { :confirm => "Are sure you don't need this comment?" }, :class => "destroy-comment" | |
42 | 41 | %tr |
43 | - %td= comment.body.gsub("\n", "<br>").html_safe | |
44 | - - if Errbit::Config.allow_comments_with_issue_tracker || !@app.issue_tracker_configured? | |
45 | - = form_for @comment, :url => app_problem_comments_path(@app, @problem) do |comment_form| | |
42 | + %td= simple_format comment.body | |
43 | + - if @problem.comments_allowed? | |
44 | + = form_for [@app, @problem, @comment] do |comment_form| | |
46 | 45 | %p Add a comment |
47 | - = comment_form.text_area :body, :style => "width: 420px; height: 80px;" | |
46 | + = comment_form.text_area :body | |
48 | 47 | = comment_form.submit "Save Comment" |
49 | 48 | |
50 | 49 | %h4= @notice.try(:message) | ... | ... |
config/application.rb
... | ... | @@ -9,9 +9,12 @@ require "action_mailer/railtie" |
9 | 9 | require 'mongoid/railtie' |
10 | 10 | require "sprockets/railtie" |
11 | 11 | |
12 | -# If you have a Gemfile, require the gems listed there, including any gems | |
13 | -# you've limited to :test, :development, or :production. | |
14 | -Bundler.require(:default, Rails.env) if defined?(Bundler) | |
12 | +if defined?(Bundler) | |
13 | + # If you precompile assets before deploying to production, use this line | |
14 | + Bundler.require(*Rails.groups(:assets => %w(development test))) | |
15 | + # If you want your assets lazily compiled in production, use this line | |
16 | + # Bundler.require(:default, :assets, Rails.env) | |
17 | +end | |
15 | 18 | |
16 | 19 | module Errbit |
17 | 20 | class Application < Rails::Application | ... | ... |
config/config.example.yml
... | ... | @@ -53,6 +53,8 @@ deployment: |
53 | 53 | repository: http://github.com/errbit/errbit.git |
54 | 54 | user: deploy |
55 | 55 | deploy_to: /var/www/apps/errbit |
56 | + # setup path to unicorn pids folder (or deploy_to/shared/pids will be used) | |
57 | + # pids: /var/www/apps/errbit/shared/pids | |
56 | 58 | |
57 | 59 | # GitHub OAuth configuration |
58 | 60 | # If you want to allow authentication via GitHub, you will need to register | ... | ... |
config/deploy.example.rb
... | ... | @@ -39,6 +39,8 @@ set(:current_branch) { `git branch`.match(/\* (\S+)\s/m)[1] || raise("Couldn't d |
39 | 39 | set :branch, defer { current_branch } |
40 | 40 | |
41 | 41 | after 'deploy:update_code', 'errbit:symlink_configs' |
42 | +# if unicorn is started through something like runit (the tool which restarts the process when it's stopped) | |
43 | +# after 'deploy:restart', 'unicorn:stop' | |
42 | 44 | |
43 | 45 | namespace :deploy do |
44 | 46 | task :start do ; end |
... | ... | @@ -72,3 +74,24 @@ namespace :db do |
72 | 74 | end |
73 | 75 | end |
74 | 76 | |
77 | +namespace :unicorn do | |
78 | + set(:unicorn_pid) do | |
79 | + path = config['pids'] || "#{deploy_to}/shared/pids" | |
80 | + "`cat #{path}/unicorn.pid`" | |
81 | + end | |
82 | + | |
83 | + desc 'Reload unicorn' | |
84 | + task :reload, :roles => :app, :except => { :no_release => true } do | |
85 | + run "kill -HUP #{unicorn_pid}" | |
86 | + end | |
87 | + | |
88 | + desc 'Stop unicorn' | |
89 | + task :stop, :roles => :app, :except => { :no_release => true } do | |
90 | + run "kill -QUIT #{unicorn_pid}" | |
91 | + end | |
92 | + | |
93 | + desc 'Reexecute unicorn' | |
94 | + task :reexec, :roles => :app, :except => { :no_release => true } do | |
95 | + run "kill -USR2 #{unicorn_pid}" | |
96 | + end | |
97 | +end | ... | ... |
config/locales/en.yml
config/mongoid.example.yml
... | ... | @@ -30,4 +30,4 @@ production: |
30 | 30 | port: <%= ENV['MONGOID_PORT'] %> |
31 | 31 | username: <%= ENV['MONGOID_USERNAME'] %> |
32 | 32 | password: <%= ENV['MONGOID_PASSWORD'] %> |
33 | - database: <%= ENV['MONGOID_DATABASE'] %> | |
34 | 33 | \ No newline at end of file |
34 | + database: <%= ENV['MONGOID_DATABASE'] %> | ... | ... |
config/newrelic.example.yml
... | ... | @@ -31,13 +31,13 @@ common: &default_settings |
31 | 31 | # into a New Relic "application" on your home dashboard page. If you want |
32 | 32 | # to map this instance into multiple apps, like "AJAX Requests" and |
33 | 33 | # "All UI" then specify a semicolon-separated list of up to three |
34 | - # distinct names. If you comment this out, it defaults to the | |
34 | + # distinct names. If you comment this out, it defaults to the | |
35 | 35 | # capitalized RAILS_ENV (i.e., Production, Staging, etc) |
36 | 36 | app_name: <%= ENV["NEW_RELIC_APP_NAME"] %> |
37 | 37 | |
38 | - # When "true", the agent collects performance data about your | |
38 | + # When "true", the agent collects performance data about your | |
39 | 39 | # application and reports this data to the New Relic service at |
40 | - # newrelic.com. This global switch is normally overridden for each | |
40 | + # newrelic.com. This global switch is normally overridden for each | |
41 | 41 | # environment below. (formerly called 'enabled') |
42 | 42 | monitor_mode: true |
43 | 43 | |
... | ... | @@ -72,11 +72,11 @@ common: &default_settings |
72 | 72 | |
73 | 73 | # Set your application's Apdex threshold value with the 'apdex_t' |
74 | 74 | # setting, in seconds. The apdex_t value determines the buckets used |
75 | - # to compute your overall Apdex score. | |
75 | + # to compute your overall Apdex score. | |
76 | 76 | # Requests that take less than apdex_t seconds to process will be |
77 | 77 | # classified as Satisfying transactions; more than apdex_t seconds |
78 | 78 | # as Tolerating transactions; and more than four times the apdex_t |
79 | - # value as Frustrating transactions. | |
79 | + # value as Frustrating transactions. | |
80 | 80 | # For more about the Apdex standard, see |
81 | 81 | # http://support.newrelic.com/faqs/general/apdex |
82 | 82 | apdex_t: 0.5 |
... | ... | @@ -97,7 +97,7 @@ common: &default_settings |
97 | 97 | # Rails: the RoR filter_parameter_logging excludes parameters |
98 | 98 | # Java: create a config setting called "ignored_params" and set it to |
99 | 99 | # a comma separated list of HTTP parameter names. |
100 | - # ex: ignored_params: credit_card, ssn, password | |
100 | + # ex: ignored_params: credit_card, ssn, password | |
101 | 101 | capture_params: false |
102 | 102 | |
103 | 103 | # Transaction tracer captures deep information about slow |
... | ... | @@ -136,7 +136,7 @@ common: &default_settings |
136 | 136 | # set to false when using other adapters. |
137 | 137 | # explain_enabled: true |
138 | 138 | |
139 | - # Threshold for query execution time below which query plans will not | |
139 | + # Threshold for query execution time below which query plans will not | |
140 | 140 | # not be captured. Relevant only when `explain_enabled` is true. |
141 | 141 | # explain_threshold: 0.5 |
142 | 142 | |
... | ... | @@ -149,10 +149,10 @@ common: &default_settings |
149 | 149 | # product level. |
150 | 150 | enabled: true |
151 | 151 | |
152 | - # Rails Only - tells error collector whether or not to capture a | |
153 | - # source snippet around the place of the error when errors are View | |
152 | + # Rails Only - tells error collector whether or not to capture a | |
153 | + # source snippet around the place of the error when errors are View | |
154 | 154 | # related. |
155 | - capture_source: true | |
155 | + capture_source: true | |
156 | 156 | |
157 | 157 | # To stop specific errors from reporting to New Relic, set this property |
158 | 158 | # to comma-separated values. Default is to ignore routing errors, |
... | ... | @@ -172,7 +172,7 @@ common: &default_settings |
172 | 172 | # disable_memcache_instrumentation: true |
173 | 173 | # disable_dj: true |
174 | 174 | |
175 | - # Certain types of instrumentation such as GC stats will not work if | |
175 | + # Certain types of instrumentation such as GC stats will not work if | |
176 | 176 | # you are running multi-threaded. Please let us know. |
177 | 177 | # multi_threaded = false |
178 | 178 | |
... | ... | @@ -190,15 +190,15 @@ development: |
190 | 190 | <<: *default_settings |
191 | 191 | # Turn off communication to New Relic service in development mode (also |
192 | 192 | # 'enabled'). |
193 | - # NOTE: for initial evaluation purposes, you may want to temporarily | |
193 | + # NOTE: for initial evaluation purposes, you may want to temporarily | |
194 | 194 | # turn agent communication on in development mode. |
195 | 195 | monitor_mode: false |
196 | 196 | |
197 | - # Rails Only - when running in Developer Mode, the New Relic Agent will | |
197 | + # Rails Only - when running in Developer Mode, the New Relic Agent will | |
198 | 198 | # present performance information on the last 100 transactions you have |
199 | 199 | # executed since starting the app server. |
200 | 200 | # NOTE: There is substantial overhead when running in developer mode. |
201 | - # Do not use for production or load testing. | |
201 | + # Do not use for production or load testing. | |
202 | 202 | developer_mode: true |
203 | 203 | |
204 | 204 | # Enable textmate links | ... | ... |
config/unicorn.rb
db/migrate/20110422152027_move_notices_to_separate_collection.rb
... | ... | @@ -5,10 +5,10 @@ class MoveNoticesToSeparateCollection < Mongoid::Migration |
5 | 5 | errs = mongo_db.collection("errs").find({ }, :fields => ["notices"]) |
6 | 6 | errs.each do |err| |
7 | 7 | next unless err['notices'] |
8 | - | |
8 | + | |
9 | 9 | # This Err was created after the Problem->Err->Notice redesign |
10 | 10 | next if err['app_id'].nil? or err['problem_id'] |
11 | - | |
11 | + | |
12 | 12 | e = Err.find(err['_id']) |
13 | 13 | # disable email notifications |
14 | 14 | old_notify = e.app.notify_on_errs? | ... | ... |
db/migrate/20111019163257_add_problem_comments_count.rb
db/migrate/20111102173347_cache_problem_statistics_fix.rb
db/migrate/20120822195841_set_first_notice_at_on_problems.rb
db/migrate/20120829034812_ensure_that_problems_last_notice_at_is_not_nil.rb
... | ... | @@ -2,15 +2,15 @@ class EnsureThatProblemsLastNoticeAtIsNotNil < Mongoid::Migration |
2 | 2 | def self.up |
3 | 3 | Problem.where("$or" => [{:last_notice_at => nil}, {:first_notice_at => nil}]).each do |problem| |
4 | 4 | first_notice = problem.notices.order_by([:created_at, :asc]).first |
5 | - | |
5 | + | |
6 | 6 | # Destroy problems with no notices |
7 | 7 | if first_notice.nil? |
8 | 8 | problem.destroy |
9 | 9 | next |
10 | 10 | end |
11 | - | |
11 | + | |
12 | 12 | last_notice = problem.notices.order_by([:created_at, :asc]).last |
13 | - | |
13 | + | |
14 | 14 | problem.update_attributes!({ |
15 | 15 | :first_notice_at => first_notice.created_at, |
16 | 16 | :last_notice_at => last_notice.created_at | ... | ... |
lib/tasks/errbit/bootstrap.rake
1 | 1 | require 'fileutils' |
2 | 2 | |
3 | 3 | namespace :errbit do |
4 | - | |
4 | + | |
5 | 5 | desc "Copys of example config files" |
6 | 6 | task :copy_configs do |
7 | 7 | configs = { |
... | ... | @@ -9,7 +9,7 @@ namespace :errbit do |
9 | 9 | 'deploy.example.rb' => 'deploy.rb', |
10 | 10 | (ENV['HEROKU'] ? 'mongoid.mongohq.yml' : 'mongoid.example.yml') => 'mongoid.yml' |
11 | 11 | } |
12 | - | |
12 | + | |
13 | 13 | puts "Copying example config files..." |
14 | 14 | configs.each do |old, new| |
15 | 15 | if File.exists?("config/#{new}") |
... | ... | @@ -20,7 +20,7 @@ namespace :errbit do |
20 | 20 | end |
21 | 21 | end |
22 | 22 | end |
23 | - | |
23 | + | |
24 | 24 | desc "Copy's over example files and seeds the database" |
25 | 25 | task :bootstrap do |
26 | 26 | Rake::Task['errbit:copy_configs'].execute |
... | ... | @@ -29,5 +29,5 @@ namespace :errbit do |
29 | 29 | puts "\n" |
30 | 30 | Rake::Task['db:mongoid:create_indexes'].invoke |
31 | 31 | end |
32 | - | |
33 | -end | |
34 | 32 | \ No newline at end of file |
33 | + | |
34 | +end | ... | ... |
spec/controllers/api/v1/problems_controller_spec.rb
1 | 1 | require 'spec_helper' |
2 | 2 | |
3 | -describe Api::V1::ProblemsController do | |
4 | - | |
3 | +describe Api::V1::ProblemsController do | |
4 | + | |
5 | 5 | context "when logged in" do |
6 | 6 | before do |
7 | 7 | @user = Fabricate(:user) |
8 | 8 | end |
9 | - | |
9 | + | |
10 | 10 | describe "GET /api/v1/problems" do |
11 | 11 | before do |
12 | 12 | Fabricate(:problem, :first_notice_at => Date.new(2012, 8, 01), :resolved_at => Date.new(2012, 8, 02)) |
... | ... | @@ -14,45 +14,45 @@ describe Api::V1::ProblemsController do |
14 | 14 | Fabricate(:problem, :first_notice_at => Date.new(2012, 8, 21)) |
15 | 15 | Fabricate(:problem, :first_notice_at => Date.new(2012, 8, 30)) |
16 | 16 | end |
17 | - | |
18 | - | |
19 | - | |
17 | + | |
18 | + | |
19 | + | |
20 | 20 | it "should return JSON if JSON is requested" do |
21 | 21 | get :index, :auth_token => @user.authentication_token, :format => "json" |
22 | 22 | lambda { JSON.load(response.body) }.should_not raise_error(JSON::ParserError) |
23 | 23 | end |
24 | - | |
24 | + | |
25 | 25 | it "should return XML if XML is requested" do |
26 | 26 | get :index, :auth_token => @user.authentication_token, :format => "xml" |
27 | 27 | lambda { XML::Parser.string(response.body).parse }.should_not raise_error |
28 | 28 | end |
29 | - | |
29 | + | |
30 | 30 | it "should return JSON by default" do |
31 | 31 | get :index, :auth_token => @user.authentication_token |
32 | 32 | lambda { JSON.load(response.body) }.should_not raise_error(JSON::ParserError) |
33 | 33 | end |
34 | - | |
35 | - | |
36 | - | |
34 | + | |
35 | + | |
36 | + | |
37 | 37 | describe "given a date range" do |
38 | - | |
38 | + | |
39 | 39 | it "should return only the problems open during the date range" do |
40 | 40 | get :index, {:auth_token => @user.authentication_token, :start_date => "2012-08-20", :end_date => "2012-08-27"} |
41 | 41 | response.should be_success |
42 | 42 | problems = JSON.load response.body |
43 | 43 | problems.length.should == 2 |
44 | 44 | end |
45 | - | |
45 | + | |
46 | 46 | end |
47 | - | |
47 | + | |
48 | 48 | it "should return all problems" do |
49 | 49 | get :index, {:auth_token => @user.authentication_token} |
50 | 50 | response.should be_success |
51 | 51 | problems = JSON.load response.body |
52 | 52 | problems.length.should == 4 |
53 | 53 | end |
54 | - | |
54 | + | |
55 | 55 | end |
56 | 56 | end |
57 | - | |
57 | + | |
58 | 58 | end | ... | ... |
spec/controllers/apps_controller_spec.rb
... | ... | @@ -31,17 +31,6 @@ describe AppsController do |
31 | 31 | assigns(:apps).should_not include(unwatched_app) |
32 | 32 | end |
33 | 33 | end |
34 | - | |
35 | - context 'when there is only one app' do | |
36 | - it 'sets unresolved_counts and problem_counts variables' do | |
37 | - sign_in Fabricate(:admin) | |
38 | - app = Fabricate(:app) | |
39 | - get :index | |
40 | - | |
41 | - assigns(:unresolved_counts).should == {app.id => 0} | |
42 | - assigns(:problem_counts).should == {app.id => 0} | |
43 | - end | |
44 | - end | |
45 | 34 | end |
46 | 35 | |
47 | 36 | describe "GET /apps/:id" do |
... | ... | @@ -261,6 +250,10 @@ describe AppsController do |
261 | 250 | end |
262 | 251 | |
263 | 252 | context "changing email_at_notices" do |
253 | + before do | |
254 | + Errbit::Config.per_app_email_at_notices = true | |
255 | + end | |
256 | + | |
264 | 257 | it "should parse legal csv values" do |
265 | 258 | put :update, :id => @app.id, :app => { :email_at_notices => '1, 4, 7,8, 10' } |
266 | 259 | @app.reload | ... | ... |
spec/controllers/problems_controller_spec.rb
... | ... | @@ -419,6 +419,16 @@ describe ProblemsController do |
419 | 419 | post :resolve_several, :problems => [@problem2.id.to_s] |
420 | 420 | @problem2.reload.resolved?.should == true |
421 | 421 | end |
422 | + | |
423 | + it "should display a message about 1 err" do | |
424 | + post :resolve_several, :problems => [@problem2.id.to_s] | |
425 | + flash[:success].should match(/1 err has been resolved/) | |
426 | + end | |
427 | + | |
428 | + it "should display a message about 2 errs" do | |
429 | + post :resolve_several, :problems => [@problem1.id.to_s, @problem2.id.to_s] | |
430 | + flash[:success].should match(/2 errs have been resolved/) | |
431 | + end | |
422 | 432 | end |
423 | 433 | |
424 | 434 | context "POST /problems/unresolve_several" do | ... | ... |
spec/fabricators/issue_tracker_fabricator.rb
... | ... | @@ -15,6 +15,10 @@ Fabricator :redmine_tracker, :from => :issue_tracker, :class_name => "IssueTrack |
15 | 15 | account 'http://redmine.example.com' |
16 | 16 | end |
17 | 17 | |
18 | +Fabricator :gitlab_tracker, :from => :issue_tracker, :class_name => "IssueTrackers::GitlabTracker" do | |
19 | + account 'http://gitlab.example.com' | |
20 | +end | |
21 | + | |
18 | 22 | Fabricator :mingle_tracker, :from => :issue_tracker, :class_name => "IssueTrackers::MingleTracker" do |
19 | 23 | account 'https://mingle.example.com' |
20 | 24 | ticket_properties 'card_type = Defect, defect_status = open, priority = essential' | ... | ... |
spec/fixtures/hoptoad_test_notice_with_one_line_of_backtrace.xml
... | ... | @@ -0,0 +1,53 @@ |
1 | +require 'spec_helper' | |
2 | + | |
3 | +describe IssueCreation do | |
4 | + subject(:issue_creation) { IssueCreation.new(problem, user, tracker_name) } | |
5 | + | |
6 | + let(:problem) { notice.problem } | |
7 | + let(:notice) { Fabricate(:notice) } | |
8 | + let(:user) { Fabricate(:admin) } | |
9 | + let(:errors) { issue_creation.errors[:base] } | |
10 | + let(:tracker_name) { nil } | |
11 | + | |
12 | + it "adds the error when issue tracker isn't configured" do | |
13 | + issue_creation.execute | |
14 | + expect(errors).to include("This app has no issue tracker setup.") | |
15 | + end | |
16 | + | |
17 | + it 'creates an issue if issue tracker is configured' do | |
18 | + tracker = Fabricate(:lighthouse_tracker, :app => notice.app) | |
19 | + tracker.should_receive(:create_issue) | |
20 | + issue_creation.execute | |
21 | + expect(errors).to be_empty | |
22 | + end | |
23 | + | |
24 | + context "with user's github" do | |
25 | + let(:tracker_name) { 'user_github' } | |
26 | + | |
27 | + it "adds the error when repo isn't set up" do | |
28 | + issue_creation.execute | |
29 | + expect(errors).to include("This app doesn't have a GitHub repo set up.") | |
30 | + end | |
31 | + | |
32 | + context 'with repo set up' do | |
33 | + before do | |
34 | + notice.app.update_attribute(:github_repo, 'errbit/errbit') | |
35 | + end | |
36 | + | |
37 | + it "adds the error when github account isn't linked" do | |
38 | + issue_creation.execute | |
39 | + expect(errors).to include("You haven't linked your Github account.") | |
40 | + end | |
41 | + | |
42 | + it 'creates an issue if github account is linked' do | |
43 | + user.github_login = 'admin' | |
44 | + user.github_oauth_token = 'oauthtoken' | |
45 | + user.save! | |
46 | + | |
47 | + GithubIssuesTracker.any_instance.should_receive(:create_issue) | |
48 | + issue_creation.execute | |
49 | + expect(errors).to be_empty | |
50 | + end | |
51 | + end | |
52 | + end | |
53 | +end | ... | ... |
spec/interactors/problem_destroy_spec.rb
... | ... | @@ -7,7 +7,7 @@ describe ProblemDestroy do |
7 | 7 | |
8 | 8 | context "in unit way" do |
9 | 9 | let(:problem) { |
10 | - problem = Problem.new() | |
10 | + problem = Problem.new | |
11 | 11 | problem.stub(:errs).and_return(mock(:criteria, :only => [err_1, err_2])) |
12 | 12 | problem.stub(:comments).and_return(mock(:criteria, :only => [comment_1, comment_2])) |
13 | 13 | problem.stub(:delete) | ... | ... |
spec/models/app_spec.rb
... | ... | @@ -23,6 +23,31 @@ describe App do |
23 | 23 | end |
24 | 24 | end |
25 | 25 | |
26 | + describe '<=>' do | |
27 | + it 'is compared by unresolved count' do | |
28 | + app_0 = stub_model(App, :name => 'app', :unresolved_count => 1, :problem_count => 1) | |
29 | + app_1 = stub_model(App, :name => 'app', :unresolved_count => 0, :problem_count => 1) | |
30 | + | |
31 | + app_0.should < app_1 | |
32 | + app_1.should > app_0 | |
33 | + end | |
34 | + | |
35 | + it 'is compared by problem count' do | |
36 | + app_0 = stub_model(App, :name => 'app', :unresolved_count => 0, :problem_count => 1) | |
37 | + app_1 = stub_model(App, :name => 'app', :unresolved_count => 0, :problem_count => 0) | |
38 | + | |
39 | + app_0.should < app_1 | |
40 | + app_1.should > app_0 | |
41 | + end | |
42 | + | |
43 | + it 'is compared by name' do | |
44 | + app_0 = stub_model(App, :name => 'app_0', :unresolved_count => 0, :problem_count => 0) | |
45 | + app_1 = stub_model(App, :name => 'app_1', :unresolved_count => 0, :problem_count => 0) | |
46 | + | |
47 | + app_0.should < app_1 | |
48 | + app_1.should > app_0 | |
49 | + end | |
50 | + end | |
26 | 51 | |
27 | 52 | context 'being created' do |
28 | 53 | it 'generates a new api-key' do | ... | ... |
spec/models/issue_trackers/bitbucket_issues_tracker_spec.rb
spec/models/issue_trackers/fogbugz_tracker_spec.rb
... | ... | @@ -9,7 +9,7 @@ describe IssueTrackers::FogbugzTracker do |
9 | 9 | number = 123 |
10 | 10 | @issue_link = "https://#{tracker.account}.fogbugz.com/default.asp?#{number}" |
11 | 11 | response = "<response><token>12345</token><case><ixBug>123</ixBug></case></response>" |
12 | - http_mock = mock() | |
12 | + http_mock = mock | |
13 | 13 | http_mock.should_receive(:new).and_return(http_mock) |
14 | 14 | http_mock.should_receive(:request).twice.and_return(response) |
15 | 15 | Fogbugz.adapter[:http] = http_mock | ... | ... |
spec/models/issue_trackers/github_issues_tracker_spec 2.rb
0 → 100644
... | ... | @@ -0,0 +1,30 @@ |
1 | +require 'spec_helper' | |
2 | + | |
3 | +describe IssueTrackers::GitlabTracker do | |
4 | + it "should create an issue on Gitlab with problem params" do | |
5 | + notice = Fabricate :notice | |
6 | + tracker = Fabricate :gitlab_tracker, :app => notice.app | |
7 | + problem = notice.problem | |
8 | + | |
9 | + number = 5 | |
10 | + @issue_link = "#{tracker.account}/#{tracker.project_id}/issues/#{number}/#{tracker.api_token}" | |
11 | + body = <<EOF | |
12 | +{ | |
13 | + "title": "Title" | |
14 | +} | |
15 | +EOF | |
16 | + | |
17 | + stub_request(:post, "#{tracker.account}/#{tracker.project_id}/issues/#{tracker.api_token}"). | |
18 | + to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body ) | |
19 | + | |
20 | + problem.app.issue_tracker.create_issue(problem) | |
21 | + problem.reload | |
22 | + | |
23 | + requested = have_requested(:post, "#{tracker.account}/#{tracker.project_id}/issues/#{tracker.api_token}") | |
24 | + WebMock.should requested.with(:body => /[production][foo#bar] FooError: Too Much Bar/) | |
25 | + WebMock.should requested.with(:body => /See this exception on Errbit/) | |
26 | + | |
27 | + problem.issue_link.should == @issue_link | |
28 | + end | |
29 | +end | |
30 | + | ... | ... |
spec/models/notice_spec.rb
1 | 1 | require 'spec_helper' |
2 | 2 | |
3 | 3 | describe Notice do |
4 | - | |
5 | - | |
6 | 4 | context 'validations' do |
7 | 5 | it 'requires a backtrace' do |
8 | 6 | notice = Fabricate.build(:notice, :backtrace => nil) |
... | ... | @@ -23,7 +21,6 @@ describe Notice do |
23 | 21 | end |
24 | 22 | end |
25 | 23 | |
26 | - | |
27 | 24 | describe "key sanitization" do |
28 | 25 | before do |
29 | 26 | @hash = { "some.key" => { "$nested.key" => {"$Path" => "/", "some$key" => "key"}}} |
... | ... | @@ -38,7 +35,6 @@ describe Notice do |
38 | 35 | end |
39 | 36 | end |
40 | 37 | |
41 | - | |
42 | 38 | describe "user agent" do |
43 | 39 | it "should be parsed and human-readable" do |
44 | 40 | notice = Fabricate.build(:notice, :request => {'cgi-data' => {'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16'}}) |
... | ... | @@ -80,4 +76,11 @@ describe Notice do |
80 | 76 | notice.host.should == 'N/A' |
81 | 77 | end |
82 | 78 | end |
79 | + | |
80 | + describe "request" do | |
81 | + it "returns empty hash if not set" do | |
82 | + notice = Notice.new | |
83 | + notice.request.should == {} | |
84 | + end | |
85 | + end | |
83 | 86 | end | ... | ... |
spec/models/notification_service/pushover_service_spec.rb
spec/models/problem_spec.rb
... | ... | @@ -24,7 +24,7 @@ describe Problem do |
24 | 24 | end |
25 | 25 | end |
26 | 26 | end |
27 | - | |
27 | + | |
28 | 28 | context '#last_notice_at' do |
29 | 29 | it "returns the created_at timestamp of the latest notice" do |
30 | 30 | err = Fabricate(:err) |
... | ... | @@ -113,7 +113,7 @@ describe Problem do |
113 | 113 | problem = Fabricate(:problem, :notices_count => 1) |
114 | 114 | original_notices_count = problem.notices_count |
115 | 115 | original_notices_count.should > 0 |
116 | - | |
116 | + | |
117 | 117 | problem.resolve! |
118 | 118 | problem.notices_count.should == original_notices_count |
119 | 119 | end | ... | ... |
spec/views/apps/index.html.haml_spec.rb
... | ... | @@ -4,8 +4,6 @@ describe "apps/index.html.haml" do |
4 | 4 | before do |
5 | 5 | app = stub_model(App, :deploys => [stub_model(Deploy, :created_at => Time.now, :revision => "123456789abcdef")]) |
6 | 6 | assign :apps, [app] |
7 | - assign :problem_counts, {app.id => 0} | |
8 | - assign :unresolved_counts, {app.id => 0} | |
9 | 7 | controller.stub(:current_user) { stub_model(User) } |
10 | 8 | end |
11 | 9 | ... | ... |