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
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
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 | ... | ... |