Commit 5aa64429386b4698930f9d70d04332a2ff367ef8
Exists in
master
and in
1 other branch
Merge upstream
Showing
163 changed files
with
3043 additions
and
1571 deletions
Show diff stats
Too many changes.
To preserve performance only 100 of 163 files displayed.
.travis.yml
Gemfile
| 1 | 1 | source 'http://rubygems.org' |
| 2 | 2 | |
| 3 | -gem 'rails', '3.2.6' | |
| 4 | - | |
| 5 | -gem 'nokogiri' | |
| 3 | +gem 'rails', '3.2.8' | |
| 6 | 4 | gem 'mongoid', '~> 2.4.10' |
| 7 | - | |
| 5 | +gem 'mongoid_rails_migrations' | |
| 6 | +gem 'devise', '~> 1.5.3' | |
| 7 | +gem 'nokogiri' | |
| 8 | 8 | gem 'haml' |
| 9 | 9 | gem 'htmlentities', "~> 4.3.0" |
| 10 | +gem 'rack-ssl', :require => 'rack/ssl' # force SSL | |
| 10 | 11 | |
| 11 | -gem 'devise', '~> 1.5.3' | |
| 12 | +gem 'useragent', '~> 0.3.1' | |
| 13 | +gem 'inherited_resources' | |
| 14 | +gem 'SystemTimer', :platform => :ruby_18 | |
| 15 | +gem 'actionmailer_inline_css', "~> 1.3.0" | |
| 16 | +gem 'kaminari' | |
| 17 | +gem 'rack-ssl-enforcer' | |
| 18 | +gem 'fabrication', "~> 1.3.0" # Used for both tests and demo data | |
| 19 | +gem 'rails_autolink', '~> 1.0.9' | |
| 20 | +# Please don't update hoptoad_notifier to airbrake. | |
| 21 | +# It's for internal use only, and we monkeypatch certain methods | |
| 22 | +gem 'hoptoad_notifier', "~> 2.4" | |
| 12 | 23 | |
| 13 | -gem 'omniauth-github' | |
| 14 | -gem 'oa-core' | |
| 15 | 24 | |
| 25 | +# Remove / comment out any of the gems below if you want to disable | |
| 26 | +# a given issue tracker, notification service, or authentication. | |
| 27 | + | |
| 28 | +# Issue Trackers | |
| 29 | +# --------------------------------------- | |
| 30 | +# Lighthouse | |
| 16 | 31 | gem 'lighthouse-api' |
| 32 | +# Redmine | |
| 17 | 33 | gem 'oruen_redmine_client', :require => 'redmine_client' |
| 18 | -gem 'mongoid_rails_migrations' | |
| 19 | -gem 'useragent', '~> 0.3.1' | |
| 34 | +# Pivotal Tracker | |
| 20 | 35 | gem 'pivotal-tracker' |
| 36 | +# Fogbugz | |
| 21 | 37 | gem 'ruby-fogbugz', :require => 'fogbugz' |
| 22 | - | |
| 38 | +# Github Issues | |
| 23 | 39 | gem 'octokit', '~> 1.0.0' |
| 24 | 40 | |
| 25 | -gem 'inherited_resources' | |
| 26 | -gem 'SystemTimer', :platform => :ruby_18 | |
| 27 | -gem 'hoptoad_notifier', "~> 2.4" | |
| 28 | -gem 'actionmailer_inline_css', "~> 1.3.0" | |
| 29 | -gem 'kaminari' | |
| 30 | -gem 'rack-ssl-enforcer' | |
| 31 | -gem 'fabrication', "~> 1.3.0" # Both for tests, and loading demo data | |
| 32 | -gem 'rails_autolink', '~> 1.0.9' | |
| 41 | +# Bitbucket Issues | |
| 42 | +gem 'bitbucket_rest_api' | |
| 43 | + | |
| 44 | +# Notification services | |
| 45 | +# --------------------------------------- | |
| 46 | +# Campfire | |
| 47 | +gem 'campy' | |
| 48 | +# Hipchat | |
| 49 | +gem 'hipchat' | |
| 50 | +# Google Talk | |
| 51 | +gem 'xmpp4r' | |
| 52 | +# Hoiio (SMS) | |
| 53 | +gem 'hoi' | |
| 54 | +# Pushover (iOS Push notifications) | |
| 55 | +gem 'rushover' | |
| 56 | + | |
| 57 | +# Authentication | |
| 58 | +# --------------------------------------- | |
| 59 | +# GitHub OAuth | |
| 60 | +gem 'omniauth-github' | |
| 61 | + | |
| 33 | 62 | |
| 34 | 63 | platform :ruby do |
| 35 | - gem 'mongo', '= 1.3.1' | |
| 36 | - gem 'bson', '= 1.3.1' | |
| 37 | - gem 'bson_ext', '= 1.3.1' | |
| 64 | + gem 'mongo', '= 1.6.2' | |
| 65 | + gem 'bson', '= 1.6.2' | |
| 66 | + gem 'bson_ext', '= 1.6.2' | |
| 38 | 67 | end |
| 39 | 68 | |
| 69 | +gem 'omniauth' | |
| 70 | +gem 'oa-core' | |
| 40 | 71 | gem 'ri_cal' |
| 41 | -gem 'yajl-ruby' | |
| 72 | +gem 'yajl-ruby', :require => "yajl" | |
| 42 | 73 | |
| 43 | 74 | group :development, :test do |
| 44 | 75 | gem 'rspec-rails', '~> 2.6' |
| ... | ... | @@ -46,8 +77,13 @@ group :development, :test do |
| 46 | 77 | unless ENV["CI"] |
| 47 | 78 | gem 'ruby-debug', :platform => :mri_18 |
| 48 | 79 | gem 'debugger', :platform => :mri_19 |
| 80 | + gem 'pry' | |
| 81 | + gem 'pry-rails' | |
| 49 | 82 | end |
| 50 | - # gem 'rpm_contrib', :git => "git://github.com/bensymonds/rpm_contrib.git", :branch => "mongo-1.4.0_update" | |
| 83 | +# gem 'rpm_contrib' | |
| 84 | +# gem 'newrelic_rpm' | |
| 85 | + gem 'capistrano' | |
| 86 | + gem 'capistrano_colors' | |
| 51 | 87 | end |
| 52 | 88 | |
| 53 | 89 | group :test do |
| ... | ... | @@ -56,6 +92,7 @@ group :test do |
| 56 | 92 | gem 'rspec', '~> 2.6' |
| 57 | 93 | gem 'database_cleaner', '~> 0.6.0' |
| 58 | 94 | gem 'email_spec' |
| 95 | + gem 'timecop' | |
| 59 | 96 | end |
| 60 | 97 | |
| 61 | 98 | group :heroku do |
| ... | ... | @@ -72,3 +109,5 @@ group :assets do |
| 72 | 109 | gem 'therubyracer', :platform => :ruby # C Ruby (MRI) or Rubinius, but NOT Windows |
| 73 | 110 | gem 'uglifier', '>= 1.0.3' |
| 74 | 111 | end |
| 112 | + | |
| 113 | +gem 'turbo-sprockets-rails3' | ... | ... |
Gemfile.lock
| ... | ... | @@ -2,43 +2,60 @@ GEM |
| 2 | 2 | remote: http://rubygems.org/ |
| 3 | 3 | specs: |
| 4 | 4 | SystemTimer (1.2.3) |
| 5 | - actionmailer (3.2.6) | |
| 6 | - actionpack (= 3.2.6) | |
| 5 | + actionmailer (3.2.8) | |
| 6 | + actionpack (= 3.2.8) | |
| 7 | 7 | mail (~> 2.4.4) |
| 8 | 8 | actionmailer_inline_css (1.3.1) |
| 9 | 9 | actionmailer (>= 3.0.0) |
| 10 | 10 | nokogiri (>= 1.4.4) |
| 11 | 11 | premailer (>= 1.7.1) |
| 12 | - actionpack (3.2.6) | |
| 13 | - activemodel (= 3.2.6) | |
| 14 | - activesupport (= 3.2.6) | |
| 12 | + actionpack (3.2.8) | |
| 13 | + activemodel (= 3.2.8) | |
| 14 | + activesupport (= 3.2.8) | |
| 15 | 15 | builder (~> 3.0.0) |
| 16 | 16 | erubis (~> 2.7.0) |
| 17 | - journey (~> 1.0.1) | |
| 17 | + journey (~> 1.0.4) | |
| 18 | 18 | rack (~> 1.4.0) |
| 19 | 19 | rack-cache (~> 1.2) |
| 20 | 20 | rack-test (~> 0.6.1) |
| 21 | 21 | sprockets (~> 2.1.3) |
| 22 | - activemodel (3.2.6) | |
| 23 | - activesupport (= 3.2.6) | |
| 22 | + activemodel (3.2.8) | |
| 23 | + activesupport (= 3.2.8) | |
| 24 | 24 | builder (~> 3.0.0) |
| 25 | - activerecord (3.2.6) | |
| 26 | - activemodel (= 3.2.6) | |
| 27 | - activesupport (= 3.2.6) | |
| 25 | + activerecord (3.2.8) | |
| 26 | + activemodel (= 3.2.8) | |
| 27 | + activesupport (= 3.2.8) | |
| 28 | 28 | arel (~> 3.0.2) |
| 29 | 29 | tzinfo (~> 0.3.29) |
| 30 | - activeresource (3.2.6) | |
| 31 | - activemodel (= 3.2.6) | |
| 32 | - activesupport (= 3.2.6) | |
| 33 | - activesupport (3.2.6) | |
| 30 | + activeresource (3.2.8) | |
| 31 | + activemodel (= 3.2.8) | |
| 32 | + activesupport (= 3.2.8) | |
| 33 | + activesupport (3.2.8) | |
| 34 | 34 | i18n (~> 0.6) |
| 35 | 35 | multi_json (~> 1.0) |
| 36 | - addressable (2.2.8) | |
| 36 | + addressable (2.3.2) | |
| 37 | 37 | arel (3.0.2) |
| 38 | 38 | bcrypt-ruby (3.0.1) |
| 39 | - bson (1.3.1) | |
| 40 | - bson_ext (1.3.1) | |
| 41 | - builder (3.0.0) | |
| 39 | + bitbucket_rest_api (0.1.1) | |
| 40 | + faraday (~> 0.8.1) | |
| 41 | + faraday_middleware (~> 0.8.1) | |
| 42 | + hashie (~> 1.2.0) | |
| 43 | + multi_json (~> 1.3) | |
| 44 | + nokogiri (~> 1.5.2) | |
| 45 | + simple_oauth | |
| 46 | + bson (1.6.2) | |
| 47 | + bson_ext (1.6.2) | |
| 48 | + bson (~> 1.6.2) | |
| 49 | + builder (3.0.4) | |
| 50 | + campy (0.1.3) | |
| 51 | + multi_json (~> 1.0) | |
| 52 | + capistrano (2.13.4) | |
| 53 | + highline | |
| 54 | + net-scp (>= 1.0.0) | |
| 55 | + net-sftp (>= 2.0.0) | |
| 56 | + net-ssh (>= 2.0.14) | |
| 57 | + net-ssh-gateway (>= 1.1.0) | |
| 58 | + capistrano_colors (0.5.5) | |
| 42 | 59 | capybara (1.1.2) |
| 43 | 60 | mime-types (>= 1.16) |
| 44 | 61 | nokogiri (>= 1.3.3) |
| ... | ... | @@ -46,8 +63,9 @@ GEM |
| 46 | 63 | rack-test (>= 0.5.4) |
| 47 | 64 | selenium-webdriver (~> 2.0) |
| 48 | 65 | xpath (~> 0.1.4) |
| 49 | - childprocess (0.3.2) | |
| 50 | - ffi (~> 1.0.6) | |
| 66 | + childprocess (0.3.5) | |
| 67 | + ffi (~> 1.0, >= 1.0.6) | |
| 68 | + coderay (1.0.6) | |
| 51 | 69 | columnize (0.3.6) |
| 52 | 70 | crack (0.3.1) |
| 53 | 71 | css_parser (1.2.6) |
| ... | ... | @@ -55,13 +73,13 @@ GEM |
| 55 | 73 | rdoc |
| 56 | 74 | daemons (1.1.8) |
| 57 | 75 | database_cleaner (0.6.7) |
| 58 | - debugger (1.1.3) | |
| 76 | + debugger (1.2.0) | |
| 59 | 77 | columnize (>= 0.3.1) |
| 60 | 78 | debugger-linecache (~> 1.1.1) |
| 61 | - debugger-ruby_core_source (~> 1.1.2) | |
| 62 | - debugger-linecache (1.1.1) | |
| 79 | + debugger-ruby_core_source (~> 1.1.3) | |
| 80 | + debugger-linecache (1.1.2) | |
| 63 | 81 | debugger-ruby_core_source (>= 1.1.1) |
| 64 | - debugger-ruby_core_source (1.1.2) | |
| 82 | + debugger-ruby_core_source (1.1.3) | |
| 65 | 83 | devise (1.5.3) |
| 66 | 84 | bcrypt-ruby (~> 3.0) |
| 67 | 85 | orm_adapter (~> 0.0.3) |
| ... | ... | @@ -75,38 +93,49 @@ GEM |
| 75 | 93 | execjs (1.4.0) |
| 76 | 94 | multi_json (~> 1.0) |
| 77 | 95 | fabrication (1.3.2) |
| 78 | - faraday (0.8.1) | |
| 96 | + faraday (0.8.4) | |
| 79 | 97 | multipart-post (~> 1.1) |
| 80 | - faraday_middleware (0.8.7) | |
| 98 | + faraday_middleware (0.8.8) | |
| 81 | 99 | faraday (>= 0.7.4, < 0.9) |
| 82 | - ffi (1.0.11) | |
| 83 | - haml (3.1.5) | |
| 100 | + ffi (1.1.4) | |
| 101 | + haml (3.1.6) | |
| 84 | 102 | happymapper (0.4.0) |
| 85 | 103 | libxml-ruby (~> 2.0) |
| 86 | 104 | has_scope (0.5.1) |
| 87 | 105 | hashie (1.2.0) |
| 106 | + highline (1.6.15) | |
| 88 | 107 | hike (1.2.1) |
| 108 | + hipchat (0.4.1) | |
| 109 | + httparty | |
| 110 | + hoi (0.0.6) | |
| 111 | + httparty (> 0.6.0) | |
| 112 | + json (> 1.4.0) | |
| 89 | 113 | hoptoad_notifier (2.4.11) |
| 90 | 114 | activesupport |
| 91 | 115 | builder |
| 92 | 116 | htmlentities (4.3.1) |
| 93 | - i18n (0.6.0) | |
| 117 | + httparty (0.9.0) | |
| 118 | + multi_json (~> 1.0) | |
| 119 | + multi_xml | |
| 120 | + httpauth (0.1) | |
| 121 | + i18n (0.6.1) | |
| 94 | 122 | inherited_resources (1.3.1) |
| 95 | 123 | has_scope (~> 0.5.0) |
| 96 | 124 | responders (~> 0.6) |
| 97 | 125 | journey (1.0.4) |
| 98 | - json (1.7.3) | |
| 99 | - kaminari (0.13.0) | |
| 126 | + json (1.7.5) | |
| 127 | + jwt (0.1.5) | |
| 128 | + multi_json (>= 1.0) | |
| 129 | + kaminari (0.14.1) | |
| 100 | 130 | actionpack (>= 3.0.0) |
| 101 | 131 | activesupport (>= 3.0.0) |
| 102 | - railties (>= 3.0.0) | |
| 103 | 132 | kgio (2.7.4) |
| 104 | - launchy (2.1.0) | |
| 105 | - addressable (~> 2.2.6) | |
| 133 | + launchy (2.1.2) | |
| 134 | + addressable (~> 2.3) | |
| 106 | 135 | libv8 (3.3.10.4) |
| 107 | - libwebsocket (0.1.3) | |
| 136 | + libwebsocket (0.1.5) | |
| 108 | 137 | addressable |
| 109 | - libxml-ruby (2.3.2) | |
| 138 | + libxml-ruby (2.3.3) | |
| 110 | 139 | lighthouse-api (2.0) |
| 111 | 140 | activeresource (>= 3.0.0) |
| 112 | 141 | activesupport (>= 3.0.0) |
| ... | ... | @@ -116,9 +145,10 @@ GEM |
| 116 | 145 | i18n (>= 0.4.0) |
| 117 | 146 | mime-types (~> 1.16) |
| 118 | 147 | treetop (~> 1.4.8) |
| 119 | - mime-types (1.18) | |
| 120 | - mongo (1.3.1) | |
| 121 | - bson (>= 1.3.1) | |
| 148 | + method_source (0.7.1) | |
| 149 | + mime-types (1.19) | |
| 150 | + mongo (1.6.2) | |
| 151 | + bson (~> 1.6.2) | |
| 122 | 152 | mongoid (2.4.10) |
| 123 | 153 | activemodel (~> 3.1) |
| 124 | 154 | mongo (~> 1.3) |
| ... | ... | @@ -129,26 +159,37 @@ GEM |
| 129 | 159 | rails (>= 3.0.0) |
| 130 | 160 | railties (>= 3.0.0) |
| 131 | 161 | multi_json (1.3.6) |
| 162 | + multi_xml (0.5.1) | |
| 132 | 163 | multipart-post (1.1.5) |
| 133 | - nokogiri (1.5.0) | |
| 164 | + net-scp (1.0.4) | |
| 165 | + net-ssh (>= 1.99.1) | |
| 166 | + net-sftp (2.0.5) | |
| 167 | + net-ssh (>= 2.0.9) | |
| 168 | + net-ssh (2.6.1) | |
| 169 | + net-ssh-gateway (1.1.0) | |
| 170 | + net-ssh (>= 1.99.1) | |
| 171 | + nokogiri (1.5.5) | |
| 134 | 172 | oa-core (0.3.2) |
| 135 | - oauth2 (0.5.2) | |
| 136 | - faraday (~> 0.7) | |
| 173 | + oauth2 (0.8.0) | |
| 174 | + faraday (~> 0.8) | |
| 175 | + httpauth (~> 0.1) | |
| 176 | + jwt (~> 0.1.4) | |
| 137 | 177 | multi_json (~> 1.0) |
| 178 | + rack (~> 1.2) | |
| 138 | 179 | octokit (1.0.7) |
| 139 | 180 | addressable (~> 2.2) |
| 140 | 181 | faraday (~> 0.8) |
| 141 | 182 | faraday_middleware (~> 0.8) |
| 142 | 183 | hashie (~> 1.2) |
| 143 | 184 | multi_json (~> 1.3) |
| 144 | - omniauth (1.0.3) | |
| 185 | + omniauth (1.1.1) | |
| 145 | 186 | hashie (~> 1.2) |
| 146 | 187 | rack |
| 147 | - omniauth-github (1.0.1) | |
| 188 | + omniauth-github (1.0.2) | |
| 148 | 189 | omniauth (~> 1.0) |
| 149 | - omniauth-oauth2 (~> 1.0) | |
| 150 | - omniauth-oauth2 (1.0.0) | |
| 151 | - oauth2 (~> 0.5.0) | |
| 190 | + omniauth-oauth2 (~> 1.1) | |
| 191 | + omniauth-oauth2 (1.1.1) | |
| 192 | + oauth2 (~> 0.8.0) | |
| 152 | 193 | omniauth (~> 1.0) |
| 153 | 194 | orm_adapter (0.0.7) |
| 154 | 195 | oruen_redmine_client (0.0.1) |
| ... | ... | @@ -166,54 +207,60 @@ GEM |
| 166 | 207 | premailer (1.7.3) |
| 167 | 208 | css_parser (>= 1.1.9) |
| 168 | 209 | htmlentities (>= 4.0.0) |
| 210 | + pry (0.9.9.6) | |
| 211 | + coderay (~> 1.0.5) | |
| 212 | + method_source (~> 0.7.1) | |
| 213 | + slop (>= 2.4.4, < 3) | |
| 214 | + pry-rails (0.2.0) | |
| 215 | + pry | |
| 169 | 216 | rack (1.4.1) |
| 170 | 217 | rack-cache (1.2) |
| 171 | 218 | rack (>= 0.4) |
| 172 | 219 | rack-ssl (1.3.2) |
| 173 | 220 | rack |
| 174 | 221 | rack-ssl-enforcer (0.2.4) |
| 175 | - rack-test (0.6.1) | |
| 222 | + rack-test (0.6.2) | |
| 176 | 223 | rack (>= 1.0) |
| 177 | - rails (3.2.6) | |
| 178 | - actionmailer (= 3.2.6) | |
| 179 | - actionpack (= 3.2.6) | |
| 180 | - activerecord (= 3.2.6) | |
| 181 | - activeresource (= 3.2.6) | |
| 182 | - activesupport (= 3.2.6) | |
| 224 | + rails (3.2.8) | |
| 225 | + actionmailer (= 3.2.8) | |
| 226 | + actionpack (= 3.2.8) | |
| 227 | + activerecord (= 3.2.8) | |
| 228 | + activeresource (= 3.2.8) | |
| 229 | + activesupport (= 3.2.8) | |
| 183 | 230 | bundler (~> 1.0) |
| 184 | - railties (= 3.2.6) | |
| 231 | + railties (= 3.2.8) | |
| 185 | 232 | rails_autolink (1.0.9) |
| 186 | 233 | rails (~> 3.1) |
| 187 | - railties (3.2.6) | |
| 188 | - actionpack (= 3.2.6) | |
| 189 | - activesupport (= 3.2.6) | |
| 234 | + railties (3.2.8) | |
| 235 | + actionpack (= 3.2.8) | |
| 236 | + activesupport (= 3.2.8) | |
| 190 | 237 | rack-ssl (~> 1.3.2) |
| 191 | 238 | rake (>= 0.8.7) |
| 192 | 239 | rdoc (~> 3.4) |
| 193 | 240 | thor (>= 0.14.6, < 2.0) |
| 194 | - raindrops (0.8.0) | |
| 241 | + raindrops (0.10.0) | |
| 195 | 242 | rake (0.9.2.2) |
| 196 | 243 | rbx-require-relative (0.0.9) |
| 197 | 244 | rdoc (3.12) |
| 198 | 245 | json (~> 1.4) |
| 199 | - responders (0.9.1) | |
| 246 | + responders (0.9.2) | |
| 200 | 247 | railties (~> 3.1) |
| 201 | 248 | rest-client (1.6.7) |
| 202 | 249 | mime-types (>= 1.16) |
| 203 | 250 | ri_cal (0.8.8) |
| 204 | - rspec (2.10.0) | |
| 205 | - rspec-core (~> 2.10.0) | |
| 206 | - rspec-expectations (~> 2.10.0) | |
| 207 | - rspec-mocks (~> 2.10.0) | |
| 208 | - rspec-core (2.10.0) | |
| 209 | - rspec-expectations (2.10.0) | |
| 251 | + rspec (2.11.0) | |
| 252 | + rspec-core (~> 2.11.0) | |
| 253 | + rspec-expectations (~> 2.11.0) | |
| 254 | + rspec-mocks (~> 2.11.0) | |
| 255 | + rspec-core (2.11.1) | |
| 256 | + rspec-expectations (2.11.2) | |
| 210 | 257 | diff-lcs (~> 1.1.3) |
| 211 | - rspec-mocks (2.10.1) | |
| 212 | - rspec-rails (2.10.1) | |
| 258 | + rspec-mocks (2.11.1) | |
| 259 | + rspec-rails (2.11.0) | |
| 213 | 260 | actionpack (>= 3.0) |
| 214 | 261 | activesupport (>= 3.0) |
| 215 | 262 | railties (>= 3.0) |
| 216 | - rspec (~> 2.10.0) | |
| 263 | + rspec (~> 2.11.0) | |
| 217 | 264 | ruby-debug (0.10.4) |
| 218 | 265 | columnize (>= 0.1) |
| 219 | 266 | ruby-debug-base (~> 0.10.4.0) |
| ... | ... | @@ -221,42 +268,51 @@ GEM |
| 221 | 268 | linecache (>= 0.3) |
| 222 | 269 | ruby-fogbugz (0.1.1) |
| 223 | 270 | crack |
| 224 | - rubyzip (0.9.8) | |
| 225 | - selenium-webdriver (2.21.2) | |
| 271 | + rubyzip (0.9.9) | |
| 272 | + rushover (0.1.1) | |
| 273 | + json | |
| 274 | + rest-client | |
| 275 | + selenium-webdriver (2.25.0) | |
| 226 | 276 | childprocess (>= 0.2.5) |
| 227 | - ffi (~> 1.0) | |
| 228 | 277 | libwebsocket (~> 0.1.3) |
| 229 | 278 | multi_json (~> 1.0) |
| 230 | 279 | rubyzip |
| 280 | + simple_oauth (0.1.9) | |
| 281 | + slop (2.4.4) | |
| 231 | 282 | sprockets (2.1.3) |
| 232 | 283 | hike (~> 1.2) |
| 233 | 284 | rack (~> 1.0) |
| 234 | 285 | tilt (~> 1.1, != 1.3.0) |
| 235 | - therubyracer (0.10.1) | |
| 286 | + therubyracer (0.10.2) | |
| 236 | 287 | libv8 (~> 3.3.10) |
| 237 | - thin (1.3.1) | |
| 288 | + thin (1.4.1) | |
| 238 | 289 | daemons (>= 1.0.9) |
| 239 | 290 | eventmachine (>= 0.12.6) |
| 240 | 291 | rack (>= 1.0.0) |
| 241 | - thor (0.15.2) | |
| 292 | + thor (0.16.0) | |
| 242 | 293 | tilt (1.3.3) |
| 294 | + timecop (0.3.5) | |
| 243 | 295 | treetop (1.4.10) |
| 244 | 296 | polyglot |
| 245 | 297 | polyglot (>= 0.3.1) |
| 298 | + turbo-sprockets-rails3 (0.1.10) | |
| 299 | + railties (>= 3.1.0) | |
| 300 | + sprockets (>= 2.0.0) | |
| 246 | 301 | tzinfo (0.3.33) |
| 247 | - uglifier (1.2.4) | |
| 302 | + uglifier (1.2.7) | |
| 248 | 303 | execjs (>= 0.3.0) |
| 249 | - multi_json (>= 1.0.2) | |
| 304 | + multi_json (~> 1.3) | |
| 250 | 305 | unicorn (4.3.1) |
| 251 | 306 | kgio (~> 2.6) |
| 252 | 307 | rack |
| 253 | 308 | raindrops (~> 0.7) |
| 254 | 309 | useragent (0.3.2) |
| 255 | - warden (1.2.0) | |
| 310 | + warden (1.2.1) | |
| 256 | 311 | rack (>= 1.0) |
| 257 | - webmock (1.8.6) | |
| 312 | + webmock (1.8.7) | |
| 258 | 313 | addressable (>= 2.2.7) |
| 259 | 314 | crack (>= 0.1.7) |
| 315 | + xmpp4r (0.5) | |
| 260 | 316 | xpath (0.1.4) |
| 261 | 317 | nokogiri (~> 1.3) |
| 262 | 318 | yajl-ruby (1.1.0) |
| ... | ... | @@ -267,8 +323,12 @@ PLATFORMS |
| 267 | 323 | DEPENDENCIES |
| 268 | 324 | SystemTimer |
| 269 | 325 | actionmailer_inline_css (~> 1.3.0) |
| 270 | - bson (= 1.3.1) | |
| 271 | - bson_ext (= 1.3.1) | |
| 326 | + bitbucket_rest_api | |
| 327 | + bson (= 1.6.2) | |
| 328 | + bson_ext (= 1.6.2) | |
| 329 | + campy | |
| 330 | + capistrano | |
| 331 | + capistrano_colors | |
| 272 | 332 | capybara |
| 273 | 333 | database_cleaner (~> 0.6.0) |
| 274 | 334 | debugger |
| ... | ... | @@ -277,33 +337,43 @@ DEPENDENCIES |
| 277 | 337 | execjs |
| 278 | 338 | fabrication (~> 1.3.0) |
| 279 | 339 | haml |
| 340 | + hipchat | |
| 341 | + hoi | |
| 280 | 342 | hoptoad_notifier (~> 2.4) |
| 281 | 343 | htmlentities (~> 4.3.0) |
| 282 | 344 | inherited_resources |
| 283 | 345 | kaminari |
| 284 | 346 | launchy |
| 285 | 347 | lighthouse-api |
| 286 | - mongo (= 1.3.1) | |
| 348 | + mongo (= 1.6.2) | |
| 287 | 349 | mongoid (~> 2.4.10) |
| 288 | 350 | mongoid_rails_migrations |
| 289 | 351 | nokogiri |
| 290 | 352 | oa-core |
| 291 | 353 | octokit (~> 1.0.0) |
| 354 | + omniauth | |
| 292 | 355 | omniauth-github |
| 293 | 356 | oruen_redmine_client |
| 294 | 357 | pivotal-tracker |
| 358 | + pry | |
| 359 | + pry-rails | |
| 360 | + rack-ssl | |
| 295 | 361 | rack-ssl-enforcer |
| 296 | - rails (= 3.2.6) | |
| 362 | + rails (= 3.2.8) | |
| 297 | 363 | rails_autolink (~> 1.0.9) |
| 298 | 364 | ri_cal |
| 299 | 365 | rspec (~> 2.6) |
| 300 | 366 | rspec-rails (~> 2.6) |
| 301 | 367 | ruby-debug |
| 302 | 368 | ruby-fogbugz |
| 369 | + rushover | |
| 303 | 370 | therubyracer |
| 304 | 371 | thin |
| 372 | + timecop | |
| 373 | + turbo-sprockets-rails3 | |
| 305 | 374 | uglifier (>= 1.0.3) |
| 306 | 375 | unicorn |
| 307 | 376 | useragent (~> 0.3.1) |
| 308 | 377 | webmock |
| 378 | + xmpp4r | |
| 309 | 379 | yajl-ruby | ... | ... |
README.md
| 1 | -# Errbit [![TravisCI][travis-img-url]][travis-ci-url] | |
| 1 | +# Errbit [![TravisCI][travis-img-url]][travis-ci-url] [![Code Climate][codeclimate-img-url]][codeclimate-url] | |
| 2 | 2 | |
| 3 | 3 | [travis-img-url]: https://secure.travis-ci.org/errbit/errbit.png?branch=master |
| 4 | 4 | [travis-ci-url]: http://travis-ci.org/errbit/errbit |
| 5 | +[codeclimate-img-url]: https://codeclimate.com/badge.png | |
| 6 | +[codeclimate-url]: https://codeclimate.com/github/errbit/errbit | |
| 7 | + | |
| 5 | 8 | |
| 6 | 9 | ### The open source, self-hosted error catcher |
| 7 | 10 | |
| ... | ... | @@ -57,12 +60,17 @@ If this doesn't sound like you, you should probably stick with [Airbrake](http:/ |
| 57 | 60 | The [Thoughtbot](http://thoughtbot.com) guys offer great support for it and it is much more worry-free. |
| 58 | 61 | They have a free package and even offer a *"Airbrake behind your firewall"* solution. |
| 59 | 62 | |
| 63 | +Mailing List | |
| 64 | +------------ | |
| 65 | + | |
| 66 | +Join the Google Group at https://groups.google.com/group/errbit to receive updates and notifications. | |
| 67 | + | |
| 60 | 68 | Demo |
| 61 | 69 | ---- |
| 62 | 70 | |
| 63 | 71 | There is a demo available at [http://errbit-demo.herokuapp.com/](http://errbit-demo.herokuapp.com/) |
| 64 | 72 | |
| 65 | -Email: demo@errbit-demo.herokuapp.com | |
| 73 | +Email: demo@errbit-demo.herokuapp.com<br/> | |
| 66 | 74 | Password: password |
| 67 | 75 | |
| 68 | 76 | Installation |
| ... | ... | @@ -145,9 +153,6 @@ git clone http://github.com/errbit/errbit.git |
| 145 | 153 | gem install heroku |
| 146 | 154 | heroku create example-errbit --stack cedar |
| 147 | 155 | heroku addons:add mongolab:starter |
| 148 | -cp -f config/mongoid.mongolab.yml config/mongoid.yml | |
| 149 | -git add -f config/mongoid.yml | |
| 150 | -git commit -m "Added mongoid config for Mongolab" | |
| 151 | 156 | heroku addons:add sendgrid:starter |
| 152 | 157 | heroku config:add HEROKU=true |
| 153 | 158 | heroku config:add ERRBIT_HOST=some-hostname.example.com |
| ... | ... | @@ -168,7 +173,7 @@ heroku run rake db:seed |
| 168 | 173 | ```bash |
| 169 | 174 | # Install the heroku scheduler add-on |
| 170 | 175 | heroku addons:add scheduler:standard |
| 171 | - | |
| 176 | + | |
| 172 | 177 | # Go open the dashboard to schedule the job. You should use |
| 173 | 178 | # 'rake errbit:db:clear_resolved' as the task command, and schedule it |
| 174 | 179 | # at whatever frequency you like (once/day should work great). |
| ... | ... | @@ -185,7 +190,7 @@ heroku run rake db:seed |
| 185 | 190 | * Or clear resolved errors manually: |
| 186 | 191 | |
| 187 | 192 | ```bash |
| 188 | - heroku rake errbit:db:clear_resolved | |
| 193 | + heroku run rake errbit:db:clear_resolved | |
| 189 | 194 | ``` |
| 190 | 195 | |
| 191 | 196 | * You may want to enable the deployment hook for heroku : |
| ... | ... | @@ -275,6 +280,11 @@ GITHUB_ACCESS_SCOPE=repo,public_repo |
| 275 | 280 | * In `config/config.yml`, set `user_has_username` to `true` |
| 276 | 281 | * Follow the instructions at https://github.com/cschiewek/devise_ldap_authenticatable |
| 277 | 282 | to set up the devise_ldap_authenticatable gem. |
| 283 | + * Ensure to set ```config.ldap_create_user = true``` in ```config/initializers/devise.rb```, this enables creating the users from LDAP, otherwhise login will not work. | |
| 284 | + * Create a new initializer (e.g. ```config/initializers/devise_ldap.rb```) and add the following code to enable ldap authentication in the User-model: | |
| 285 | +```ruby | |
| 286 | +Errbit::Config.devise_modules << :ldap_authenticatable | |
| 287 | +``` | |
| 278 | 288 | |
| 279 | 289 | * If you are authenticating by `username`, you will need to set the user's email manually |
| 280 | 290 | before authentication. You must add the following lines to `app/models/user.rb`: |
| ... | ... | @@ -286,6 +296,15 @@ GITHUB_ACCESS_SCOPE=repo,public_repo |
| 286 | 296 | end |
| 287 | 297 | ``` |
| 288 | 298 | |
| 299 | + * Now login with your user from LDAP, this will create a user in the database | |
| 300 | + * Open a rails console and set the admin flag for your user: | |
| 301 | + | |
| 302 | +```ruby | |
| 303 | +user = User.first | |
| 304 | +user.admin = true | |
| 305 | +user.save! | |
| 306 | +``` | |
| 307 | + | |
| 289 | 308 | Upgrading |
| 290 | 309 | --------- |
| 291 | 310 | When upgrading Errbit, please run: |
| ... | ... | @@ -357,6 +376,12 @@ card_type = Defect, status = Open, priority = Essential |
| 357 | 376 | * For 'Account/Repository', the account will either be a username or organization. i.e. **errbit/errbit** |
| 358 | 377 | * You will also need to provide your username and password for your GitHub account. |
| 359 | 378 | * (We'd really appreciate it if you wanted to help us implement OAuth instead!) |
| 379 | + | |
| 380 | +**Bitbucket Issues Integration** | |
| 381 | + | |
| 382 | +* For 'BITBUCKET REPO' field, the account will either be a username or organization. i.e. **errbit/errbit** | |
| 383 | +* You will also need to provide your username and password for your Bitbucket account. | |
| 384 | + | |
| 360 | 385 | |
| 361 | 386 | |
| 362 | 387 | What if Errbit has an error? |
| ... | ... | @@ -384,6 +409,23 @@ or you can set up the GitHub Issues tracker for your **Self.Errbit** app: |
| 384 | 409 | * You can now easily post bug reports to GitHub Issues by clicking the **Create Issue** button on a **Self.Errbit** error. |
| 385 | 410 | |
| 386 | 411 | |
| 412 | +Use Errbit with applications written in other languages | |
| 413 | +------------------------------------------------------- | |
| 414 | + | |
| 415 | +In theory, any Airbrake-compatible error catcher for other languages should work with Errbit. | |
| 416 | +Solutions known to work are listed below: | |
| 417 | + | |
| 418 | +<table> | |
| 419 | + <tr> | |
| 420 | + <th>PHP (>= 5.3)</th> | |
| 421 | + <td>https://github.com/flippa/errbit-php</td> | |
| 422 | + </tr> | |
| 423 | + <tr> | |
| 424 | + <th>Python</th> | |
| 425 | + <td>https://github.com/mkorenkov/errbit.py , https://github.com/pulseenergy/airbrakepy</td> | |
| 426 | + </tr> | |
| 427 | +</table> | |
| 428 | + | |
| 387 | 429 | TODO |
| 388 | 430 | ---- |
| 389 | 431 | ... | ... |
2.54 KB
3.61 KB
1.95 KB
3.19 KB
3.19 KB
2.8 KB
4.62 KB
4.62 KB
4.07 KB
2.05 KB
2.05 KB
1.16 KB
1.72 KB
1.72 KB
874 Bytes
1.91 KB
1.91 KB
1.02 KB
app/assets/javascripts/errbit.js
| ... | ... | @@ -130,5 +130,10 @@ $(function() { |
| 130 | 130 | // Hide external backtrace on page load |
| 131 | 131 | hide_external_backtrace(); |
| 132 | 132 | |
| 133 | + $('.head a.show_tail').click(function(e) { | |
| 134 | + $(this).hide().closest('.head_and_tail').find('.tail').show(); | |
| 135 | + e.preventDefault(); | |
| 136 | + }); | |
| 137 | + | |
| 133 | 138 | init(); |
| 134 | 139 | }); | ... | ... |
app/assets/javascripts/form.js
| ... | ... | @@ -8,6 +8,9 @@ $(function(){ |
| 8 | 8 | if($('div.issue_tracker.nested').length) |
| 9 | 9 | activateTypeSelector('issue_tracker', 'tracker_params'); |
| 10 | 10 | |
| 11 | + if($('div.notification_service.nested').length) | |
| 12 | + activateTypeSelector('notification_service', 'notification_params'); | |
| 13 | + | |
| 11 | 14 | $('body').addClass('has-js'); |
| 12 | 15 | $('.label_radio').click(function(){ |
| 13 | 16 | activateLabelIcons(); | ... | ... |
app/assets/stylesheets/application.css.erb
app/assets/stylesheets/errbit.css
| ... | ... | @@ -535,10 +535,11 @@ a.button.active { |
| 535 | 535 | display: inline-block; |
| 536 | 536 | } |
| 537 | 537 | |
| 538 | -/* Watchers and Issue Tracker Forms */ | |
| 539 | -div.watcher.nested .watcher_params, div.issue_tracker.nested .tracker_params { | |
| 538 | +/* Watchers / Issue Tracker / Notification Forms */ | |
| 539 | +div.watcher.nested .watcher_params, div.issue_tracker.nested .tracker_params, div.notification_service.nested .notification_params { | |
| 540 | 540 | display: none; |
| 541 | 541 | } |
| 542 | + | |
| 542 | 543 | div.nested .chosen { |
| 543 | 544 | display: block !important; |
| 544 | 545 | } |
| ... | ... | @@ -546,35 +547,35 @@ div.nested .choose { |
| 546 | 547 | margin-bottom: 0.5em; |
| 547 | 548 | } |
| 548 | 549 | |
| 549 | -div.issue_tracker.nested .choose { | |
| 550 | +div.issue_tracker.nested .choose, div.notification_service.nested .choose { | |
| 550 | 551 | background-color: #ebebeb; |
| 551 | 552 | border: 1px solid #dddddd; |
| 552 | 553 | margin: 0 0 15px; |
| 553 | 554 | padding: 12px; |
| 554 | 555 | } |
| 555 | -div.issue_tracker.nested img { | |
| 556 | +div.issue_tracker.nested img, div.notification_service.nested img { | |
| 556 | 557 | vertical-align: middle; |
| 557 | 558 | } |
| 558 | 559 | |
| 559 | 560 | /* Icons for Issue Tracker Radio Buttons */ |
| 560 | -div.issue_tracker.nested label.label_radio { | |
| 561 | +div.issue_tracker.nested label.label_radio, div.notification_service.nested label.label_radio { | |
| 561 | 562 | color: #929292; |
| 562 | 563 | padding-left: 33px; |
| 563 | 564 | margin-bottom: 6px; |
| 564 | 565 | margin-right: 8px; |
| 565 | 566 | line-height: 30px; |
| 566 | 567 | } |
| 567 | -div.issue_tracker.nested .choose { | |
| 568 | +div.issue_tracker.nested .choose, div.notification_service.nested .choose { | |
| 568 | 569 | padding-bottom: 6px; |
| 569 | 570 | } |
| 570 | -div.issue_tracker.nested label.label_radio:hover { | |
| 571 | +div.issue_tracker.nested label.label_radio:hover, div.notification_service.nested label.label_radio:hover { | |
| 571 | 572 | color: #696969; |
| 572 | 573 | } |
| 573 | -div.issue_tracker.nested .label_radio input { | |
| 574 | +div.issue_tracker.nested .label_radio input, div.notification_service.nested .label_radio input { | |
| 574 | 575 | position: absolute; left: -9999px; |
| 575 | 576 | } |
| 576 | 577 | |
| 577 | -div.issue_tracker.nested label.r_on, div.issue_tracker.nested label.r_on:hover { | |
| 578 | +div.issue_tracker.nested label.r_on, div.issue_tracker.nested label.r_on:hover, div.notification_service.nested label.r_on, div.notification_service.nested label.r_on:hover { | |
| 578 | 579 | color: #191919; |
| 579 | 580 | } |
| 580 | 581 | |
| ... | ... | @@ -648,6 +649,13 @@ table.errs td.app .environment { |
| 648 | 649 | table.errs td.message a { |
| 649 | 650 | display: block; |
| 650 | 651 | word-wrap: break-word; |
| 652 | + /* PjpG - configuration in WHAT & WHERE table's columns using ellipsis to avoid oversizing table's width */ | |
| 653 | + width: 300px; | |
| 654 | + overflow: hidden; | |
| 655 | + text-overflow: ellipsis; | |
| 656 | + -o-text-overflow: ellipsis; | |
| 657 | + white-space: nowrap; | |
| 658 | + /* ------ */ | |
| 651 | 659 | } |
| 652 | 660 | table.errs td.message em { |
| 653 | 661 | color: #727272; |
| ... | ... | @@ -834,15 +842,32 @@ table.comment tbody th { |
| 834 | 842 | height: 20px; |
| 835 | 843 | line-height: 0.5em; |
| 836 | 844 | } |
| 845 | +table.comment th span, table.comment th img { | |
| 846 | + vertical-align: middle; | |
| 847 | +} | |
| 848 | +table.comment th span.comment-info { | |
| 849 | + line-height: 21px; | |
| 850 | + float: left; | |
| 851 | +} | |
| 852 | +table.comment img.gravatar { | |
| 853 | + margin-right: 7px; | |
| 854 | + float: left; | |
| 855 | +} | |
| 856 | + | |
| 837 | 857 | table.comment tbody td { |
| 838 | 858 | background-color: #F9F9F9; |
| 839 | 859 | } |
| 840 | 860 | #content-comments a.destroy-comment { |
| 841 | 861 | color: #EE0000; |
| 842 | 862 | margin-right: 5px; |
| 863 | + margin-top: 2px; | |
| 864 | + font-size: 21px; | |
| 865 | + line-height: 1; | |
| 866 | + float: right; | |
| 843 | 867 | } |
| 844 | 868 | #content-comments a.destroy-comment:hover { |
| 845 | 869 | text-decoration: none; |
| 870 | + color: #AA0000; | |
| 846 | 871 | } |
| 847 | 872 | #content-comments #comment_submit { |
| 848 | 873 | margin-top: 15px; |
| ... | ... | @@ -871,3 +896,12 @@ table.errs tr td.message .inline_comment em.commenter { |
| 871 | 896 | |
| 872 | 897 | .current.asc:after { content: ' ↑'; } |
| 873 | 898 | .current.desc:after { content: ' ↓'; } |
| 899 | + | |
| 900 | + | |
| 901 | +table.users td { | |
| 902 | + vertical-align: middle; | |
| 903 | +} | |
| 904 | +table.users td img.gravatar { | |
| 905 | + vertical-align: middle; | |
| 906 | + margin-left: 3px; | |
| 907 | +} | ... | ... |
app/assets/stylesheets/issue_tracker_icons.css.erb
| 1 | 1 | /* Issue Tracker inactive, select, create and goto icons */ |
| 2 | 2 | <% trackers = IssueTracker.subclasses.map{|t| t.label } << 'none' %> |
| 3 | + | |
| 3 | 4 | <% trackers.each do |tracker| %> |
| 4 | 5 | div.issue_tracker.nested label.<%= tracker %> { |
| 5 | 6 | background: url(/assets/<%= tracker %>_inactive.png) no-repeat; |
| ... | ... | @@ -14,3 +15,4 @@ div.issue_tracker.nested label.r_on.<%= tracker %> { |
| 14 | 15 | background: transparent url(/assets/<%= tracker %>_goto.png) 6px 5px no-repeat; |
| 15 | 16 | } |
| 16 | 17 | <% end %> |
| 18 | + | ... | ... |
app/assets/stylesheets/notification_service_icons.css.erb
0 → 100644
| ... | ... | @@ -0,0 +1,18 @@ |
| 1 | + /* Notification Service inactive, select, create and goto icons */ | |
| 2 | +<% notification_services = NotificationService.subclasses.map{|t| t.label } << 'none' %> | |
| 3 | + | |
| 4 | +<% notification_services.each do |notification_service| %> | |
| 5 | +div.notification_service.nested label.<%= notification_service %> { | |
| 6 | + background: url(/assets/<%= notification_service %>_inactive.png) no-repeat; | |
| 7 | +} | |
| 8 | +div.notification_service.nested label.r_on.<%= notification_service %> { | |
| 9 | + background: url(/assets/<%= notification_service %>_create.png) no-repeat; | |
| 10 | +} | |
| 11 | +#action-bar a.<%= notification_service %>_create { | |
| 12 | + background: transparent url(/assets/<%= notification_service %>_create.png) 6px 5px no-repeat; | |
| 13 | +} | |
| 14 | +#action-bar a.<%= notification_service %>_goto { | |
| 15 | + background: transparent url(/assets/<%= notification_service %>_goto.png) 6px 5px no-repeat; | |
| 16 | +} | |
| 17 | +<% end %> | |
| 18 | + | ... | ... |
| ... | ... | @@ -0,0 +1,23 @@ |
| 1 | +class Api::V1::NoticesController < ApplicationController | |
| 2 | + respond_to :json, :xml | |
| 3 | + | |
| 4 | + def index | |
| 5 | + query = {} | |
| 6 | + fields = %w{created_at message error_class} | |
| 7 | + | |
| 8 | + if params.key?(:start_date) && params.key?(:end_date) | |
| 9 | + start_date = Time.parse(params[:start_date]).utc | |
| 10 | + end_date = Time.parse(params[:end_date]).utc | |
| 11 | + query = {:created_at => {"$lte" => end_date, "$gte" => start_date}} | |
| 12 | + end | |
| 13 | + | |
| 14 | + results = benchmark("[api/v1/notices_controller] query time") { Mongoid.master["notices"].find(query, :fields => fields).to_a } | |
| 15 | + | |
| 16 | + respond_to do |format| | |
| 17 | + format.html { render :json => Yajl.dump(results) } # render JSON if no extension specified on path | |
| 18 | + format.json { render :json => Yajl.dump(results) } | |
| 19 | + format.xml { render :xml => results } | |
| 20 | + end | |
| 21 | + end | |
| 22 | + | |
| 23 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,23 @@ |
| 1 | +class Api::V1::ProblemsController < ApplicationController | |
| 2 | + respond_to :json, :xml | |
| 3 | + | |
| 4 | + def index | |
| 5 | + query = {} | |
| 6 | + fields = %w{app_id app_name environment message where first_notice_at last_notice_at resolved resolved_at notices_count} | |
| 7 | + | |
| 8 | + if params.key?(:start_date) && params.key?(:end_date) | |
| 9 | + start_date = Time.parse(params[:start_date]).utc | |
| 10 | + end_date = Time.parse(params[:end_date]).utc | |
| 11 | + query = {:first_notice_at=>{"$lte"=>end_date}, "$or"=>[{:resolved_at=>nil}, {:resolved_at=>{"$gte"=>start_date}}]} | |
| 12 | + end | |
| 13 | + | |
| 14 | + results = benchmark("[api/v1/problems_controller] query time") { Mongoid.master["problems"].find(query, :fields => fields).to_a } | |
| 15 | + | |
| 16 | + respond_to do |format| | |
| 17 | + format.html { render :json => Yajl.dump(results) } # render JSON if no extension specified on path | |
| 18 | + format.json { render :json => Yajl.dump(results) } | |
| 19 | + format.xml { render :xml => results } | |
| 20 | + end | |
| 21 | + end | |
| 22 | + | |
| 23 | +end | ... | ... |
app/controllers/apps_controller.rb
| ... | ... | @@ -29,12 +29,14 @@ class AppsController < InheritedResources::Base |
| 29 | 29 | def create |
| 30 | 30 | @app = App.new(params[:app]) |
| 31 | 31 | initialize_subclassed_issue_tracker |
| 32 | + initialize_subclassed_notification_service | |
| 32 | 33 | create! |
| 33 | 34 | end |
| 34 | 35 | |
| 35 | 36 | def update |
| 36 | 37 | @app = resource |
| 37 | 38 | initialize_subclassed_issue_tracker |
| 39 | + initialize_subclassed_notification_service | |
| 38 | 40 | update! |
| 39 | 41 | end |
| 40 | 42 | |
| ... | ... | @@ -70,6 +72,7 @@ class AppsController < InheritedResources::Base |
| 70 | 72 | end |
| 71 | 73 | |
| 72 | 74 | def initialize_subclassed_issue_tracker |
| 75 | + # set the app's issue tracker | |
| 73 | 76 | if params[:app][:issue_tracker_attributes] && tracker_type = params[:app][:issue_tracker_attributes][:type] |
| 74 | 77 | if IssueTracker.subclasses.map(&:name).concat(["IssueTracker"]).include?(tracker_type) |
| 75 | 78 | @app.issue_tracker = tracker_type.constantize.new(params[:app][:issue_tracker_attributes]) |
| ... | ... | @@ -77,6 +80,15 @@ class AppsController < InheritedResources::Base |
| 77 | 80 | end |
| 78 | 81 | end |
| 79 | 82 | |
| 83 | + def initialize_subclassed_notification_service | |
| 84 | + # set the app's notification service | |
| 85 | + if params[:app][:notification_service_attributes] && notification_type = params[:app][:notification_service_attributes][:type] | |
| 86 | + if NotificationService.subclasses.map(&:name).concat(["NotificationService"]).include?(notification_type) | |
| 87 | + @app.notification_service = notification_type.constantize.new(params[:app][:notification_service_attributes]) | |
| 88 | + end | |
| 89 | + end | |
| 90 | + end | |
| 91 | + | |
| 80 | 92 | def begin_of_association_chain |
| 81 | 93 | # Filter the @apps collection to apps watched by the current user, unless user is an admin. |
| 82 | 94 | # If user is an admin, then no filter is applied, and all apps are shown. |
| ... | ... | @@ -90,6 +102,7 @@ class AppsController < InheritedResources::Base |
| 90 | 102 | def plug_params app |
| 91 | 103 | app.watchers.build if app.watchers.none? |
| 92 | 104 | app.issue_tracker = IssueTracker.new unless app.issue_tracker_configured? |
| 105 | + app.notification_service = NotificationService.new unless app.notification_service_configured? | |
| 93 | 106 | app.copy_attributes_from(params[:copy_attributes_from]) if params[:copy_attributes_from] |
| 94 | 107 | end |
| 95 | 108 | ... | ... |
app/controllers/comments_controller.rb
| ... | ... | @@ -11,7 +11,7 @@ class CommentsController < ApplicationController |
| 11 | 11 | else |
| 12 | 12 | flash[:error] = "I'm sorry, your comment was blank! Try again?" |
| 13 | 13 | end |
| 14 | - redirect_to app_err_path(@app, @problem) | |
| 14 | + redirect_to app_problem_path(@app, @problem) | |
| 15 | 15 | end |
| 16 | 16 | |
| 17 | 17 | def destroy |
| ... | ... | @@ -21,7 +21,7 @@ class CommentsController < ApplicationController |
| 21 | 21 | else |
| 22 | 22 | flash[:error] = "Sorry, I couldn't delete your comment for some reason. I hope you don't have any sensitive information in there!" |
| 23 | 23 | end |
| 24 | - redirect_to app_err_path(@app, @problem) | |
| 24 | + redirect_to app_problem_path(@app, @problem) | |
| 25 | 25 | end |
| 26 | 26 | |
| 27 | 27 | protected |
| ... | ... | @@ -34,7 +34,7 @@ class CommentsController < ApplicationController |
| 34 | 34 | end |
| 35 | 35 | |
| 36 | 36 | def find_problem |
| 37 | - @problem = @app.problems.find(params[:err_id]) | |
| 37 | + @problem = @app.problems.find(params[:problem_id]) | |
| 38 | 38 | end |
| 39 | 39 | end |
| 40 | 40 | ... | ... |
app/controllers/errs_controller.rb
| ... | ... | @@ -1,157 +0,0 @@ |
| 1 | -class ErrsController < ApplicationController | |
| 2 | - include ActionView::Helpers::TextHelper | |
| 3 | - | |
| 4 | - before_filter :find_app, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several] | |
| 5 | - before_filter :find_problem, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several] | |
| 6 | - before_filter :find_selected_problems, :only => [:destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several] | |
| 7 | - before_filter :set_sorting_params, :only => [:index, :all] | |
| 8 | - before_filter :set_tracker_params, :only => [:create_issue] | |
| 9 | - | |
| 10 | - def index | |
| 11 | - app_scope = current_user.admin? ? App.all : current_user.apps | |
| 12 | - | |
| 13 | - @problems = Problem.for_apps(app_scope).in_env(params[:environment]).unresolved.ordered_by(@sort, @order) | |
| 14 | - @selected_problems = params[:problems] || [] | |
| 15 | - respond_to do |format| | |
| 16 | - format.html do | |
| 17 | - @problems = @problems.page(params[:page]).per(current_user.per_page) | |
| 18 | - end | |
| 19 | - format.atom | |
| 20 | - end | |
| 21 | - end | |
| 22 | - | |
| 23 | - def all | |
| 24 | - app_scope = current_user.admin? ? App.all : current_user.apps | |
| 25 | - @problems = Problem.for_apps(app_scope).ordered_by(@sort, @order).page(params[:page]).per(current_user.per_page) | |
| 26 | - @selected_problems = params[:problems] || [] | |
| 27 | - end | |
| 28 | - | |
| 29 | - def show | |
| 30 | - @notices = @problem.notices.reverse_ordered.page(params[:notice]).per(1) | |
| 31 | - @notice = @notices.first | |
| 32 | - @comment = Comment.new | |
| 33 | - if request.headers['X-PJAX'] | |
| 34 | - params["_pjax"] = nil | |
| 35 | - render :layout => false | |
| 36 | - end | |
| 37 | - end | |
| 38 | - | |
| 39 | - 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 | |
| 57 | - | |
| 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 | |
| 70 | - end | |
| 71 | - | |
| 72 | - redirect_to app_err_path(@app, @problem) | |
| 73 | - end | |
| 74 | - | |
| 75 | - def unlink_issue | |
| 76 | - @problem.update_attribute :issue_link, nil | |
| 77 | - redirect_to app_err_path(@app, @problem) | |
| 78 | - end | |
| 79 | - | |
| 80 | - def resolve | |
| 81 | - @problem.resolve! | |
| 82 | - flash[:success] = 'Great news everyone! The err has been resolved.' | |
| 83 | - redirect_to :back | |
| 84 | - rescue ActionController::RedirectBackError | |
| 85 | - redirect_to app_path(@app) | |
| 86 | - end | |
| 87 | - | |
| 88 | - def resolve_several | |
| 89 | - @selected_problems.each(&:resolve!) | |
| 90 | - flash[:success] = "Great news everyone! #{pluralize(@selected_problems.count, 'err has', 'errs have')} been resolved." | |
| 91 | - redirect_to :back | |
| 92 | - end | |
| 93 | - | |
| 94 | - def unresolve_several | |
| 95 | - @selected_problems.each(&:unresolve!) | |
| 96 | - flash[:success] = "#{pluralize(@selected_problems.count, 'err has', 'errs have')} been unresolved." | |
| 97 | - redirect_to :back | |
| 98 | - end | |
| 99 | - | |
| 100 | - def merge_several | |
| 101 | - if @selected_problems.length < 2 | |
| 102 | - flash[:notice] = "You must select at least two errors to merge" | |
| 103 | - else | |
| 104 | - @merged_problem = Problem.merge!(@selected_problems) | |
| 105 | - flash[:notice] = "#{@selected_problems.count} errors have been merged." | |
| 106 | - end | |
| 107 | - redirect_to :back | |
| 108 | - end | |
| 109 | - | |
| 110 | - def unmerge_several | |
| 111 | - all = @selected_problems.map(&:unmerge!).flatten | |
| 112 | - flash[:success] = "#{pluralize(all.length, 'err has', 'errs have')} been unmerged." | |
| 113 | - redirect_to :back | |
| 114 | - end | |
| 115 | - | |
| 116 | - def destroy_several | |
| 117 | - nb_problem_destroy = ProblemDestroy.execute(@selected_problems) | |
| 118 | - flash[:notice] = "#{pluralize(nb_problem_destroy, 'err has', 'errs have')} been deleted." | |
| 119 | - redirect_to :back | |
| 120 | - end | |
| 121 | - | |
| 122 | - protected | |
| 123 | - def find_app | |
| 124 | - @app = App.find(params[:app_id]) | |
| 125 | - | |
| 126 | - # Mongoid Bug: could not chain: current_user.apps.find_by_id! | |
| 127 | - # apparently finding by 'watchers.email' and 'id' is broken | |
| 128 | - raise(Mongoid::Errors::DocumentNotFound.new(App,@app.id)) unless current_user.admin? || current_user.watching?(@app) | |
| 129 | - end | |
| 130 | - | |
| 131 | - def find_problem | |
| 132 | - @problem = @app.problems.find(params[:id]) | |
| 133 | - end | |
| 134 | - | |
| 135 | - def set_tracker_params | |
| 136 | - IssueTracker.default_url_options[:host] = request.host | |
| 137 | - IssueTracker.default_url_options[:port] = request.port | |
| 138 | - IssueTracker.default_url_options[:protocol] = request.scheme | |
| 139 | - end | |
| 140 | - | |
| 141 | - def find_selected_problems | |
| 142 | - err_ids = (params[:problems] || []).compact | |
| 143 | - if err_ids.empty? | |
| 144 | - flash[:notice] = "You have not selected any errors" | |
| 145 | - redirect_to :back | |
| 146 | - else | |
| 147 | - @selected_problems = Array(Problem.find(err_ids)) | |
| 148 | - end | |
| 149 | - end | |
| 150 | - | |
| 151 | - def set_sorting_params | |
| 152 | - @sort = params[:sort] | |
| 153 | - @sort = "last_notice_at" unless %w{app message last_notice_at last_deploy_at count}.member?(@sort) | |
| 154 | - @order = params[:order] || "desc" | |
| 155 | - end | |
| 156 | -end | |
| 157 | - |
app/controllers/notices_controller.rb
| ... | ... | @@ -5,9 +5,16 @@ class NoticesController < ApplicationController |
| 5 | 5 | |
| 6 | 6 | def create |
| 7 | 7 | # params[:data] if the notice came from a GET request, raw_post if it came via POST |
| 8 | - @notice = App.report_error!(params[:data] || request.raw_post) | |
| 9 | - respond_with @notice | |
| 8 | + notice = App.report_error!(params[:data] || request.raw_post) | |
| 9 | + api_xml = notice.to_xml(:only => false, :methods => [:id]) do |xml| | |
| 10 | + xml.url locate_url(notice.id, :host => Errbit::Config.host) | |
| 11 | + end | |
| 12 | + render :xml => api_xml | |
| 10 | 13 | end |
| 11 | 14 | |
| 15 | + # Redirects a notice to the problem page. Useful when using User Information at Airbrake gem. | |
| 16 | + def locate | |
| 17 | + problem = Notice.find(params[:id]).problem | |
| 18 | + redirect_to app_problem_path(problem.app, problem) | |
| 19 | + end | |
| 12 | 20 | end |
| 13 | - | ... | ... |
| ... | ... | @@ -0,0 +1,157 @@ |
| 1 | +class ProblemsController < ApplicationController | |
| 2 | + include ActionView::Helpers::TextHelper | |
| 3 | + | |
| 4 | + before_filter :find_app, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several] | |
| 5 | + before_filter :find_problem, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several] | |
| 6 | + before_filter :find_selected_problems, :only => [:destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several] | |
| 7 | + before_filter :set_sorting_params, :only => [:index, :all] | |
| 8 | + before_filter :set_tracker_params, :only => [:create_issue] | |
| 9 | + | |
| 10 | + def index | |
| 11 | + app_scope = current_user.admin? ? App.all : current_user.apps | |
| 12 | + | |
| 13 | + @problems = Problem.for_apps(app_scope).in_env(params[:environment]).unresolved.ordered_by(@sort, @order) | |
| 14 | + @selected_problems = params[:problems] || [] | |
| 15 | + respond_to do |format| | |
| 16 | + format.html do | |
| 17 | + @problems = @problems.page(params[:page]).per(current_user.per_page) | |
| 18 | + end | |
| 19 | + format.atom | |
| 20 | + end | |
| 21 | + end | |
| 22 | + | |
| 23 | + def all | |
| 24 | + app_scope = current_user.admin? ? App.all : current_user.apps | |
| 25 | + @problems = Problem.for_apps(app_scope).ordered_by(@sort, @order).page(params[:page]).per(current_user.per_page) | |
| 26 | + @selected_problems = params[:problems] || [] | |
| 27 | + end | |
| 28 | + | |
| 29 | + def show | |
| 30 | + @notices = @problem.notices.reverse_ordered.page(params[:notice]).per(1) | |
| 31 | + @notice = @notices.first | |
| 32 | + @comment = Comment.new | |
| 33 | + if request.headers['X-PJAX'] | |
| 34 | + params["_pjax"] = nil | |
| 35 | + render :layout => false | |
| 36 | + end | |
| 37 | + end | |
| 38 | + | |
| 39 | + 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 | |
| 57 | + | |
| 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 | |
| 70 | + end | |
| 71 | + | |
| 72 | + redirect_to app_problem_path(@app, @problem) | |
| 73 | + end | |
| 74 | + | |
| 75 | + def unlink_issue | |
| 76 | + @problem.update_attribute :issue_link, nil | |
| 77 | + redirect_to app_problem_path(@app, @problem) | |
| 78 | + end | |
| 79 | + | |
| 80 | + def resolve | |
| 81 | + @problem.resolve! | |
| 82 | + flash[:success] = 'Great news everyone! The err has been resolved.' | |
| 83 | + redirect_to :back | |
| 84 | + rescue ActionController::RedirectBackError | |
| 85 | + redirect_to app_path(@app) | |
| 86 | + end | |
| 87 | + | |
| 88 | + def resolve_several | |
| 89 | + @selected_problems.each(&:resolve!) | |
| 90 | + flash[:success] = "Great news everyone! #{pluralize(@selected_problems.count, 'err has', 'errs have')} been resolved." | |
| 91 | + redirect_to :back | |
| 92 | + end | |
| 93 | + | |
| 94 | + def unresolve_several | |
| 95 | + @selected_problems.each(&:unresolve!) | |
| 96 | + flash[:success] = "#{pluralize(@selected_problems.count, 'err has', 'errs have')} been unresolved." | |
| 97 | + redirect_to :back | |
| 98 | + end | |
| 99 | + | |
| 100 | + def merge_several | |
| 101 | + if @selected_problems.length < 2 | |
| 102 | + flash[:notice] = "You must select at least two errors to merge" | |
| 103 | + else | |
| 104 | + @merged_problem = Problem.merge!(@selected_problems) | |
| 105 | + flash[:notice] = "#{@selected_problems.count} errors have been merged." | |
| 106 | + end | |
| 107 | + redirect_to :back | |
| 108 | + end | |
| 109 | + | |
| 110 | + def unmerge_several | |
| 111 | + all = @selected_problems.map(&:unmerge!).flatten | |
| 112 | + flash[:success] = "#{pluralize(all.length, 'err has', 'errs have')} been unmerged." | |
| 113 | + redirect_to :back | |
| 114 | + end | |
| 115 | + | |
| 116 | + def destroy_several | |
| 117 | + nb_problem_destroy = ProblemDestroy.execute(@selected_problems) | |
| 118 | + flash[:notice] = "#{pluralize(nb_problem_destroy, 'err has', 'errs have')} been deleted." | |
| 119 | + redirect_to :back | |
| 120 | + end | |
| 121 | + | |
| 122 | + protected | |
| 123 | + def find_app | |
| 124 | + @app = App.find(params[:app_id]) | |
| 125 | + | |
| 126 | + # Mongoid Bug: could not chain: current_user.apps.find_by_id! | |
| 127 | + # apparently finding by 'watchers.email' and 'id' is broken | |
| 128 | + raise(Mongoid::Errors::DocumentNotFound.new(App,@app.id)) unless current_user.admin? || current_user.watching?(@app) | |
| 129 | + end | |
| 130 | + | |
| 131 | + def find_problem | |
| 132 | + @problem = @app.problems.find(params[:id]) | |
| 133 | + end | |
| 134 | + | |
| 135 | + def set_tracker_params | |
| 136 | + IssueTracker.default_url_options[:host] = request.host | |
| 137 | + IssueTracker.default_url_options[:port] = request.port | |
| 138 | + IssueTracker.default_url_options[:protocol] = request.scheme | |
| 139 | + end | |
| 140 | + | |
| 141 | + def find_selected_problems | |
| 142 | + err_ids = (params[:problems] || []).compact | |
| 143 | + if err_ids.empty? | |
| 144 | + flash[:notice] = "You have not selected any errors" | |
| 145 | + redirect_to :back | |
| 146 | + else | |
| 147 | + @selected_problems = Array(Problem.find(err_ids)) | |
| 148 | + end | |
| 149 | + end | |
| 150 | + | |
| 151 | + def set_sorting_params | |
| 152 | + @sort = params[:sort] | |
| 153 | + @sort = "last_notice_at" unless %w{app message last_notice_at last_deploy_at count}.member?(@sort) | |
| 154 | + @order = params[:order] || "desc" | |
| 155 | + end | |
| 156 | +end | |
| 157 | + | ... | ... |
app/helpers/application_helper.rb
| ... | ... | @@ -13,7 +13,7 @@ module ApplicationHelper |
| 13 | 13 | event.dtend = notice.created_at.utc + 60.minutes |
| 14 | 14 | event.organizer = notice.server_environment && notice.server_environment["hostname"] |
| 15 | 15 | event.location = notice.server_environment && notice.server_environment["project-root"] |
| 16 | - event.url = app_err_url(:app_id => notice.problem.app.id, :id => notice.problem) | |
| 16 | + event.url = app_problem_url(:app_id => notice.problem.app.id, :id => notice.problem) | |
| 17 | 17 | end |
| 18 | 18 | end |
| 19 | 19 | end.to_s |
| ... | ... | @@ -58,12 +58,25 @@ module ApplicationHelper |
| 58 | 58 | percent = 100.0 / total.to_f |
| 59 | 59 | rows = tallies.map {|value, count| [(count.to_f * percent), value]} \ |
| 60 | 60 | .sort {|a, b| a[0] <=> b[0]} |
| 61 | - render "errs/tally_table", :rows => rows | |
| 61 | + render "problems/tally_table", :rows => rows | |
| 62 | + end | |
| 63 | + | |
| 64 | + def head(collection) | |
| 65 | + collection.first(head_size) | |
| 66 | + end | |
| 67 | + | |
| 68 | + def tail(collection) | |
| 69 | + collection.to_a[head_size..-1].to_a | |
| 62 | 70 | end |
| 63 | 71 | |
| 64 | 72 | private |
| 65 | 73 | def total_from_tallies(tallies) |
| 66 | 74 | tallies.values.inject(0) {|sum, n| sum + n} |
| 67 | 75 | end |
| 76 | + | |
| 77 | + def head_size | |
| 78 | + 4 | |
| 79 | + end | |
| 80 | + | |
| 68 | 81 | end |
| 69 | 82 | ... | ... |
app/helpers/apps_helper.rb
| ... | ... | @@ -16,6 +16,16 @@ module AppsHelper |
| 16 | 16 | @any_github_repos |
| 17 | 17 | end |
| 18 | 18 | |
| 19 | + def any_notification_services? | |
| 20 | + detect_any_apps_with_attributes unless @any_notification_services | |
| 21 | + @any_notification_services | |
| 22 | + end | |
| 23 | + | |
| 24 | + def any_bitbucket_repos? | |
| 25 | + detect_any_apps_with_attributes unless @any_bitbucket_repos | |
| 26 | + @any_bitbucket_repos | |
| 27 | + end | |
| 28 | + | |
| 19 | 29 | def any_issue_trackers? |
| 20 | 30 | detect_any_apps_with_attributes unless @any_issue_trackers |
| 21 | 31 | @any_issue_trackers |
| ... | ... | @@ -29,11 +39,14 @@ module AppsHelper |
| 29 | 39 | private |
| 30 | 40 | |
| 31 | 41 | def detect_any_apps_with_attributes |
| 32 | - @any_github_repos = @any_issue_trackers = @any_deploys = false | |
| 42 | + @any_github_repos = @any_issue_trackers = @any_deploys = @any_bitbucket_repos = @any_notification_services = false | |
| 43 | + | |
| 33 | 44 | @apps.each do |app| |
| 34 | 45 | @any_github_repos ||= app.github_repo? |
| 46 | + @any_bitbucket_repos ||= app.bitbucket_repo? | |
| 35 | 47 | @any_issue_trackers ||= app.issue_tracker_configured? |
| 36 | 48 | @any_deploys ||= !!app.last_deploy_at |
| 49 | + @any_notification_services ||= app.notification_service_configured? | |
| 37 | 50 | end |
| 38 | 51 | end |
| 39 | 52 | end | ... | ... |
| ... | ... | @@ -0,0 +1,16 @@ |
| 1 | +module BacktraceHelper | |
| 2 | + # Group lines into sections of in-app files and external files | |
| 3 | + # (An implementation of Enumerable#chunk so we don't break 1.8.7 support.) | |
| 4 | + def grouped_lines(lines) | |
| 5 | + line_groups = [] | |
| 6 | + lines.each do |line| | |
| 7 | + in_app = line.in_app? | |
| 8 | + if line_groups.last && line_groups.last[0] == in_app | |
| 9 | + line_groups.last[1] << line | |
| 10 | + else | |
| 11 | + line_groups << [in_app, [line]] | |
| 12 | + end | |
| 13 | + end | |
| 14 | + line_groups | |
| 15 | + end | |
| 16 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,38 @@ |
| 1 | +module BacktraceLineHelper | |
| 2 | + def link_to_source_file(line, &block) | |
| 3 | + text = capture_haml(&block) | |
| 4 | + line.in_app? ? link_to_in_app_source_file(line, text) : link_to_external_source_file(text) | |
| 5 | + end | |
| 6 | + | |
| 7 | + private | |
| 8 | + def link_to_in_app_source_file(line, text) | |
| 9 | + link_to_repo_source_file(line, text) || link_to_issue_tracker_file(line, text) | |
| 10 | + end | |
| 11 | + | |
| 12 | + def link_to_repo_source_file(line, text) | |
| 13 | + link_to_github(line, text) || link_to_bitbucket(line, text) | |
| 14 | + end | |
| 15 | + | |
| 16 | + def link_to_external_source_file(text) | |
| 17 | + text | |
| 18 | + end | |
| 19 | + | |
| 20 | + def link_to_github(line, text = nil) | |
| 21 | + return unless line.app.github_repo? | |
| 22 | + href = "%s#L%s" % [line.app.github_url_to_file(line.file), line.number] | |
| 23 | + link_to(text || line.file_name, href, :target => '_blank') | |
| 24 | + end | |
| 25 | + | |
| 26 | + def link_to_bitbucket(line, text = nil) | |
| 27 | + return unless line.app.bitbucket_repo? | |
| 28 | + href = "%s#cl-%s" % [line.app.bitbucket_url_to_file(line.file), line.number] | |
| 29 | + link_to(text || line.file_name, href, :target => '_blank') | |
| 30 | + end | |
| 31 | + | |
| 32 | + def link_to_issue_tracker_file(line, text = nil) | |
| 33 | + return unless line.app.issue_tracker && line.app.issue_tracker.respond_to?(:url_to_file) | |
| 34 | + href = line.app.issue_tracker.url_to_file(line.file, line.number) | |
| 35 | + link_to(text || line.file_name, href, :target => '_blank') | |
| 36 | + end | |
| 37 | + | |
| 38 | +end | ... | ... |
app/helpers/errs_helper.rb
| ... | ... | @@ -1,17 +0,0 @@ |
| 1 | -module ErrsHelper | |
| 2 | - def last_notice_at(problem) | |
| 3 | - problem.last_notice_at || problem.created_at | |
| 4 | - end | |
| 5 | - | |
| 6 | - def err_confirm | |
| 7 | - Errbit::Config.confirm_resolve_err === false ? nil : 'Seriously?' | |
| 8 | - end | |
| 9 | - | |
| 10 | - def truncated_err_message(problem) | |
| 11 | - unless (msg = problem.message).blank? | |
| 12 | - # Truncate & insert invisible chars so that firefox can emulate 'word-wrap: break-word' CSS rule | |
| 13 | - truncate(msg, :length => 300).scan(/.{1,5}/).map { |s| h(s) }.join("​").html_safe | |
| 14 | - end | |
| 15 | - end | |
| 16 | -end | |
| 17 | - |
app/helpers/notices_helper.rb
| 1 | 1 | # encoding: utf-8 |
| 2 | 2 | module NoticesHelper |
| 3 | - def in_app_backtrace_line?(line) | |
| 4 | - !!(line['file'] =~ %r{^\[PROJECT_ROOT\]/(?!(vendor))}) | |
| 5 | - end | |
| 6 | - | |
| 7 | 3 | def notice_atom_summary(notice) |
| 8 | 4 | render "notices/atom_entry.html.haml", :notice => notice |
| 9 | 5 | end |
| 10 | - | |
| 11 | - def link_to_source_file(app, line, &block) | |
| 12 | - text = capture_haml(&block) | |
| 13 | - if in_app_backtrace_line?(line) | |
| 14 | - return link_to_github(app, line, text) if app.github_repo? | |
| 15 | - if app.issue_tracker && app.issue_tracker.respond_to?(:url_to_file) | |
| 16 | - # Return link to file on tracker if issue tracker supports this | |
| 17 | - return link_to_issue_tracker_file(app, line, text) | |
| 18 | - end | |
| 19 | - end | |
| 20 | - text | |
| 21 | - end | |
| 22 | - | |
| 23 | - def filepath_parts(file) | |
| 24 | - [file.split('/').last, file.gsub('[PROJECT_ROOT]', '')] | |
| 25 | - end | |
| 26 | - | |
| 27 | - def link_to_github(app, line, text = nil) | |
| 28 | - file_name, file_path = filepath_parts(line['file']) | |
| 29 | - href = "%s#L%s" % [app.github_url_to_file(file_path), line['number']] | |
| 30 | - link_to(text || file_name, href, :target => '_blank') | |
| 31 | - end | |
| 32 | - | |
| 33 | - def link_to_issue_tracker_file(app, line, text = nil) | |
| 34 | - file_name, file_path = filepath_parts(line['file']) | |
| 35 | - href = app.issue_tracker.url_to_file(file_path, line['number']) | |
| 36 | - link_to(text || file_name, href, :target => '_blank') | |
| 37 | - end | |
| 38 | - | |
| 39 | - # Group lines into sections of in-app files and external files | |
| 40 | - # (An implementation of Enumerable#chunk so we don't break 1.8.7 support.) | |
| 41 | - def grouped_lines(lines) | |
| 42 | - line_groups = [] | |
| 43 | - lines.each do |line| | |
| 44 | - in_app = in_app_backtrace_line?(line) | |
| 45 | - if line_groups.last && line_groups.last[0] == in_app | |
| 46 | - line_groups.last[1] << line | |
| 47 | - else | |
| 48 | - line_groups << [in_app, [line]] | |
| 49 | - end | |
| 50 | - end | |
| 51 | - line_groups | |
| 52 | - end | |
| 53 | - | |
| 54 | - def path_for_backtrace_line(line) | |
| 55 | - path = File.dirname(line['file']) | |
| 56 | - return '' if path == '.' | |
| 57 | - # Remove [PROJECT_ROOT] | |
| 58 | - path.gsub!('[PROJECT_ROOT]/', '') | |
| 59 | - # Make gem name bold if starts with [GEM_ROOT]/gems | |
| 60 | - path.gsub!(/\[GEM_ROOT\]\/gems\/([^\/]+)/, "<strong>\\1</strong>") | |
| 61 | - (path << '/').html_safe | |
| 62 | - end | |
| 63 | - | |
| 64 | - def file_for_backtrace_line(line) | |
| 65 | - file = File.basename(line['file']) | |
| 66 | - end | |
| 67 | 6 | end |
| 68 | 7 | ... | ... |
| ... | ... | @@ -0,0 +1,27 @@ |
| 1 | +module ProblemsHelper | |
| 2 | + def problem_confirm | |
| 3 | + Errbit::Config.confirm_resolve_err === false ? nil : 'Seriously?' | |
| 4 | + end | |
| 5 | + | |
| 6 | + def truncated_problem_message(problem) | |
| 7 | + unless (msg = problem.message).blank? | |
| 8 | + # Truncate & insert invisible chars so that firefox can emulate 'word-wrap: break-word' CSS rule | |
| 9 | + truncate(msg, :length => 300).scan(/.{1,5}/).map { |s| h(s) }.join("​").html_safe | |
| 10 | + end | |
| 11 | + end | |
| 12 | + | |
| 13 | + def gravatar_tag(email, options = {}) | |
| 14 | + image_tag gravatar_url(email, options), :alt => email, :class => 'gravatar' | |
| 15 | + end | |
| 16 | + | |
| 17 | + def gravatar_url(email, options = {}) | |
| 18 | + default_options = { | |
| 19 | + :d => Errbit::Config.gravatar_default, | |
| 20 | + } | |
| 21 | + options.reverse_merge! default_options | |
| 22 | + params = options.extract!(:s, :d).delete_if { |k, v| v.blank? } | |
| 23 | + email_hash = Digest::MD5.hexdigest(email) | |
| 24 | + "http://www.gravatar.com/avatar/#{email_hash}?#{params.to_query}" | |
| 25 | + end | |
| 26 | +end | |
| 27 | + | ... | ... |
app/models/app.rb
| ... | ... | @@ -5,6 +5,8 @@ class App |
| 5 | 5 | field :name, :type => String |
| 6 | 6 | field :api_key |
| 7 | 7 | field :github_repo |
| 8 | + field :bitbucket_repo | |
| 9 | + field :repository_branch | |
| 8 | 10 | field :resolve_errs_on_deploy, :type => Boolean, :default => false |
| 9 | 11 | field :notify_all_users, :type => Boolean, :default => false |
| 10 | 12 | field :notify_on_errs, :type => Boolean, :default => true |
| ... | ... | @@ -17,6 +19,8 @@ class App |
| 17 | 19 | embeds_many :watchers |
| 18 | 20 | embeds_many :deploys |
| 19 | 21 | embeds_one :issue_tracker |
| 22 | + embeds_one :notification_service | |
| 23 | + | |
| 20 | 24 | has_many :problems, :inverse_of => :app, :dependent => :destroy |
| 21 | 25 | |
| 22 | 26 | before_validation :generate_api_key, :on => :create |
| ... | ... | @@ -33,7 +37,8 @@ class App |
| 33 | 37 | :reject_if => proc { |attrs| attrs[:user_id].blank? && attrs[:email].blank? } |
| 34 | 38 | accepts_nested_attributes_for :issue_tracker, :allow_destroy => true, |
| 35 | 39 | :reject_if => proc { |attrs| !IssueTracker.subclasses.map(&:to_s).include?(attrs[:type].to_s) } |
| 36 | - | |
| 40 | + accepts_nested_attributes_for :notification_service, :allow_destroy => true, | |
| 41 | + :reject_if => proc { |attrs| !NotificationService.subclasses.map(&:to_s).include?(attrs[:type].to_s) } | |
| 37 | 42 | |
| 38 | 43 | # Processes a new error report. |
| 39 | 44 | # |
| ... | ... | @@ -74,8 +79,7 @@ class App |
| 74 | 79 | end |
| 75 | 80 | |
| 76 | 81 | def find_or_create_err!(attrs) |
| 77 | - Err.any_in(:problem_id => problems.map { |a| a.id }). | |
| 78 | - where(attrs).first || problems.create!.errs.create!(attrs) | |
| 82 | + Err.where(:fingerprint => attrs[:fingerprint]).first || problems.create!.errs.create!(attrs) | |
| 79 | 83 | end |
| 80 | 84 | |
| 81 | 85 | # Mongoid Bug: find(id) on association proxies returns an Enumerator |
| ... | ... | @@ -103,6 +107,9 @@ class App |
| 103 | 107 | end |
| 104 | 108 | alias :notify_on_deploys? :notify_on_deploys |
| 105 | 109 | |
| 110 | + def repo_branch | |
| 111 | + self.repository_branch.present? ? self.repository_branch : 'master' | |
| 112 | + end | |
| 106 | 113 | |
| 107 | 114 | def github_repo? |
| 108 | 115 | self.github_repo.present? |
| ... | ... | @@ -113,7 +120,19 @@ class App |
| 113 | 120 | end |
| 114 | 121 | |
| 115 | 122 | def github_url_to_file(file) |
| 116 | - "#{github_url}/blob/master#{file}" | |
| 123 | + "#{github_url}/blob/#{repo_branch + file}" | |
| 124 | + end | |
| 125 | + | |
| 126 | + def bitbucket_repo? | |
| 127 | + self.bitbucket_repo.present? | |
| 128 | + end | |
| 129 | + | |
| 130 | + def bitbucket_url | |
| 131 | + "https://bitbucket.org/#{bitbucket_repo}" if bitbucket_repo? | |
| 132 | + end | |
| 133 | + | |
| 134 | + def bitbucket_url_to_file(file) | |
| 135 | + "#{bitbucket_url}/src/#{repo_branch + file}" | |
| 117 | 136 | end |
| 118 | 137 | |
| 119 | 138 | |
| ... | ... | @@ -121,6 +140,11 @@ class App |
| 121 | 140 | !!(issue_tracker && issue_tracker.class < IssueTracker && issue_tracker.project_id.present?) |
| 122 | 141 | end |
| 123 | 142 | |
| 143 | + def notification_service_configured? | |
| 144 | + !!(notification_service && notification_service.class < NotificationService && notification_service.api_token.present?) | |
| 145 | + end | |
| 146 | + | |
| 147 | + | |
| 124 | 148 | def notification_recipients |
| 125 | 149 | if notify_all_users |
| 126 | 150 | (User.all.map(&:email).reject(&:blank?) + watchers.map(&:address)).uniq |
| ... | ... | @@ -137,7 +161,7 @@ class App |
| 137 | 161 | self.send("#{k}=", copy_app.send(k)) |
| 138 | 162 | end |
| 139 | 163 | # Clone the embedded objects that can be changed via apps/edit (ignore errs & deploys, etc.) |
| 140 | - %w(watchers issue_tracker).each do |relation| | |
| 164 | + %w(watchers issue_tracker notification_service).each do |relation| | |
| 141 | 165 | if obj = copy_app.send(relation) |
| 142 | 166 | self.send("#{relation}=", obj.is_a?(Array) ? obj.map(&:clone) : obj.clone) |
| 143 | 167 | end | ... | ... |
| ... | ... | @@ -0,0 +1,36 @@ |
| 1 | +class Backtrace | |
| 2 | + include Mongoid::Document | |
| 3 | + include Mongoid::Timestamps | |
| 4 | + | |
| 5 | + field :fingerprint | |
| 6 | + index :fingerprint | |
| 7 | + | |
| 8 | + has_many :notices | |
| 9 | + has_one :notice | |
| 10 | + | |
| 11 | + embeds_many :lines, :class_name => "BacktraceLine" | |
| 12 | + | |
| 13 | + after_initialize :generate_fingerprint | |
| 14 | + | |
| 15 | + delegate :app, :to => :notice | |
| 16 | + | |
| 17 | + def self.find_or_create(attributes = {}) | |
| 18 | + new(attributes).similar || create(attributes) | |
| 19 | + end | |
| 20 | + | |
| 21 | + def similar | |
| 22 | + Backtrace.first(:conditions => { :fingerprint => fingerprint } ) | |
| 23 | + end | |
| 24 | + | |
| 25 | + def raw=(raw) | |
| 26 | + raw.each do |raw_line| | |
| 27 | + lines << BacktraceLine.new(BacktraceLineNormalizer.new(raw_line).call) | |
| 28 | + end | |
| 29 | + end | |
| 30 | + | |
| 31 | + private | |
| 32 | + def generate_fingerprint | |
| 33 | + self.fingerprint = Digest::SHA1.hexdigest(lines.map(&:to_s).join) | |
| 34 | + end | |
| 35 | + | |
| 36 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,42 @@ |
| 1 | +class BacktraceLine | |
| 2 | + include Mongoid::Document | |
| 3 | + IN_APP_PATH = %r{^\[PROJECT_ROOT\]\/(?!(vendor))} | |
| 4 | + GEMS_PATH = %r{\[GEM_ROOT\]\/gems\/([^\/]+)} | |
| 5 | + | |
| 6 | + field :number, :type => Integer | |
| 7 | + field :file | |
| 8 | + field :method | |
| 9 | + | |
| 10 | + embedded_in :backtrace | |
| 11 | + | |
| 12 | + scope :in_app, where(:file => IN_APP_PATH) | |
| 13 | + | |
| 14 | + delegate :app, :to => :backtrace | |
| 15 | + | |
| 16 | + def to_s | |
| 17 | + "#{file}:#{number}" | |
| 18 | + end | |
| 19 | + | |
| 20 | + def in_app? | |
| 21 | + !!(file =~ IN_APP_PATH) | |
| 22 | + end | |
| 23 | + | |
| 24 | + def path | |
| 25 | + File.dirname(file).gsub(/^\.$/, '') + "/" | |
| 26 | + end | |
| 27 | + | |
| 28 | + def file_relative | |
| 29 | + file.to_s.sub(IN_APP_PATH, '') | |
| 30 | + end | |
| 31 | + | |
| 32 | + def file_name | |
| 33 | + File.basename file | |
| 34 | + end | |
| 35 | + | |
| 36 | + def decorated_path | |
| 37 | + path.sub(BacktraceLine::IN_APP_PATH, ''). | |
| 38 | + sub(BacktraceLine::GEMS_PATH, "<strong>\\1</strong>") | |
| 39 | + end | |
| 40 | + | |
| 41 | +end | |
| 42 | + | ... | ... |
| ... | ... | @@ -0,0 +1,19 @@ |
| 1 | +class BacktraceLineNormalizer | |
| 2 | + def initialize(raw_line) | |
| 3 | + @raw_line = raw_line | |
| 4 | + end | |
| 5 | + | |
| 6 | + def call | |
| 7 | + @raw_line.merge 'file' => normalized_file, 'method' => normalized_method | |
| 8 | + end | |
| 9 | + | |
| 10 | + private | |
| 11 | + def normalized_file | |
| 12 | + @raw_line['file'].blank? ? "[unknown source]" : @raw_line['file'].to_s.gsub(/\[PROJECT_ROOT\]\/.*\/ruby\/[0-9.]+\/gems/, '[GEM_ROOT]/gems') | |
| 13 | + end | |
| 14 | + | |
| 15 | + def normalized_method | |
| 16 | + @raw_line['method'].gsub(/[0-9_]{10,}+/, "__FRAGMENT__") | |
| 17 | + end | |
| 18 | + | |
| 19 | +end | ... | ... |
app/models/err.rb
| ... | ... | @@ -6,20 +6,19 @@ class Err |
| 6 | 6 | include Mongoid::Document |
| 7 | 7 | include Mongoid::Timestamps |
| 8 | 8 | |
| 9 | - field :error_class | |
| 9 | + field :error_class, :default => "UnknownError" | |
| 10 | 10 | field :component |
| 11 | 11 | field :action |
| 12 | - field :environment | |
| 12 | + field :environment, :default => "unknown" | |
| 13 | 13 | field :fingerprint |
| 14 | 14 | |
| 15 | 15 | belongs_to :problem |
| 16 | 16 | index :problem_id |
| 17 | 17 | index :error_class |
| 18 | + index :fingerprint | |
| 18 | 19 | |
| 19 | 20 | has_many :notices, :inverse_of => :err, :dependent => :destroy |
| 20 | 21 | |
| 21 | - validates_presence_of :error_class, :environment | |
| 22 | - | |
| 23 | 22 | delegate :app, :resolved?, :to => :problem |
| 24 | 23 | |
| 25 | 24 | end | ... | ... |
app/models/error_report.rb
| 1 | -require 'digest/md5' | |
| 1 | +require 'digest/sha1' | |
| 2 | 2 | require 'hoptoad_notifier' |
| 3 | 3 | |
| 4 | 4 | class ErrorReport |
| 5 | - attr_reader :error_class, :message, :backtrace, :request, :server_environment, :api_key, :notifier, :user_attributes | |
| 5 | + attr_reader :error_class, :message, :request, :server_environment, :api_key, :notifier, :user_attributes, :current_user | |
| 6 | 6 | |
| 7 | 7 | def initialize(xml_or_attributes) |
| 8 | 8 | @attributes = (xml_or_attributes.is_a?(String) ? Hoptoad.parse_xml!(xml_or_attributes) : xml_or_attributes).with_indifferent_access |
| ... | ... | @@ -10,11 +10,7 @@ class ErrorReport |
| 10 | 10 | end |
| 11 | 11 | |
| 12 | 12 | def fingerprint |
| 13 | - normalized_backtrace = backtrace[0...3].map do |trace| | |
| 14 | - trace.merge 'method' => trace['method'].gsub(/[0-9_]{10,}+/, "__FRAGMENT__") | |
| 15 | - end | |
| 16 | - | |
| 17 | - @fingerprint ||= Digest::MD5.hexdigest(normalized_backtrace.to_s) | |
| 13 | + @fingerprint ||= Digest::SHA1.hexdigest(fingerprint_source.to_s) | |
| 18 | 14 | end |
| 19 | 15 | |
| 20 | 16 | def rails_env |
| ... | ... | @@ -33,15 +29,21 @@ class ErrorReport |
| 33 | 29 | @app ||= App.find_by_api_key!(api_key) |
| 34 | 30 | end |
| 35 | 31 | |
| 32 | + def backtrace | |
| 33 | + @normalized_backtrace ||= Backtrace.find_or_create(:raw => @backtrace) | |
| 34 | + end | |
| 35 | + | |
| 36 | 36 | def generate_notice! |
| 37 | 37 | notice = Notice.new( |
| 38 | 38 | :message => message, |
| 39 | 39 | :error_class => error_class, |
| 40 | - :backtrace => backtrace, | |
| 40 | + :backtrace_id => backtrace.id, | |
| 41 | 41 | :request => request, |
| 42 | 42 | :server_environment => server_environment, |
| 43 | 43 | :notifier => notifier, |
| 44 | - :user_attributes => user_attributes) | |
| 44 | + :user_attributes => user_attributes, | |
| 45 | + :current_user => current_user | |
| 46 | + ) | |
| 45 | 47 | |
| 46 | 48 | err = app.find_or_create_err!( |
| 47 | 49 | :error_class => error_class, |
| ... | ... | @@ -53,5 +55,18 @@ class ErrorReport |
| 53 | 55 | err.notices << notice |
| 54 | 56 | notice |
| 55 | 57 | end |
| 58 | + | |
| 59 | + private | |
| 60 | + def fingerprint_source | |
| 61 | + { | |
| 62 | + :backtrace => backtrace.id, | |
| 63 | + :error_class => error_class, | |
| 64 | + :component => component, | |
| 65 | + :action => action, | |
| 66 | + :environment => rails_env, | |
| 67 | + :api_key => api_key | |
| 68 | + } | |
| 69 | + end | |
| 70 | + | |
| 56 | 71 | end |
| 57 | 72 | ... | ... |
app/models/issue_tracker.rb
| ... | ... | @@ -0,0 +1,47 @@ |
| 1 | +class IssueTrackers::BitbucketIssuesTracker < IssueTracker | |
| 2 | + Label = "bitbucket" | |
| 3 | + Note = 'Please configure your Bitbucket repository in the <strong>BITBUCKET REPO</strong> field above.' | |
| 4 | + Fields = [ | |
| 5 | + [:api_token, { | |
| 6 | + :placeholder => "Your username on Bitbucket account", | |
| 7 | + :label => "Username" | |
| 8 | + }], | |
| 9 | + [:project_id, { | |
| 10 | + :placeholder => "Password for your Bitbucket account", | |
| 11 | + :label => "Password" | |
| 12 | + }] | |
| 13 | + ] | |
| 14 | + | |
| 15 | + def check_params | |
| 16 | + if Fields.detect {|f| self[f[0]].blank? } | |
| 17 | + errors.add :base, 'You must specify your Bitbucket username and password' | |
| 18 | + end | |
| 19 | + end | |
| 20 | + | |
| 21 | + def repo_name | |
| 22 | + app.bitbucket_repo | |
| 23 | + end | |
| 24 | + | |
| 25 | + def create_issue(problem, reported_by = nil) | |
| 26 | + bitbucket = BitBucket.new :basic_auth => "#{api_token}:#{project_id}" | |
| 27 | + | |
| 28 | + begin | |
| 29 | + issue = bitbucket.issues.create api_token, repo_name.split('/')[1], :title => issue_title(problem), :content => body_template.result(binding), :priority => 'critical' | |
| 30 | + problem.update_attributes( | |
| 31 | + :issue_link => "https://bitbucket.org/#{repo_name}/issue/#{issue.local_id}/", | |
| 32 | + :issue_type => Label | |
| 33 | + ) | |
| 34 | + rescue BitBucket::Error::Unauthorized | |
| 35 | + raise IssueTrackers::AuthenticationError, "Could not authenticate with BitBucket. Please check your username and password." | |
| 36 | + end | |
| 37 | + end | |
| 38 | + | |
| 39 | + def body_template | |
| 40 | + @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/bitbucket_issues_body.txt.erb")) | |
| 41 | + end | |
| 42 | + | |
| 43 | + def url | |
| 44 | + "https://www.bitbucket.org/#{repo_name}/issues" | |
| 45 | + end | |
| 46 | +end | |
| 47 | + | ... | ... |
app/models/issue_trackers/fogbugz_tracker.rb
| 1 | -class IssueTrackers::FogbugzTracker < IssueTracker | |
| 2 | - Label = "fogbugz" | |
| 3 | - Fields = [ | |
| 4 | - [:project_id, { | |
| 5 | - :label => "Area Name" | |
| 6 | - }], | |
| 7 | - [:account, { | |
| 8 | - :label => "FogBugz URL", | |
| 9 | - :placeholder => "abc from http://abc.fogbugz.com/" | |
| 10 | - }], | |
| 11 | - [:username, { | |
| 12 | - :placeholder => "Username/Email for your account" | |
| 13 | - }], | |
| 14 | - [:password, { | |
| 15 | - :placeholder => "Password for your account" | |
| 16 | - }] | |
| 17 | - ] | |
| 18 | - | |
| 19 | - def check_params | |
| 20 | - if Fields.detect {|f| self[f[0]].blank? } | |
| 21 | - errors.add :base, 'You must specify your FogBugz Area Name, FogBugz URL, Username, and Password' | |
| 1 | +if defined? Fogbugz | |
| 2 | + class IssueTrackers::FogbugzTracker < IssueTracker | |
| 3 | + Label = "fogbugz" | |
| 4 | + Fields = [ | |
| 5 | + [:project_id, { | |
| 6 | + :label => "Area Name" | |
| 7 | + }], | |
| 8 | + [:account, { | |
| 9 | + :label => "FogBugz URL", | |
| 10 | + :placeholder => "abc from http://abc.fogbugz.com/" | |
| 11 | + }], | |
| 12 | + [:username, { | |
| 13 | + :placeholder => "Username/Email for your account" | |
| 14 | + }], | |
| 15 | + [:password, { | |
| 16 | + :placeholder => "Password for your account" | |
| 17 | + }] | |
| 18 | + ] | |
| 19 | + | |
| 20 | + def check_params | |
| 21 | + if Fields.detect {|f| self[f[0]].blank? } | |
| 22 | + errors.add :base, 'You must specify your FogBugz Area Name, FogBugz URL, Username, and Password' | |
| 23 | + end | |
| 22 | 24 | end |
| 23 | - end | |
| 24 | 25 | |
| 25 | - def create_issue(problem, reported_by = nil) | |
| 26 | - fogbugz = Fogbugz::Interface.new(:email => username, :password => password, :uri => "https://#{account}.fogbugz.com") | |
| 27 | - fogbugz.authenticate | |
| 26 | + def create_issue(problem, reported_by = nil) | |
| 27 | + fogbugz = Fogbugz::Interface.new(:email => username, :password => password, :uri => "https://#{account}.fogbugz.com") | |
| 28 | + fogbugz.authenticate | |
| 28 | 29 | |
| 29 | - issue = {} | |
| 30 | - issue['sTitle'] = issue_title problem | |
| 31 | - issue['sArea'] = project_id | |
| 32 | - issue['sEvent'] = body_template.result(binding) | |
| 33 | - issue['sTags'] = ['errbit'].join(',') | |
| 34 | - issue['cols'] = ['ixBug'].join(',') | |
| 30 | + issue = {} | |
| 31 | + issue['sTitle'] = issue_title problem | |
| 32 | + issue['sArea'] = project_id | |
| 33 | + issue['sEvent'] = body_template.result(binding) | |
| 34 | + issue['sTags'] = ['errbit'].join(',') | |
| 35 | + issue['cols'] = ['ixBug'].join(',') | |
| 35 | 36 | |
| 36 | - fb_resp = fogbugz.command(:new, issue) | |
| 37 | - problem.update_attributes( | |
| 38 | - :issue_link => "https://#{account}.fogbugz.com/default.asp?#{fb_resp['case']['ixBug']}", | |
| 39 | - :issue_type => Label | |
| 40 | - ) | |
| 37 | + fb_resp = fogbugz.command(:new, issue) | |
| 38 | + problem.update_attributes( | |
| 39 | + :issue_link => "https://#{account}.fogbugz.com/default.asp?#{fb_resp['case']['ixBug']}", | |
| 40 | + :issue_type => Label | |
| 41 | + ) | |
| 41 | 42 | |
| 42 | - end | |
| 43 | + end | |
| 43 | 44 | |
| 44 | - def body_template | |
| 45 | - @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/fogbugz_body.txt.erb")) | |
| 46 | - end | |
| 45 | + def body_template | |
| 46 | + @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/fogbugz_body.txt.erb")) | |
| 47 | + end | |
| 47 | 48 | |
| 48 | - def url | |
| 49 | - "http://#{account}.fogbugz.com/" | |
| 49 | + def url | |
| 50 | + "http://#{account}.fogbugz.com/" | |
| 51 | + end | |
| 50 | 52 | end |
| 51 | 53 | end |
| 52 | - | ... | ... |
app/models/issue_trackers/github_issues_tracker.rb
| 1 | -class IssueTrackers::GithubIssuesTracker < IssueTracker | |
| 2 | - Label = "github" | |
| 3 | - Note = 'Please configure your github repository in the <strong>GITHUB REPO</strong> field above.<br/>' << | |
| 4 | - 'Instead of providing your username & password, you can link your Github account ' << | |
| 5 | - 'to your user profile, and allow Errbit to create issues using your OAuth token.' | |
| 6 | - | |
| 7 | - Fields = [ | |
| 8 | - [:username, { | |
| 9 | - :placeholder => "Your username on GitHub" | |
| 10 | - }], | |
| 11 | - [:password, { | |
| 12 | - :placeholder => "Password for your account" | |
| 13 | - }] | |
| 14 | - ] | |
| 15 | - | |
| 16 | - attr_accessor :oauth_token | |
| 17 | - | |
| 18 | - def project_id | |
| 19 | - app.github_repo | |
| 20 | - end | |
| 21 | - | |
| 22 | - def check_params | |
| 23 | - if Fields.detect {|f| self[f[0]].blank? } | |
| 24 | - errors.add :base, 'You must specify your GitHub username and password' | |
| 1 | +if defined? Octokit | |
| 2 | + class IssueTrackers::GithubIssuesTracker < IssueTracker | |
| 3 | + Label = "github" | |
| 4 | + Note = 'Please configure your github repository in the <strong>GITHUB REPO</strong> field above.<br/>' << | |
| 5 | + 'Instead of providing your username & password, you can link your Github account ' << | |
| 6 | + 'to your user profile, and allow Errbit to create issues using your OAuth token.' | |
| 7 | + | |
| 8 | + Fields = [ | |
| 9 | + [:username, { | |
| 10 | + :placeholder => "Your username on GitHub" | |
| 11 | + }], | |
| 12 | + [:password, { | |
| 13 | + :placeholder => "Password for your account" | |
| 14 | + }] | |
| 15 | + ] | |
| 16 | + | |
| 17 | + attr_accessor :oauth_token | |
| 18 | + | |
| 19 | + def project_id | |
| 20 | + app.github_repo | |
| 25 | 21 | end |
| 26 | - end | |
| 27 | 22 | |
| 28 | - def create_issue(problem, reported_by = nil) | |
| 29 | - # Login using OAuth token, if given. | |
| 30 | - if oauth_token | |
| 31 | - client = Octokit::Client.new(:login => username, :oauth_token => oauth_token) | |
| 32 | - else | |
| 33 | - client = Octokit::Client.new(:login => username, :password => password) | |
| 23 | + def check_params | |
| 24 | + if Fields.detect {|f| self[f[0]].blank? } | |
| 25 | + errors.add :base, 'You must specify your GitHub username and password' | |
| 26 | + end | |
| 34 | 27 | end |
| 35 | 28 | |
| 36 | - begin | |
| 37 | - issue = client.create_issue(project_id, issue_title(problem), body_template.result(binding).unpack('C*').pack('U*'), options = {}) | |
| 38 | - problem.update_attributes( | |
| 39 | - :issue_link => issue.html_url, | |
| 40 | - :issue_type => Label | |
| 41 | - ) | |
| 42 | - | |
| 43 | - rescue Octokit::Unauthorized | |
| 44 | - raise IssueTrackers::AuthenticationError, "Could not authenticate with GitHub. Please check your username and password." | |
| 29 | + def create_issue(problem, reported_by = nil) | |
| 30 | + # Login using OAuth token, if given. | |
| 31 | + if oauth_token | |
| 32 | + client = Octokit::Client.new(:login => username, :oauth_token => oauth_token) | |
| 33 | + else | |
| 34 | + client = Octokit::Client.new(:login => username, :password => password) | |
| 35 | + end | |
| 36 | + | |
| 37 | + begin | |
| 38 | + issue = client.create_issue(project_id, issue_title(problem), body_template.result(binding).unpack('C*').pack('U*'), options = {}) | |
| 39 | + problem.update_attributes( | |
| 40 | + :issue_link => issue.html_url, | |
| 41 | + :issue_type => Label | |
| 42 | + ) | |
| 43 | + | |
| 44 | + rescue Octokit::Unauthorized | |
| 45 | + raise IssueTrackers::AuthenticationError, "Could not authenticate with GitHub. Please check your username and password." | |
| 46 | + end | |
| 45 | 47 | end |
| 46 | - end | |
| 47 | 48 | |
| 48 | - def body_template | |
| 49 | - @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/github_issues_body.txt.erb").gsub(/^\s*/, '')) | |
| 50 | - end | |
| 49 | + def body_template | |
| 50 | + @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/github_issues_body.txt.erb").gsub(/^\s*/, '')) | |
| 51 | + end | |
| 51 | 52 | |
| 52 | - def url | |
| 53 | - "https://github.com/#{project_id}/issues" | |
| 53 | + def url | |
| 54 | + "https://github.com/#{project_id}/issues" | |
| 55 | + end | |
| 54 | 56 | end |
| 55 | 57 | -end |
| 58 | +end | |
| 56 | 59 | \ No newline at end of file | ... | ... |
app/models/issue_trackers/lighthouse_tracker.rb
| 1 | -class IssueTrackers::LighthouseTracker < IssueTracker | |
| 2 | - Label = "lighthouseapp" | |
| 3 | - Fields = [ | |
| 4 | - [:account, { | |
| 5 | - :placeholder => "abc from http://abc.lighthouseapp.com" | |
| 6 | - }], | |
| 7 | - [:api_token, { | |
| 8 | - :placeholder => "API Token for your account" | |
| 9 | - }], | |
| 10 | - [:project_id, { | |
| 11 | - :placeholder => "Lighthouse project" | |
| 12 | - }] | |
| 13 | - ] | |
| 14 | - | |
| 15 | - def check_params | |
| 16 | - if Fields.detect {|f| self[f[0]].blank? } | |
| 17 | - errors.add :base, 'You must specify your Lighthouseapp account, API token and Project ID' | |
| 1 | +if defined? Lighthouse | |
| 2 | + class IssueTrackers::LighthouseTracker < IssueTracker | |
| 3 | + Label = "lighthouseapp" | |
| 4 | + Fields = [ | |
| 5 | + [:account, { | |
| 6 | + :placeholder => "abc from http://abc.lighthouseapp.com" | |
| 7 | + }], | |
| 8 | + [:api_token, { | |
| 9 | + :placeholder => "API Token for your account" | |
| 10 | + }], | |
| 11 | + [:project_id, { | |
| 12 | + :placeholder => "Lighthouse project" | |
| 13 | + }] | |
| 14 | + ] | |
| 15 | + | |
| 16 | + def check_params | |
| 17 | + if Fields.detect {|f| self[f[0]].blank? } | |
| 18 | + errors.add :base, 'You must specify your Lighthouseapp account, API token and Project ID' | |
| 19 | + end | |
| 18 | 20 | end |
| 19 | - end | |
| 20 | 21 | |
| 21 | - def create_issue(problem, reported_by = nil) | |
| 22 | - Lighthouse.account = account | |
| 23 | - Lighthouse.token = api_token | |
| 24 | - # updating lighthouse account | |
| 25 | - Lighthouse::Ticket.site | |
| 26 | - Lighthouse::Ticket.format = :xml | |
| 27 | - ticket = Lighthouse::Ticket.new(:project_id => project_id) | |
| 28 | - ticket.title = issue_title problem | |
| 22 | + def create_issue(problem, reported_by = nil) | |
| 23 | + Lighthouse.account = account | |
| 24 | + Lighthouse.token = api_token | |
| 25 | + # updating lighthouse account | |
| 26 | + Lighthouse::Ticket.site | |
| 27 | + Lighthouse::Ticket.format = :xml | |
| 28 | + ticket = Lighthouse::Ticket.new(:project_id => project_id) | |
| 29 | + ticket.title = issue_title problem | |
| 29 | 30 | |
| 30 | - ticket.body = body_template.result(binding) | |
| 31 | + ticket.body = body_template.result(binding) | |
| 31 | 32 | |
| 32 | - ticket.tags << "errbit" | |
| 33 | - ticket.save! | |
| 34 | - problem.update_attributes( | |
| 35 | - :issue_link => "#{Lighthouse::Ticket.site.to_s.sub(/#{Lighthouse::Ticket.site.path}$/, '')}#{Lighthouse::Ticket.element_path(ticket.id, :project_id => project_id)}".sub(/\.xml$/, ''), | |
| 36 | - :issue_type => Label | |
| 37 | - ) | |
| 33 | + ticket.tags << "errbit" | |
| 34 | + ticket.save! | |
| 35 | + problem.update_attributes( | |
| 36 | + :issue_link => "#{Lighthouse::Ticket.site.to_s.sub(/#{Lighthouse::Ticket.site.path}$/, '')}#{Lighthouse::Ticket.element_path(ticket.id, :project_id => project_id)}".sub(/\.xml$/, ''), | |
| 37 | + :issue_type => Label | |
| 38 | + ) | |
| 38 | 39 | |
| 39 | - end | |
| 40 | + end | |
| 40 | 41 | |
| 41 | - def body_template | |
| 42 | - @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/lighthouseapp_body.txt.erb").gsub(/^\s*/, '')) | |
| 43 | - end | |
| 42 | + def body_template | |
| 43 | + @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/lighthouseapp_body.txt.erb").gsub(/^\s*/, '')) | |
| 44 | + end | |
| 44 | 45 | |
| 45 | - def url | |
| 46 | - "http://#{account}.lighthouseapp.com" | |
| 46 | + def url | |
| 47 | + "http://#{account}.lighthouseapp.com" | |
| 48 | + end | |
| 47 | 49 | end |
| 48 | -end | |
| 49 | - | |
| 50 | +end | |
| 50 | 51 | \ No newline at end of file | ... | ... |
app/models/issue_trackers/pivotal_labs_tracker.rb
| 1 | -class IssueTrackers::PivotalLabsTracker < IssueTracker | |
| 2 | - Label = "pivotal" | |
| 3 | - Fields = [ | |
| 4 | - [:api_token, { | |
| 5 | - :placeholder => "API Token for your account" | |
| 6 | - }], | |
| 7 | - [:project_id, {}] | |
| 8 | - ] | |
| 1 | +if defined? PivotalTracker | |
| 2 | + class IssueTrackers::PivotalLabsTracker < IssueTracker | |
| 3 | + Label = "pivotal" | |
| 4 | + Fields = [ | |
| 5 | + [:api_token, { | |
| 6 | + :placeholder => "API Token for your account" | |
| 7 | + }], | |
| 8 | + [:project_id, {}] | |
| 9 | + ] | |
| 9 | 10 | |
| 10 | - def check_params | |
| 11 | - if Fields.detect {|f| self[f[0]].blank? } | |
| 12 | - errors.add :base, 'You must specify your Pivotal Tracker API token and Project ID' | |
| 11 | + def check_params | |
| 12 | + if Fields.detect {|f| self[f[0]].blank? } | |
| 13 | + errors.add :base, 'You must specify your Pivotal Tracker API token and Project ID' | |
| 14 | + end | |
| 13 | 15 | end |
| 14 | - end | |
| 15 | 16 | |
| 16 | - def create_issue(problem, reported_by = nil) | |
| 17 | - PivotalTracker::Client.token = api_token | |
| 18 | - PivotalTracker::Client.use_ssl = true | |
| 19 | - project = PivotalTracker::Project.find project_id.to_i | |
| 20 | - story = project.stories.create :name => issue_title(problem), | |
| 21 | - :story_type => 'bug', :description => body_template.result(binding), | |
| 22 | - :requested_by => reported_by.name | |
| 17 | + def create_issue(problem, reported_by = nil) | |
| 18 | + PivotalTracker::Client.token = api_token | |
| 19 | + PivotalTracker::Client.use_ssl = true | |
| 20 | + project = PivotalTracker::Project.find project_id.to_i | |
| 21 | + story = project.stories.create :name => issue_title(problem), | |
| 22 | + :story_type => 'bug', :description => body_template.result(binding), | |
| 23 | + :requested_by => reported_by.name | |
| 23 | 24 | |
| 24 | - if story.errors.present? | |
| 25 | - raise IssueTrackers::IssueTrackerError, story.errors.first | |
| 26 | - else | |
| 27 | - problem.update_attributes( | |
| 28 | - :issue_link => "https://www.pivotaltracker.com/story/show/#{story.id}", | |
| 29 | - :issue_type => Label | |
| 30 | - ) | |
| 25 | + if story.errors.present? | |
| 26 | + raise IssueTrackers::IssueTrackerError, story.errors.first | |
| 27 | + else | |
| 28 | + problem.update_attributes( | |
| 29 | + :issue_link => "https://www.pivotaltracker.com/story/show/#{story.id}", | |
| 30 | + :issue_type => Label | |
| 31 | + ) | |
| 32 | + end | |
| 31 | 33 | end |
| 32 | - end | |
| 33 | 34 | |
| 34 | - def body_template | |
| 35 | - @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/pivotal_body.txt.erb")) | |
| 36 | - end | |
| 35 | + def body_template | |
| 36 | + @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/pivotal_body.txt.erb")) | |
| 37 | + end | |
| 37 | 38 | |
| 38 | - def url | |
| 39 | - "https://www.pivotaltracker.com/" | |
| 39 | + def url | |
| 40 | + "https://www.pivotaltracker.com/" | |
| 41 | + end | |
| 40 | 42 | end |
| 41 | -end | |
| 42 | - | |
| 43 | +end | |
| 43 | 44 | \ No newline at end of file | ... | ... |
app/models/issue_trackers/redmine_tracker.rb
| 1 | -class IssueTrackers::RedmineTracker < IssueTracker | |
| 2 | - Label = "redmine" | |
| 3 | - Fields = [ | |
| 4 | - [:account, { | |
| 5 | - :label => "Redmine URL", | |
| 6 | - :placeholder => "e.g. http://www.redmine.org/" | |
| 7 | - }], | |
| 8 | - [:api_token, { | |
| 9 | - :placeholder => "API Token for your account" | |
| 10 | - }], | |
| 11 | - [:project_id, { | |
| 12 | - :label => "Ticket Project", | |
| 13 | - :placeholder => "Redmine Project where tickets will be created" | |
| 14 | - }], | |
| 15 | - [:alt_project_id, { | |
| 16 | - :optional => true, | |
| 17 | - :label => "App Project", | |
| 18 | - :placeholder => "Where app's files & revisions can be viewed. (Leave blank to use the above project by default)" | |
| 19 | - }] | |
| 20 | - ] | |
| 1 | +if defined? RedmineClient | |
| 2 | + class IssueTrackers::RedmineTracker < IssueTracker | |
| 3 | + Label = "redmine" | |
| 4 | + Fields = [ | |
| 5 | + [:account, { | |
| 6 | + :label => "Redmine URL", | |
| 7 | + :placeholder => "e.g. http://www.redmine.org/" | |
| 8 | + }], | |
| 9 | + [:api_token, { | |
| 10 | + :placeholder => "API Token for your account" | |
| 11 | + }], | |
| 12 | + [:project_id, { | |
| 13 | + :label => "Ticket Project", | |
| 14 | + :placeholder => "Redmine Project where tickets will be created" | |
| 15 | + }], | |
| 16 | + [:alt_project_id, { | |
| 17 | + :optional => true, | |
| 18 | + :label => "App Project", | |
| 19 | + :placeholder => "Where app's files & revisions can be viewed. (Leave blank to use the above project by default)" | |
| 20 | + }] | |
| 21 | + ] | |
| 21 | 22 | |
| 22 | - def check_params | |
| 23 | - if Fields.detect {|f| self[f[0]].blank? && !f[1][:optional]} | |
| 24 | - errors.add :base, 'You must specify your Redmine URL, API token and Project ID' | |
| 23 | + def check_params | |
| 24 | + if Fields.detect {|f| self[f[0]].blank? && !f[1][:optional]} | |
| 25 | + errors.add :base, 'You must specify your Redmine URL, API token and Project ID' | |
| 26 | + end | |
| 25 | 27 | end |
| 26 | - end | |
| 27 | 28 | |
| 28 | - def create_issue(problem, reported_by = nil) | |
| 29 | - token = api_token | |
| 30 | - acc = account | |
| 31 | - RedmineClient::Base.configure do | |
| 32 | - self.token = token | |
| 33 | - self.site = acc | |
| 34 | - self.format = :xml | |
| 29 | + def create_issue(problem, reported_by = nil) | |
| 30 | + token = api_token | |
| 31 | + acc = account | |
| 32 | + RedmineClient::Base.configure do | |
| 33 | + self.token = token | |
| 34 | + self.site = acc | |
| 35 | + self.format = :xml | |
| 36 | + end | |
| 37 | + issue = RedmineClient::Issue.new(:project_id => project_id) | |
| 38 | + issue.subject = issue_title problem | |
| 39 | + issue.description = body_template.result(binding) | |
| 40 | + issue.save! | |
| 41 | + problem.update_attributes( | |
| 42 | + :issue_link => "#{RedmineClient::Issue.site.to_s.sub(/#{RedmineClient::Issue.site.path}$/, '')}#{RedmineClient::Issue.element_path(issue.id, :project_id => project_id)}".sub(/\.xml\?project_id=#{project_id}$/, "\?project_id=#{project_id}"), | |
| 43 | + :issue_type => Label | |
| 44 | + ) | |
| 35 | 45 | end |
| 36 | - issue = RedmineClient::Issue.new(:project_id => project_id) | |
| 37 | - issue.subject = issue_title problem | |
| 38 | - issue.description = body_template.result(binding) | |
| 39 | - issue.save! | |
| 40 | - problem.update_attributes( | |
| 41 | - :issue_link => "#{RedmineClient::Issue.site.to_s.sub(/#{RedmineClient::Issue.site.path}$/, '')}#{RedmineClient::Issue.element_path(issue.id, :project_id => project_id)}".sub(/\.xml\?project_id=#{project_id}$/, "\?project_id=#{project_id}"), | |
| 42 | - :issue_type => Label | |
| 43 | - ) | |
| 44 | - end | |
| 45 | 46 | |
| 46 | - def url_to_file(file_path, line_number = nil) | |
| 47 | - # alt_project_id let's users specify a different project for tickets / app files. | |
| 48 | - project = self.alt_project_id.present? ? self.alt_project_id : self.project_id | |
| 49 | - url = "#{self.account}/projects/#{project}/repository/annotate/#{file_path.sub(/^\//,'')}" | |
| 50 | - line_number ? url << "#L#{line_number}" : url | |
| 51 | - end | |
| 47 | + def url_to_file(file_path, line_number = nil) | |
| 48 | + # alt_project_id let's users specify a different project for tickets / app files. | |
| 49 | + project = self.alt_project_id.present? ? self.alt_project_id : self.project_id | |
| 50 | + url = "#{self.account}/projects/#{project}/repository/annotate/#{file_path.sub(/^\//,'')}" | |
| 51 | + line_number ? url << "#L#{line_number}" : url | |
| 52 | + end | |
| 52 | 53 | |
| 53 | - def body_template | |
| 54 | - @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/textile_body.txt.erb")) | |
| 55 | - end | |
| 54 | + def body_template | |
| 55 | + @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/textile_body.txt.erb")) | |
| 56 | + end | |
| 56 | 57 | |
| 57 | - def url | |
| 58 | - acc_url = account.start_with?('http') ? account : "http://#{account}" | |
| 59 | - URI.parse("#{acc_url}?project_id=#{project_id}").to_s | |
| 60 | - rescue URI::InvalidURIError | |
| 58 | + def url | |
| 59 | + acc_url = account.start_with?('http') ? account : "http://#{account}" | |
| 60 | + URI.parse("#{acc_url}?project_id=#{project_id}").to_s | |
| 61 | + rescue URI::InvalidURIError | |
| 62 | + end | |
| 61 | 63 | end |
| 62 | 64 | end |
| 63 | - | ... | ... |
app/models/notice.rb
| ... | ... | @@ -6,14 +6,17 @@ class Notice |
| 6 | 6 | include Mongoid::Timestamps |
| 7 | 7 | |
| 8 | 8 | field :message |
| 9 | - field :backtrace, :type => Array | |
| 10 | 9 | field :server_environment, :type => Hash |
| 11 | 10 | field :request, :type => Hash |
| 12 | 11 | field :notifier, :type => Hash |
| 13 | 12 | field :user_attributes, :type => Hash |
| 13 | + field :current_user, :type => Hash | |
| 14 | 14 | field :error_class |
| 15 | + delegate :lines, :to => :backtrace, :prefix => true | |
| 16 | + delegate :app, :to => :err | |
| 15 | 17 | |
| 16 | 18 | belongs_to :err |
| 19 | + belongs_to :backtrace, :index => true | |
| 17 | 20 | index :created_at |
| 18 | 21 | index( |
| 19 | 22 | [ |
| ... | ... | @@ -89,17 +92,8 @@ class Notice |
| 89 | 92 | request['session'] || {} |
| 90 | 93 | end |
| 91 | 94 | |
| 92 | - # Backtrace containing only files from the app itself (ignore gems) | |
| 93 | - def app_backtrace | |
| 94 | - backtrace.select { |l| l && l['file'] && l['file'].include?("[PROJECT_ROOT]") } | |
| 95 | - end | |
| 96 | - | |
| 97 | - def backtrace | |
| 98 | - # If gems are vendored into project, treat vendored gem dir as [GEM_ROOT] | |
| 99 | - (read_attribute(:backtrace) || []).map do |line| | |
| 100 | - # Changes "[PROJECT_ROOT]/rubygems/ruby/1.9.1/gems" to "[GEM_ROOT]/gems" | |
| 101 | - line.merge 'file' => line['file'].to_s.gsub(/\[PROJECT_ROOT\]\/.*\/ruby\/[0-9.]+\/gems/, '[GEM_ROOT]/gems') | |
| 102 | - end | |
| 95 | + def in_app_backtrace_lines | |
| 96 | + backtrace_lines.in_app | |
| 103 | 97 | end |
| 104 | 98 | |
| 105 | 99 | protected |
| ... | ... | @@ -113,11 +107,11 @@ class Notice |
| 113 | 107 | end |
| 114 | 108 | |
| 115 | 109 | def remove_cached_attributes_from_problem |
| 116 | - problem.remove_cached_notice_attribures(self) if err | |
| 110 | + problem.remove_cached_notice_attributes(self) if err | |
| 117 | 111 | end |
| 118 | 112 | |
| 119 | 113 | def unresolve_problem |
| 120 | - problem.update_attribute(:resolved, false) if problem.resolved? | |
| 114 | + problem.update_attributes!(:resolved => false, :resolved_at => nil, :notices_count => 1) if problem.resolved? | |
| 121 | 115 | end |
| 122 | 116 | |
| 123 | 117 | def cache_attributes_on_problem |
| ... | ... | @@ -128,8 +122,6 @@ class Notice |
| 128 | 122 | [:server_environment, :request, :notifier].each do |h| |
| 129 | 123 | send("#{h}=",sanitize_hash(send(h))) |
| 130 | 124 | end |
| 131 | - # Set unknown backtrace files | |
| 132 | - read_attribute(:backtrace).each{|line| line['file'] = "[unknown source]" if line['file'].blank? } | |
| 133 | 125 | end |
| 134 | 126 | |
| 135 | 127 | def sanitize_hash(h) | ... | ... |
app/models/notice_observer.rb
| ... | ... | @@ -2,18 +2,21 @@ class NoticeObserver < Mongoid::Observer |
| 2 | 2 | observe :notice |
| 3 | 3 | |
| 4 | 4 | def after_create notice |
| 5 | - return unless should_notify? notice | |
| 5 | + # if the app has a notification service, fire it off | |
| 6 | + if notice.app.notification_service_configured? | |
| 7 | + notice.app.notification_service.create_notification(notice.problem) | |
| 8 | + end | |
| 6 | 9 | |
| 7 | - Mailer.err_notification(notice).deliver | |
| 10 | + if notice.app.notification_recipients.any? | |
| 11 | + Mailer.err_notification(notice).deliver | |
| 12 | + end | |
| 8 | 13 | end |
| 9 | 14 | |
| 10 | 15 | private |
| 11 | 16 | |
| 12 | 17 | def should_notify? notice |
| 13 | 18 | app = notice.app |
| 14 | - app.notify_on_errs? && | |
| 15 | - (Errbit::Config.per_app_email_at_notices && app.email_at_notices || Errbit::Config.email_at_notices).include?(notice.problem.notices_count) && | |
| 16 | - app.notification_recipients.any? | |
| 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) | |
| 17 | 21 | end |
| 18 | - | |
| 19 | 22 | end | ... | ... |
| ... | ... | @@ -0,0 +1,33 @@ |
| 1 | +class NotificationService | |
| 2 | + include Mongoid::Document | |
| 3 | + | |
| 4 | + include Rails.application.routes.url_helpers | |
| 5 | + default_url_options[:host] = ActionMailer::Base.default_url_options[:host] | |
| 6 | + | |
| 7 | + field :room_id, :type => String | |
| 8 | + field :api_token, :type => String | |
| 9 | + field :subdomain, :type => String | |
| 10 | + field :sender_name, :type => String | |
| 11 | + | |
| 12 | + embedded_in :app, :inverse_of => :notification_service | |
| 13 | + | |
| 14 | + validate :check_params | |
| 15 | + | |
| 16 | + # Subclasses are responsible for overwriting this method. | |
| 17 | + def check_params; true; end | |
| 18 | + | |
| 19 | + def notification_description(problem) | |
| 20 | + "[#{ problem.environment }][#{ problem.where }] #{problem.message.to_s.truncate(100)}" | |
| 21 | + end | |
| 22 | + | |
| 23 | + # Allows us to set the issue tracker class from a single form. | |
| 24 | + def type; self._type; end | |
| 25 | + def type=(t); self._type=t; end | |
| 26 | + | |
| 27 | + def url; nil; end | |
| 28 | + | |
| 29 | + # Retrieve tracker label from either class or instance. | |
| 30 | + Label = '' | |
| 31 | + def self.label; self::Label; end | |
| 32 | + def label; self.class.label; end | |
| 33 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,34 @@ |
| 1 | +if defined? Campy | |
| 2 | + class NotificationServices::CampfireService < NotificationService | |
| 3 | + Label = "campfire" | |
| 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 | + }], | |
| 15 | + ] | |
| 16 | + | |
| 17 | + def check_params | |
| 18 | + if Fields.detect {|f| self[f[0]].blank? } | |
| 19 | + errors.add :base, 'You must specify your Campfire Subdomain, API token and Room ID' | |
| 20 | + end | |
| 21 | + end | |
| 22 | + | |
| 23 | + def url | |
| 24 | + "http://campfirenow.com/" | |
| 25 | + end | |
| 26 | + | |
| 27 | + def create_notification(problem) | |
| 28 | + # build the campfire client | |
| 29 | + campy = Campy::Room.new(:account => subdomain, :token => api_token, :room_id => room_id) | |
| 30 | + # post the issue to the campfire room | |
| 31 | + campy.speak "[errbit] #{problem.app.name} #{notification_description problem} - http://#{Errbit::Config.host}/apps/#{problem.app.id.to_s}/problems/#{problem.id.to_s}" | |
| 32 | + end | |
| 33 | + end | |
| 34 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,37 @@ |
| 1 | +class NotificationServices::GtalkService < NotificationService | |
| 2 | + Label = "gtalk" | |
| 3 | + Fields = [ | |
| 4 | + [:subdomain, { | |
| 5 | + :placeholder => "username@example.com", | |
| 6 | + :label => "Username" | |
| 7 | + }], | |
| 8 | + [:api_token, { | |
| 9 | + :placeholder => "password", | |
| 10 | + :label => "Password" | |
| 11 | + }], | |
| 12 | + [:room_id, { | |
| 13 | + :placeholder => "touser@example.com", | |
| 14 | + :label => "Send To User" | |
| 15 | + }], | |
| 16 | + ] | |
| 17 | + | |
| 18 | + def check_params | |
| 19 | + if Fields.detect {|f| self[f[0]].blank? } | |
| 20 | + errors.add :base, 'You must specify your Username, Password and To User' | |
| 21 | + end | |
| 22 | + end | |
| 23 | + | |
| 24 | + def url | |
| 25 | + "http://www.google.com/talk/" | |
| 26 | + end | |
| 27 | + | |
| 28 | + def create_notification(problem) | |
| 29 | + # build the xmpp client | |
| 30 | + client = Jabber::Client.new(Jabber::JID.new(subdomain)) | |
| 31 | + client.connect("talk.google.com") | |
| 32 | + client.auth(api_token) | |
| 33 | + | |
| 34 | + # post the issue to the xmpp room | |
| 35 | + client.send(Jabber::Message.new(room_id, "[errbit] http://#{Errbit::Config.host}/apps/#{problem.app.id.to_s} #{notification_description problem}")) | |
| 36 | + end | |
| 37 | +end | |
| 0 | 38 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -0,0 +1,31 @@ |
| 1 | +if defined? HipChat | |
| 2 | + class NotificationServices::HipchatService < NotificationService | |
| 3 | + Label = 'hipchat' | |
| 4 | + Fields = [ | |
| 5 | + [:api_token, { | |
| 6 | + :placeholder => "API Token" | |
| 7 | + }], | |
| 8 | + [:room_id, { | |
| 9 | + :placeholder => "Room ID", | |
| 10 | + :label => "Room ID" | |
| 11 | + }], | |
| 12 | + ] | |
| 13 | + | |
| 14 | + def check_params | |
| 15 | + if Fields.any? { |f, _| self[f].blank? } | |
| 16 | + errors.add :base, 'You must specify your Hipchat API token and Room ID' | |
| 17 | + end | |
| 18 | + end | |
| 19 | + | |
| 20 | + def create_notification(problem) | |
| 21 | + url = app_problem_url problem.app, problem | |
| 22 | + message = <<-MSG.strip_heredoc | |
| 23 | + [#{ERB::Util.html_escape problem.app.name}]#{ERB::Util.html_escape notification_description(problem)}<br> | |
| 24 | + <a href="#{url}">#{url}</a> | |
| 25 | + MSG | |
| 26 | + | |
| 27 | + client = HipChat::Client.new(api_token) | |
| 28 | + client[room_id].send('Errbit', message, :color => 'red') | |
| 29 | + end | |
| 30 | + end | |
| 31 | +end | |
| 0 | 32 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -0,0 +1,38 @@ |
| 1 | +class NotificationServices::HoiioService < NotificationService | |
| 2 | + Label = "hoiio" | |
| 3 | + Fields = [ | |
| 4 | + [:api_token, { | |
| 5 | + :placeholder => "App ID", | |
| 6 | + :label => "App ID" | |
| 7 | + }], | |
| 8 | + [:subdomain, { | |
| 9 | + :placeholder => "Access Token", | |
| 10 | + :label => "Access Token" | |
| 11 | + }], | |
| 12 | + [:room_id, { | |
| 13 | + :placeholder => "+6511111111, +6511111111", | |
| 14 | + :label => "Recipient's phone numbers seperated by comma. Phone numbers should start with a \"+\" and country code." | |
| 15 | + }] | |
| 16 | + ] | |
| 17 | + | |
| 18 | + def check_params | |
| 19 | + if Fields.detect {|f| self[f[0]].blank? } | |
| 20 | + errors.add :base, 'You must specify your App ID, Access Token and Recipient\'s phone numbers' | |
| 21 | + end | |
| 22 | + end | |
| 23 | + | |
| 24 | + def notification_description(problem) | |
| 25 | + "[#{ problem.environment }]#{problem.message.to_s.truncate(50)}" | |
| 26 | + end | |
| 27 | + | |
| 28 | + def create_notification(problem) | |
| 29 | + # build the hoi client | |
| 30 | + sms = Hoi::SMS.new(api_token, subdomain) | |
| 31 | + | |
| 32 | + # send sms | |
| 33 | + room_id.split(',').each do |number| | |
| 34 | + sms.send :dest => number, :msg => "http://#{Errbit::Config.host}/apps/#{problem.app.id.to_s} #{notification_description problem}" | |
| 35 | + end | |
| 36 | + | |
| 37 | + end | |
| 38 | +end | |
| 0 | 39 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -0,0 +1,28 @@ |
| 1 | +class NotificationServices::PushoverService < NotificationService | |
| 2 | + Label = "pushover" | |
| 3 | + Fields = [ | |
| 4 | + [:api_token, { | |
| 5 | + :placeholder => "User Key", | |
| 6 | + :label => "User Key" | |
| 7 | + }], | |
| 8 | + [:subdomain, { | |
| 9 | + :placeholder => "Application API Token", | |
| 10 | + :label => "Application API Token" | |
| 11 | + }] | |
| 12 | + ] | |
| 13 | + | |
| 14 | + def check_params | |
| 15 | + if Fields.detect {|f| self[f[0]].blank? } | |
| 16 | + errors.add :base, 'You must specify your User Key and Application API Token.' | |
| 17 | + end | |
| 18 | + end | |
| 19 | + | |
| 20 | + def create_notification(problem) | |
| 21 | + # build the hoi client | |
| 22 | + notification = Rushover::Client.new(subdomain) | |
| 23 | + | |
| 24 | + # send push notification to pushover | |
| 25 | + notification.notify(api_token, "#{notification_description problem}", :priority => 1, :title => "Errbit Notification", :url => "http://#{Errbit::Config.host}/apps/#{problem.app.id.to_s}", :url_title => "Link to error") | |
| 26 | + | |
| 27 | + end | |
| 28 | +end | |
| 0 | 29 | \ No newline at end of file | ... | ... |
app/models/problem.rb
| ... | ... | @@ -6,9 +6,11 @@ class Problem |
| 6 | 6 | include Mongoid::Document |
| 7 | 7 | include Mongoid::Timestamps |
| 8 | 8 | |
| 9 | - field :last_notice_at, :type => DateTime | |
| 9 | + field :last_notice_at, :type => DateTime, :default => Proc.new { Time.now } | |
| 10 | + field :first_notice_at, :type => DateTime, :default => Proc.new { Time.now } | |
| 10 | 11 | field :last_deploy_at, :type => Time |
| 11 | 12 | field :resolved, :type => Boolean, :default => false |
| 13 | + field :resolved_at, :type => Time | |
| 12 | 14 | field :issue_link, :type => String |
| 13 | 15 | field :issue_type, :type => String |
| 14 | 16 | |
| ... | ... | @@ -28,7 +30,9 @@ class Problem |
| 28 | 30 | index :app_name |
| 29 | 31 | index :message |
| 30 | 32 | index :last_notice_at |
| 33 | + index :first_notice_at | |
| 31 | 34 | index :last_deploy_at |
| 35 | + index :resolved_at | |
| 32 | 36 | index :notices_count |
| 33 | 37 | |
| 34 | 38 | belongs_to :app |
| ... | ... | @@ -41,6 +45,8 @@ class Problem |
| 41 | 45 | scope :unresolved, where(:resolved => false) |
| 42 | 46 | scope :ordered, order_by(:last_notice_at.desc) |
| 43 | 47 | scope :for_apps, lambda {|apps| where(:app_id.in => apps.all.map(&:id))} |
| 48 | + | |
| 49 | + validates_presence_of :last_notice_at, :first_notice_at | |
| 44 | 50 | |
| 45 | 51 | |
| 46 | 52 | def self.in_env(env) |
| ... | ... | @@ -52,11 +58,11 @@ class Problem |
| 52 | 58 | end |
| 53 | 59 | |
| 54 | 60 | def resolve! |
| 55 | - self.update_attributes!(:resolved => true, :notices_count => 0) | |
| 61 | + self.update_attributes!(:resolved => true, :resolved_at => Time.now) | |
| 56 | 62 | end |
| 57 | 63 | |
| 58 | 64 | def unresolve! |
| 59 | - self.update_attributes!(:resolved => false) | |
| 65 | + self.update_attributes!(:resolved => false, :resolved_at => nil) | |
| 60 | 66 | end |
| 61 | 67 | |
| 62 | 68 | def unresolved? |
| ... | ... | @@ -103,6 +109,10 @@ class Problem |
| 103 | 109 | else raise("\"#{sort}\" is not a recognized sort") |
| 104 | 110 | end |
| 105 | 111 | end |
| 112 | + | |
| 113 | + def self.in_date_range(date_range) | |
| 114 | + where(:first_notice_at.lte => date_range.end).where("$or" => [{:resolved_at => nil}, {:resolved_at.gte => date_range.begin}]) | |
| 115 | + end | |
| 106 | 116 | |
| 107 | 117 | |
| 108 | 118 | def reset_cached_attributes |
| ... | ... | @@ -124,21 +134,26 @@ class Problem |
| 124 | 134 | end |
| 125 | 135 | |
| 126 | 136 | def cache_notice_attributes(notice=nil) |
| 127 | - notice ||= notices.first | |
| 128 | - attrs = {:last_notice_at => notices.order_by([:created_at, :asc]).last.try(:created_at)} | |
| 137 | + first_notice = notices.order_by([:created_at, :asc]).first | |
| 138 | + last_notice = notices.order_by([:created_at, :asc]).last | |
| 139 | + notice ||= first_notice | |
| 140 | + | |
| 141 | + attrs = {} | |
| 142 | + attrs[:first_notice_at] = first_notice.created_at if first_notice | |
| 143 | + attrs[:last_notice_at] = last_notice.created_at if last_notice | |
| 129 | 144 | attrs.merge!( |
| 130 | - :message => notice.message, | |
| 145 | + :message => notice.message, | |
| 131 | 146 | :environment => notice.environment_name, |
| 132 | 147 | :error_class => notice.error_class, |
| 133 | - :where => notice.where, | |
| 148 | + :where => notice.where, | |
| 134 | 149 | :messages => attribute_count_increase(:messages, notice.message), |
| 135 | 150 | :hosts => attribute_count_increase(:hosts, notice.host), |
| 136 | 151 | :user_agents => attribute_count_increase(:user_agents, notice.user_agent_string) |
| 137 | - ) if notice | |
| 152 | + ) if notice | |
| 138 | 153 | update_attributes!(attrs) |
| 139 | 154 | end |
| 140 | 155 | |
| 141 | - def remove_cached_notice_attribures(notice) | |
| 156 | + def remove_cached_notice_attributes(notice) | |
| 142 | 157 | update_attributes!( |
| 143 | 158 | :messages => attribute_count_descrease(:messages, notice.message), |
| 144 | 159 | :hosts => attribute_count_descrease(:hosts, notice.host), | ... | ... |
app/views/apps/_configuration_instructions.html.haml
| 1 | 1 | %pre |
| 2 | - %code | |
| 3 | - :preserve | |
| 4 | - # Require the hoptoad_notifier gem in your App. | |
| 5 | - # --------------------------------------------- | |
| 6 | - # | |
| 7 | - # Rails 3 - In your Gemfile | |
| 8 | - # gem 'airbrake' | |
| 9 | - # | |
| 10 | - # Rails 2 - In environment.rb | |
| 11 | - # config.gem 'airbrake' | |
| 12 | - # | |
| 13 | - # Then add the following to config/initializers/errbit.rb | |
| 14 | - # ------------------------------------------------------- | |
| 2 | + %code | |
| 3 | + :preserve | |
| 4 | + # Require the hoptoad_notifier gem in your App. | |
| 5 | + # --------------------------------------------- | |
| 6 | + # | |
| 7 | + # Rails 3 - In your Gemfile | |
| 8 | + # gem 'airbrake' | |
| 9 | + # | |
| 10 | + # Rails 2 - In environment.rb | |
| 11 | + # config.gem 'airbrake' | |
| 12 | + # | |
| 13 | + # Then add the following to config/initializers/errbit.rb | |
| 14 | + # ------------------------------------------------------- | |
| 15 | 15 | |
| 16 | - Airbrake.configure do |config| | |
| 17 | - config.api_key = '#{app.api_key}' | |
| 18 | - config.host = '#{request.host}' | |
| 19 | - config.port = #{request.port} | |
| 20 | - config.secure = config.port == 443 | |
| 21 | - end | |
| 16 | + Airbrake.configure do |config| | |
| 17 | + config.api_key = '#{app.api_key}' | |
| 18 | + config.host = '#{request.host}' | |
| 19 | + config.port = #{request.port} | |
| 20 | + config.secure = config.port == 443 | |
| 21 | + end | |
| 22 | 22 | |
| 23 | - # Set up Javascript notifications | |
| 24 | - # ------------------------------- | |
| 25 | - # | |
| 26 | - # To receive notifications for javascript errors, | |
| 27 | - # you should add <%= airbrake_javascript_notifier %> to the top of your layouts. | |
| 28 | - # | |
| 29 | - # Testing | |
| 30 | - # ------- | |
| 31 | - # | |
| 32 | - # Rails 2 - you'll need to vendor airbrake to get the rake tasks | |
| 33 | - # rake gems:unpack GEM=airbrake | |
| 34 | - # | |
| 35 | - # Run: | |
| 36 | - # rake airbrake:test | |
| 37 | - # refresh this page | |
| 23 | + # Set up Javascript notifications | |
| 24 | + # ------------------------------- | |
| 25 | + # | |
| 26 | + # To receive notifications for javascript errors, | |
| 27 | + # you should add <%= airbrake_javascript_notifier %> to the top of your layouts. | |
| 28 | + # | |
| 29 | + # Testing | |
| 30 | + # ------- | |
| 31 | + # | |
| 32 | + # Rails 2 - you'll need to vendor airbrake to get the rake tasks | |
| 33 | + # rake gems:unpack GEM=airbrake | |
| 34 | + # | |
| 35 | + # Run: | |
| 36 | + # rake airbrake:test | |
| 37 | + # refresh this page | |
| 38 | 38 | ... | ... |
app/views/apps/_fields.html.haml
| ... | ... | @@ -5,8 +5,14 @@ |
| 5 | 5 | = f.text_field :name |
| 6 | 6 | |
| 7 | 7 | %div |
| 8 | + = f.label :repository_branch | |
| 9 | + = f.text_field :repository_branch, :placeholder => "master" | |
| 10 | +%div | |
| 8 | 11 | = f.label :github_repo |
| 9 | 12 | = f.text_field :github_repo, :placeholder => "errbit/errbit from https://github.com/errbit/errbit" |
| 13 | +%div | |
| 14 | + = f.label :bitbucket_repo | |
| 15 | + = f.text_field :bitbucket_repo, :placeholder => "errbit/errbit from https://bitbucket.org/errbit/errbit" | |
| 10 | 16 | |
| 11 | 17 | %fieldset |
| 12 | 18 | %legend Notifications |
| ... | ... | @@ -46,4 +52,5 @@ |
| 46 | 52 | = f.label :resolve_errs_on_deploy, 'Resolve errs on deploy' |
| 47 | 53 | |
| 48 | 54 | = render "issue_tracker_fields", :f => f |
| 55 | += render "service_notification_fields", :f => f | |
| 49 | 56 | ... | ... |
| ... | ... | @@ -0,0 +1,25 @@ |
| 1 | +%fieldset | |
| 2 | + %legend Notification Service | |
| 3 | + = f.fields_for :notification_service do |w| | |
| 4 | + %div.notification_service.nested | |
| 5 | + %div.choose | |
| 6 | + = label_tag :type_none, :for => label_for_attr(w, 'type_notificationservice'), :class => "label_radio none" do | |
| 7 | + = w.radio_button :type, "NotificationService", 'data-section' => 'none' | |
| 8 | + (None) | |
| 9 | + - NotificationService.subclasses.each do |notification_service| | |
| 10 | + = label_tag "type_#{notification_service.label}:", :for => label_for_attr(w, "type_#{notification_service.name.downcase.gsub(':','')}"), :class => "label_radio #{notification_service.label}" do | |
| 11 | + = w.radio_button :type, notification_service.name, 'data-section' => notification_service.label | |
| 12 | + = notification_service.name[/::(.*)Service/,1].titleize | |
| 13 | + | |
| 14 | + %div.notification_params.none{:class => (w.object && !(w.object.class < NotificationService)) ? 'chosen' : nil} | |
| 15 | + - NotificationService.subclasses.each do |notification_service| | |
| 16 | + %div.notification_params{:class => (w.object.is_a?(notification_service) ? 'chosen ' : '') << notification_service.label} | |
| 17 | + - notification_service::Fields.each do |field, field_info| | |
| 18 | + = w.label field, field_info[:label] || field.to_s.titleize | |
| 19 | + - field_type = field == :password ? :password_field : :text_field | |
| 20 | + = w.send field_type, field, :placeholder => field_info[:placeholder], :value => w.object.send(field) | |
| 21 | + | |
| 22 | + .image_preloader | |
| 23 | + - (NotificationService.subclasses.map{|t| t.label } << 'none').each do |notification_service| | |
| 24 | + = image_tag "#{notification_service}_inactive.png" | |
| 25 | + = image_tag "#{notification_service}_create.png" | ... | ... |
app/views/apps/index.html.haml
| ... | ... | @@ -6,8 +6,10 @@ |
| 6 | 6 | %thead |
| 7 | 7 | %tr |
| 8 | 8 | %th Name |
| 9 | - - if any_github_repos? | |
| 10 | - %th GitHub Repo | |
| 9 | + - if any_github_repos? || any_bitbucket_repos? | |
| 10 | + %th Repository | |
| 11 | + - if any_notification_services? | |
| 12 | + %th Notification Service | |
| 11 | 13 | - if any_issue_trackers? |
| 12 | 14 | %th Tracker |
| 13 | 15 | - if any_deploys? |
| ... | ... | @@ -17,10 +19,20 @@ |
| 17 | 19 | - @apps.each do |app| |
| 18 | 20 | %tr |
| 19 | 21 | %td.name= link_to app.name, app_path(app) |
| 20 | - - if any_github_repos? | |
| 22 | + - if any_github_repos? or any_bitbucket_repos? | |
| 21 | 23 | %td.github_repo |
| 22 | 24 | - if app.github_repo? |
| 23 | 25 | = link_to(app.github_repo, app.github_url, :target => '_blank') |
| 26 | + - if app.bitbucket_repo? | |
| 27 | + = link_to(app.bitbucket_repo, app.bitbucket_url, :target => '_blank') | |
| 28 | + - if any_notification_services? | |
| 29 | + %td.notification_service | |
| 30 | + - if app.notification_service_configured? | |
| 31 | + - notification_service_img = image_tag("#{app.notification_service.label}_goto.png") | |
| 32 | + - if app.notification_service.url | |
| 33 | + = link_to( notification_service_img, app.notification_service.url, :target => "_blank" ) | |
| 34 | + - else | |
| 35 | + = notification_service_img | |
| 24 | 36 | - if any_issue_trackers? |
| 25 | 37 | %td.issue_tracker |
| 26 | 38 | - if app.issue_tracker_configured? | ... | ... |
app/views/apps/show.atom.builder
app/views/apps/show.html.haml
| ... | ... | @@ -12,10 +12,10 @@ |
| 12 | 12 | - if current_user.admin? |
| 13 | 13 | = link_to 'edit', edit_app_path(@app), :class => 'button' |
| 14 | 14 | = link_to 'destroy', app_path(@app), :method => :delete, :data => { :confirm => 'Seriously?' }, :class => 'button' |
| 15 | - - if @all_errs | |
| 16 | - = link_to 'unresolved errs', app_path(@app), :class => 'button' | |
| 17 | - - else | |
| 18 | - = link_to 'all errs', app_path(@app, :all_errs => true), :class => 'button' | |
| 15 | + - if @all_errs | |
| 16 | + = link_to 'unresolved errs', app_path(@app), :class => 'button' | |
| 17 | + - else | |
| 18 | + = link_to 'all errs', app_path(@app, :all_errs => true), :class => 'button' | |
| 19 | 19 | = link_to 'unwatch', app_watcher_path({:app_id => @app, :id => current_user.id}), :method => :delete, :class => 'button', :confirm => 'Are you sure?' |
| 20 | 20 | %h3#watchers_toggle |
| 21 | 21 | Watchers |
| ... | ... | @@ -83,7 +83,7 @@ |
| 83 | 83 | |
| 84 | 84 | - if @app.problems.any? |
| 85 | 85 | %h3.clear Errors |
| 86 | - = render 'errs/table', :errs => @problems | |
| 86 | + = render 'problems/table', :problems => @problems | |
| 87 | 87 | - else |
| 88 | 88 | %h3.clear No errs have been caught yet, make sure you setup your app |
| 89 | 89 | = render 'configuration_instructions', :app => @app | ... | ... |
app/views/errs/_issue_tracker_links.html.haml
| ... | ... | @@ -1,15 +0,0 @@ |
| 1 | -- if @app.issue_tracker_configured? || current_user.github_account? | |
| 2 | - - if @problem.issue_link.present? | |
| 3 | - %span= link_to 'go to issue', @problem.issue_link, :class => "#{@problem.issue_type}_goto goto-issue" | |
| 4 | - = link_to 'unlink issue', unlink_issue_app_err_path(@app, @problem), :method => :delete, :data => { :confirm => "Unlink err issues?" }, :class => "unlink-issue" | |
| 5 | - - elsif @problem.issue_link == "pending" | |
| 6 | - %span.disabled= link_to 'creating...', '#', :class => "#{@problem.issue_type}_inactive create-issue" | |
| 7 | - = link_to 'retry', create_issue_app_err_path(@app, @problem), :method => :post | |
| 8 | - - else | |
| 9 | - - if @app.github_repo? | |
| 10 | - - if current_user.can_create_github_issues? | |
| 11 | - %span= link_to 'create issue', create_issue_app_err_path(@app, @problem, :tracker => 'user_github'), :method => :post, :class => "github_create create-issue" | |
| 12 | - - elsif @app.issue_tracker_configured? && @app.issue_tracker.is_a?(GithubIssuesTracker) | |
| 13 | - %span= link_to 'create issue', create_issue_app_err_path(@app, @problem), :method => :post, :class => "github_create create-issue" | |
| 14 | - - if @app.issue_tracker_configured? && !@app.issue_tracker.is_a?(GithubIssuesTracker) | |
| 15 | - %span= link_to 'create issue', create_issue_app_err_path(@app, @problem), :method => :post, :class => "#{@app.issue_tracker.label}_create create-issue" |
app/views/errs/_list.atom.builder
| ... | ... | @@ -1,15 +0,0 @@ |
| 1 | -feed.updated(@problems.first.try(:created_at) || Time.now) | |
| 2 | - | |
| 3 | -for problem in @problems | |
| 4 | - notice = problem.notices.first | |
| 5 | - | |
| 6 | - feed.entry(problem, :url => app_err_url(problem.app, problem)) do |entry| | |
| 7 | - entry.title "[#{ problem.where }] #{problem.message.to_s.truncate(27)}" | |
| 8 | - entry.author do |author| | |
| 9 | - author.name "#{ problem.app.name } [#{ problem.environment }]" | |
| 10 | - end | |
| 11 | - if notice | |
| 12 | - entry.summary(notice_atom_summary(notice), :type => "html") | |
| 13 | - end | |
| 14 | - end | |
| 15 | -end |
app/views/errs/_table.html.haml
| ... | ... | @@ -1,56 +0,0 @@ |
| 1 | -- any_issue_links = errs.any?{|e| e.issue_link.present? && e.issue_link != 'pending' } | |
| 2 | -=form_tag do | |
| 3 | - %table.errs.selectable | |
| 4 | - %thead | |
| 5 | - %tr | |
| 6 | - %th= check_box_tag "toggle_problems_checkboxes" | |
| 7 | - %th= link_for_sort "App" | |
| 8 | - %th= link_for_sort "What & Where".html_safe, "message" | |
| 9 | - %th= link_for_sort "Latest", "last_notice_at" | |
| 10 | - %th= link_for_sort "Deploy", "last_deploy_at" | |
| 11 | - %th= link_for_sort "Count" | |
| 12 | - - if any_issue_links | |
| 13 | - %th Issue | |
| 14 | - %th Resolve | |
| 15 | - %tbody | |
| 16 | - - errs.each do |problem| | |
| 17 | - %tr{:class => problem.resolved? ? 'resolved' : 'unresolved'} | |
| 18 | - %td.select | |
| 19 | - = check_box_tag "problems[]", problem.id, @selected_problems.member?(problem.id.to_s) | |
| 20 | - %td.app | |
| 21 | - = link_to problem.app.name, app_path(problem.app) | |
| 22 | - - if current_page?(:controller => 'errs') | |
| 23 | - %span.environment= link_to problem.environment, errs_path(:environment => problem.environment) | |
| 24 | - - else | |
| 25 | - %span.environment= link_to problem.environment, app_path(problem.app, :environment => problem.environment) | |
| 26 | - %td.message | |
| 27 | - = link_to truncated_err_message(problem), app_err_path(problem.app, problem) | |
| 28 | - %em= problem.where | |
| 29 | - - if problem.comments_count > 0 | |
| 30 | - - comment = problem.comments.last | |
| 31 | - %br | |
| 32 | - .inline_comment | |
| 33 | - - if comment.user | |
| 34 | - %em.commenter= (Errbit::Config.user_has_username ? comment.user.username : comment.user.email).to_s << ":" | |
| 35 | - %em= truncate(comment.body, :length => 100, :separator => ' ') | |
| 36 | - %td.latest #{time_ago_in_words(last_notice_at problem)} ago | |
| 37 | - %td.deploy= problem.last_deploy_at ? problem.last_deploy_at.to_s(:micro) : 'n/a' | |
| 38 | - %td.count= link_to problem.notices_count, app_err_path(problem.app, problem) | |
| 39 | - - if any_issue_links | |
| 40 | - %td.issue_link | |
| 41 | - - if problem.app.issue_tracker_configured? && problem.issue_link.present? && problem.issue_link != 'pending' | |
| 42 | - = link_to image_tag("#{problem.issue_type}_goto.png"), problem.issue_link, :target => "_blank" | |
| 43 | - %td.resolve= link_to image_tag("thumbs-up.png"), resolve_app_err_path(problem.app, problem), :title => "Resolve", :method => :put, :data => { :confirm => err_confirm }, :class => 'resolve' if problem.unresolved? | |
| 44 | - - if errs.none? | |
| 45 | - %tr | |
| 46 | - %td{:colspan => (any_issue_links ? 8 : 7)} | |
| 47 | - %em No errs here | |
| 48 | - = paginate errs | |
| 49 | - .tab-bar | |
| 50 | - %ul | |
| 51 | - %li= submit_tag 'Merge', :id => 'merge_errs', :class => 'button', 'data-action' => merge_several_errs_path | |
| 52 | - %li= submit_tag 'Unmerge', :id => 'unmerge_errs', :class => 'button', 'data-action' => unmerge_several_errs_path | |
| 53 | - %li= submit_tag 'Resolve', :id => 'resolve_errs', :class => 'button', 'data-action' => resolve_several_errs_path | |
| 54 | - %li= submit_tag 'Unresolve', :id => 'unresolve_errs', :class => 'button', 'data-action' => unresolve_several_errs_path | |
| 55 | - %li= submit_tag 'Delete', :id => 'delete_errs', :class => 'button', 'data-action' => destroy_several_errs_path | |
| 56 | - |
app/views/errs/_tally_table.html.haml
app/views/errs/all.html.haml
app/views/errs/index.atom.builder
app/views/errs/index.html.haml
| ... | ... | @@ -1,6 +0,0 @@ |
| 1 | -- content_for :title, 'Unresolved Errors' | |
| 2 | -- content_for :head do | |
| 3 | - = auto_discovery_link_tag :atom, errs_path(User.token_authentication_key => current_user.authentication_token, :format => "atom"), :title => "Errbit notices at #{request.host}" | |
| 4 | -- content_for :action_bar do | |
| 5 | - = link_to 'show resolved', all_errs_path, :class => 'button' | |
| 6 | -= render 'table', :errs => @problems |
app/views/errs/show.html.haml
| ... | ... | @@ -1,80 +0,0 @@ |
| 1 | -- content_for :page_title, @problem.message | |
| 2 | -- content_for :title_css_class, 'err_show' | |
| 3 | -- content_for :title, @problem.error_class || truncate(@problem.message, :length => 32) | |
| 4 | -- content_for :meta do | |
| 5 | - %strong App: | |
| 6 | - = link_to @app.name, app_path(@app) | |
| 7 | - %strong Where: | |
| 8 | - = @problem.where | |
| 9 | - %br | |
| 10 | - %strong Environment: | |
| 11 | - = @problem.environment | |
| 12 | - %strong Last Notice: | |
| 13 | - = last_notice_at(@problem).to_s(:precise) | |
| 14 | -- content_for :action_bar do | |
| 15 | - - if @problem.unresolved? | |
| 16 | - %span= link_to 'resolve', resolve_app_err_path(@app, @problem), :method => :put, :data => { :confirm => err_confirm }, :class => 'resolve' | |
| 17 | - - if current_user.authentication_token | |
| 18 | - %span= link_to 'iCal', app_err_path(:app_id => @app.id, :id => @problem.id, :format => "ics", :auth_token => current_user.authentication_token), :class => "calendar_link" | |
| 19 | - %span>= link_to 'up', (request.env['HTTP_REFERER'] ? :back : app_errs_path(@app)), :class => 'up' | |
| 20 | - %br | |
| 21 | - = render "issue_tracker_links" | |
| 22 | - | |
| 23 | -- if Errbit::Config.allow_comments_with_issue_tracker || !@app.issue_tracker_configured? || @problem.comments.any? | |
| 24 | - - content_for :comments do | |
| 25 | - %h3 Comments | |
| 26 | - - @problem.comments.each do |comment| | |
| 27 | - .window | |
| 28 | - %table.comment | |
| 29 | - %tr | |
| 30 | - %th | |
| 31 | - %span= link_to '✘'.html_safe, app_err_comment_path(@app, @problem, comment), :method => :delete, :data => { :confirm => "Are sure you don't need this comment?" }, :class => "destroy-comment" | |
| 32 | - = time_ago_in_words(comment.created_at, true) << " ago by " | |
| 33 | - = link_to comment.user.email, user_path(comment.user) | |
| 34 | - %tr | |
| 35 | - %td= comment.body.gsub("\n", "<br>").html_safe | |
| 36 | - - if Errbit::Config.allow_comments_with_issue_tracker || !@app.issue_tracker_configured? | |
| 37 | - = form_for @comment, :url => app_err_comments_path(@app, @problem) do |comment_form| | |
| 38 | - %p Add a comment | |
| 39 | - = comment_form.text_area :body, :style => "width: 420px; height: 80px;" | |
| 40 | - = comment_form.submit "Save Comment" | |
| 41 | - | |
| 42 | -%h4= @notice.try(:message) | |
| 43 | - | |
| 44 | -= paginate @notices, :param_name => :notice, :theme => :notices | |
| 45 | - | |
| 46 | -.tab-bar | |
| 47 | - %ul | |
| 48 | - %li= link_to 'Summary', '#summary', :rel => 'summary', :class => 'button' | |
| 49 | - %li= link_to 'Backtrace', '#backtrace', :rel => 'backtrace', :class => 'button' | |
| 50 | - - if @notice && @notice.user_attributes.present? | |
| 51 | - %li= link_to 'User Details', '#user_attributes', :rel => 'user_attributes', :class => 'button' | |
| 52 | - %li= link_to 'Environment', '#environment', :rel => 'environment', :class => 'button' | |
| 53 | - %li= link_to 'Parameters', '#params', :rel => 'params', :class => 'button' | |
| 54 | - %li= link_to 'Session', '#session', :rel => 'session', :class => 'button' | |
| 55 | - | |
| 56 | -- if @notice | |
| 57 | - #summary | |
| 58 | - %h3 Summary | |
| 59 | - = render 'notices/summary', :notice => @notice, :problem => @problem | |
| 60 | - | |
| 61 | - #backtrace | |
| 62 | - %h3 Backtrace | |
| 63 | - = render 'notices/backtrace', :lines => @notice.backtrace | |
| 64 | - | |
| 65 | - - if @notice.user_attributes.present? | |
| 66 | - #user_attributes | |
| 67 | - %h3 User Details | |
| 68 | - = render 'notices/user_attributes', :user => @notice.user_attributes | |
| 69 | - | |
| 70 | - #environment | |
| 71 | - %h3 Environment | |
| 72 | - = render 'notices/environment', :notice => @notice | |
| 73 | - | |
| 74 | - #params | |
| 75 | - %h3 Parameters | |
| 76 | - = render 'notices/params', :notice => @notice | |
| 77 | - | |
| 78 | - #session | |
| 79 | - %h3 Session | |
| 80 | - = render 'notices/session', :notice => @notice |
app/views/errs/show.ics.haml
| ... | ... | @@ -1 +0,0 @@ |
| 1 | -= generate_problem_ical(@problem.notices.order_by(:created_at.asc)) |
| ... | ... | @@ -0,0 +1,58 @@ |
| 1 | +[[<%= app_problem_url problem.app, problem %>| [See this exception on Errbit]]] | |
| 2 | + | |
| 3 | +---- | |
| 4 | + | |
| 5 | +<% if notice = problem.notices.first %> | |
| 6 | + <%= notice.message %> | |
| 7 | + | |
| 8 | +---- | |
| 9 | + | |
| 10 | + == Summary == | |
| 11 | + <% if notice.request['url'].present? %> | |
| 12 | + === URL === | |
| 13 | + [[<%= notice.request['url'] %>]] | |
| 14 | + <% end %> | |
| 15 | + | |
| 16 | +---- | |
| 17 | + | |
| 18 | + === Where === | |
| 19 | + <%= notice.where %> | |
| 20 | + | |
| 21 | +---- | |
| 22 | + | |
| 23 | + === Occured === | |
| 24 | + <%= notice.created_at.to_s(:micro) %> | |
| 25 | + | |
| 26 | +---- | |
| 27 | + | |
| 28 | + === Similar === | |
| 29 | + <%= (notice.problem.notices_count - 1).to_s %> | |
| 30 | + | |
| 31 | +---- | |
| 32 | + | |
| 33 | + == Params == | |
| 34 | +{{{ | |
| 35 | +<%= pretty_hash(notice.params) %> | |
| 36 | +}}} | |
| 37 | + | |
| 38 | +---- | |
| 39 | + | |
| 40 | + == Session == | |
| 41 | +{{{ | |
| 42 | +<%= pretty_hash(notice.session) %> | |
| 43 | +}}} | |
| 44 | + | |
| 45 | +---- | |
| 46 | + | |
| 47 | + == Backtrace == | |
| 48 | + <% notice.backtrace_lines.each do |line| %>| <%= line['number'] %>: | <%= line['file'].to_s.sub(/^\[PROJECT_ROOT\]/, '') %> -> **<%= line['method'] %>** | | |
| 49 | + <% end %> | |
| 50 | + | |
| 51 | +---- | |
| 52 | + | |
| 53 | + == Environment == | |
| 54 | + <% for key, val in notice.env_vars %> | |
| 55 | + | <%= key %>: | <%= val %> | | |
| 56 | + <% end %> | |
| 57 | +<% end %> | |
| 58 | + | ... | ... |
app/views/issue_trackers/fogbugz_body.txt.erb
| 1 | -"See this exception on Errbit": <%= app_err_url(problem.app, problem) %> | |
| 1 | +"See this exception on Errbit": <%= app_problem_url(problem.app, problem) %> | |
| 2 | 2 | <% if notice = problem.notices.first %> |
| 3 | 3 | <%= notice.message %> |
| 4 | 4 | |
| ... | ... | @@ -19,8 +19,8 @@ |
| 19 | 19 | <%= pretty_hash(notice.session) %> |
| 20 | 20 | |
| 21 | 21 | Backtrace |
| 22 | - <% for line in notice.backtrace %> | |
| 23 | - <%= line['number'] %>: <%= line['file'].to_s.sub(/^\[PROJECT_ROOT\]/, '') %> | |
| 22 | + <% notice.backtrace_lines.each do |line| %> | |
| 23 | + <%= line.number %>: <%= line.file_relative %> | |
| 24 | 24 | <% end %> |
| 25 | 25 | |
| 26 | 26 | Environment | ... | ... |
app/views/issue_trackers/github_issues_body.txt.erb
| 1 | -[See this exception on Errbit](<%= app_err_url problem.app, problem %> "See this exception on Errbit") | |
| 1 | +[See this exception on Errbit](<%= app_problem_url problem.app, problem %> "See this exception on Errbit") | |
| 2 | 2 | <% if notice = problem.notices.first %> |
| 3 | 3 | # <%= notice.message %> # |
| 4 | 4 | ## Summary ## |
| ... | ... | @@ -27,7 +27,7 @@ |
| 27 | 27 | |
| 28 | 28 | ## Backtrace ## |
| 29 | 29 | ``` |
| 30 | -<% for line in notice.backtrace %><%= line['number'] %>: <%= line['file'].to_s.sub(/^\[PROJECT_ROOT\]/, '') %> -> **<%= line['method'] %>** | |
| 30 | +<% notice.backtrace_lines.each do |line| %><%= line.number %>: <%= line.file_relative %> -> **<%= line.method %>** | |
| 31 | 31 | <% end %> |
| 32 | 32 | ``` |
| 33 | 33 | ... | ... |
app/views/issue_trackers/lighthouseapp_body.txt.erb
| 1 | -[See this exception on Errbit](<%= app_err_url problem.app, problem %> "See this exception on Errbit") | |
| 1 | +[See this exception on Errbit](<%= app_problem_url problem.app, problem %> "See this exception on Errbit") | |
| 2 | 2 | <% if notice = problem.notices.first %> |
| 3 | 3 | # <%= notice.message %> # |
| 4 | 4 | ## Summary ## |
| ... | ... | @@ -23,7 +23,7 @@ |
| 23 | 23 | |
| 24 | 24 | ## Backtrace ## |
| 25 | 25 | <code> |
| 26 | - <% for line in notice.backtrace %><%= line['number'] %>: <%= line['file'].to_s.sub(/^\[PROJECT_ROOT\]/, '') %> -> **<%= line['method'] %>** | |
| 26 | + <% notice.backtrace_lines.each do |line| %><%= line.number %>: <%= line.file_relative %> -> **<%= line.method %>** | |
| 27 | 27 | <% end %> |
| 28 | 28 | </code> |
| 29 | 29 | ... | ... |
app/views/issue_trackers/pivotal_body.txt.erb
| 1 | -See this exception on Errbit: <%= app_err_url problem.app, problem %> | |
| 1 | +See this exception on Errbit: <%= app_problem_url problem.app, problem %> | |
| 2 | 2 | <% if notice = problem.notices.first %> |
| 3 | 3 | <% if notice.request['url'].present? %>URL: <%= notice.request['url'] %><% end %> |
| 4 | 4 | Where: <%= notice.where %> |
| ... | ... | @@ -12,6 +12,6 @@ See this exception on Errbit: <%= app_err_url problem.app, problem %> |
| 12 | 12 | <%= pretty_hash notice.session %> |
| 13 | 13 | |
| 14 | 14 | Backtrace: |
| 15 | - <%= notice.backtrace[0..4].map { |line| "#{line['number']}: #{line['file'].to_s.sub(/^\[PROJECT_ROOT\]/, '')} -> *#{line['method']}*" }.join "\n" %> | |
| 15 | + <%= notice.backtrace_lines[0..4].map { |line| "#{line.number}: #{line.file_relative} -> *#{line.method}*" }.join "\n" %> | |
| 16 | 16 | <% end %> |
| 17 | 17 | ... | ... |
app/views/issue_trackers/textile_body.txt.erb
| 1 | 1 | <% if notice = problem.notices.first %> |
| 2 | 2 | h1. <%= notice.message %> |
| 3 | 3 | |
| 4 | -h3. "See this exception on Errbit":<%= app_err_url problem.app, problem %> | |
| 4 | +h3. "See this exception on Errbit":<%= app_problem_url problem.app, problem %> | |
| 5 | 5 | |
| 6 | 6 | h2. Summary |
| 7 | 7 | <% if notice.request['url'].present? %> |
| ... | ... | @@ -32,7 +32,7 @@ h2. Session |
| 32 | 32 | h2. Backtrace |
| 33 | 33 | |
| 34 | 34 | | Line | File | Method | |
| 35 | -<% for line in notice.backtrace %>| <%= line['number'] %> | <%= line['file'].to_s.sub(/^\[PROJECT_ROOT\]/, '') %> | *<%= line['method'] %>* | | |
| 35 | +<% notice.backtrace_lines.each do |line| %>| <%= line.number %> | <%= line.file_relative %> | *<%= line.method %>* | | |
| 36 | 36 | <% end %> |
| 37 | 37 | |
| 38 | 38 | h2. Environment | ... | ... |
app/views/kaminari/_first_page.html.haml
| ... | ... | @@ -2,7 +2,7 @@ |
| 2 | 2 | -# available local variables |
| 3 | 3 | -# url: url to the first page |
| 4 | 4 | -# current_page: a page object for the currently displayed page |
| 5 | --# num_pages: total number of pages | |
| 5 | +-# total_pages: total number of pages | |
| 6 | 6 | -# per_page: number of items to fetch per page |
| 7 | 7 | -# remote: data-remote |
| 8 | 8 | %span.first | ... | ... |
app/views/kaminari/_gap.html.haml
| 1 | 1 | -# Non-link tag that stands for skipped pages... |
| 2 | 2 | -# available local variables |
| 3 | 3 | -# current_page: a page object for the currently displayed page |
| 4 | --# num_pages: total number of pages | |
| 4 | +-# total_pages: total number of pages | |
| 5 | 5 | -# per_page: number of items to fetch per page |
| 6 | 6 | -# remote: data-remote |
| 7 | 7 | %span.page.gap | ... | ... |
app/views/kaminari/_last_page.html.haml
| ... | ... | @@ -2,7 +2,7 @@ |
| 2 | 2 | -# available local variables |
| 3 | 3 | -# url: url to the last page |
| 4 | 4 | -# current_page: a page object for the currently displayed page |
| 5 | --# num_pages: total number of pages | |
| 5 | +-# total_pages: total number of pages | |
| 6 | 6 | -# per_page: number of items to fetch per page |
| 7 | 7 | -# remote: data-remote |
| 8 | 8 | %span.last | ... | ... |
app/views/kaminari/_next_page.html.haml
| ... | ... | @@ -2,7 +2,7 @@ |
| 2 | 2 | -# available local variables |
| 3 | 3 | -# url: url to the next page |
| 4 | 4 | -# current_page: a page object for the currently displayed page |
| 5 | --# num_pages: total number of pages | |
| 5 | +-# total_pages: total number of pages | |
| 6 | 6 | -# per_page: number of items to fetch per page |
| 7 | 7 | -# remote: data-remote |
| 8 | 8 | %span.next | ... | ... |
app/views/kaminari/_page.html.haml
| ... | ... | @@ -3,7 +3,7 @@ |
| 3 | 3 | -# page: a page object for "this" page |
| 4 | 4 | -# url: url to this page |
| 5 | 5 | -# current_page: a page object for the currently displayed page |
| 6 | --# num_pages: total number of pages | |
| 6 | +-# total_pages: total number of pages | |
| 7 | 7 | -# per_page: number of items to fetch per page |
| 8 | 8 | -# remote: data-remote |
| 9 | 9 | %span{:class => "page#{' current' if page.current?}"} | ... | ... |
app/views/kaminari/_paginator.html.haml
| 1 | 1 | -# The container tag |
| 2 | 2 | -# available local variables |
| 3 | 3 | -# current_page: a page object for the currently displayed page |
| 4 | --# num_pages: total number of pages | |
| 4 | +-# total_pages: total number of pages | |
| 5 | 5 | -# per_page: number of items to fetch per page |
| 6 | 6 | -# remote: data-remote |
| 7 | 7 | -# paginator: the paginator that renders the pagination tags inside | ... | ... |
app/views/kaminari/_prev_page.html.haml
| ... | ... | @@ -2,7 +2,7 @@ |
| 2 | 2 | -# available local variables |
| 3 | 3 | -# url: url to the previous page |
| 4 | 4 | -# current_page: a page object for the currently displayed page |
| 5 | --# num_pages: total number of pages | |
| 5 | +-# total_pages: total number of pages | |
| 6 | 6 | -# per_page: number of items to fetch per page |
| 7 | 7 | -# remote: data-remote |
| 8 | 8 | %span.prev | ... | ... |
app/views/kaminari/notices/_first_page.html.haml
| ... | ... | @@ -2,7 +2,7 @@ |
| 2 | 2 | -# available local variables |
| 3 | 3 | -# url: url to the first page |
| 4 | 4 | -# current_page: a page object for the currently displayed page |
| 5 | --# num_pages: total number of pages | |
| 5 | +-# total_pages: total number of pages | |
| 6 | 6 | -# per_page: number of items to fetch per page |
| 7 | 7 | -# remote: data-remote |
| 8 | 8 | %span.first | ... | ... |
app/views/kaminari/notices/_gap.html.haml
| 1 | 1 | -# Non-link tag that stands for skipped pages... |
| 2 | 2 | -# available local variables |
| 3 | 3 | -# current_page: a page object for the currently displayed page |
| 4 | --# num_pages: total number of pages | |
| 4 | +-# total_pages: total number of pages | |
| 5 | 5 | -# per_page: number of items to fetch per page |
| 6 | 6 | -# remote: data-remote |
| 7 | 7 | %span.page.gap | ... | ... |
app/views/kaminari/notices/_last_page.html.haml
| ... | ... | @@ -2,7 +2,7 @@ |
| 2 | 2 | -# available local variables |
| 3 | 3 | -# url: url to the last page |
| 4 | 4 | -# current_page: a page object for the currently displayed page |
| 5 | --# num_pages: total number of pages | |
| 5 | +-# total_pages: total number of pages | |
| 6 | 6 | -# per_page: number of items to fetch per page |
| 7 | 7 | -# remote: data-remote |
| 8 | 8 | %span.last | ... | ... |
app/views/kaminari/notices/_page.html.haml
| ... | ... | @@ -3,7 +3,7 @@ |
| 3 | 3 | -# page: a page object for "this" page |
| 4 | 4 | -# url: url to this page |
| 5 | 5 | -# current_page: a page object for the currently displayed page |
| 6 | --# num_pages: total number of pages | |
| 6 | +-# total_pages: total number of pages | |
| 7 | 7 | -# per_page: number of items to fetch per page |
| 8 | 8 | -# remote: data-remote |
| 9 | 9 | %span{:class => "page#{' current' if page.current?}"} | ... | ... |
app/views/kaminari/notices/_paginator.html.haml
| 1 | 1 | -# The container tag |
| 2 | 2 | -# available local variables |
| 3 | 3 | -# current_page: a page object for the currently displayed page |
| 4 | --# num_pages: total number of pages | |
| 4 | +-# total_pages: total number of pages | |
| 5 | 5 | -# per_page: number of items to fetch per page |
| 6 | 6 | -# remote: data-remote |
| 7 | 7 | -# paginator: the paginator that renders the pagination tags inside |
| ... | ... | @@ -11,4 +11,4 @@ |
| 11 | 11 | | |
| 12 | 12 | = prev_page_tag |
| 13 | 13 | .notice-pagination-loader= image_tag 'loader.gif' |
| 14 | -viewing occurrence #{page_count_from_end(current_page, num_pages)} of #{num_pages} | |
| 14 | +viewing occurrence #{page_count_from_end(current_page, total_pages)} of #{total_pages} | ... | ... |
app/views/layouts/application.html.haml
| ... | ... | @@ -18,7 +18,7 @@ |
| 18 | 18 | = render 'shared/navigation' if current_user |
| 19 | 19 | = render 'shared/session' |
| 20 | 20 | #content-wrapper |
| 21 | - #content-title{ :class => (yield :title_css_class).to_s } | |
| 21 | + #content-title{ :class => (yield :title_css_class).to_s, :style => (yield :title_style) } | |
| 22 | 22 | %h1= yield :title |
| 23 | 23 | %span.meta= yield :meta |
| 24 | 24 | - if (action_bar = yield(:action_bar)).present? |
| ... | ... | @@ -33,3 +33,4 @@ |
| 33 | 33 | #footer= "Powered by #{link_to 'Errbit', 'http://github.com/errbit/errbit', :target => '_blank'}: the open source error catcher.".html_safe |
| 34 | 34 | = yield :scripts |
| 35 | 35 | |
| 36 | += yield :before_title | |
| 36 | 37 | \ No newline at end of file | ... | ... |
app/views/mailer/err_notification.html.haml
| ... | ... | @@ -14,7 +14,7 @@ |
| 14 | 14 | %br |
| 15 | 15 | This err has occurred #{pluralize @notice.problem.notices_count, 'time'}. |
| 16 | 16 | %p |
| 17 | - = link_to("Click here to view the error on Errbit", app_err_url(@app, @notice.problem), :class => "bold") << "." | |
| 17 | + = link_to("Click here to view the error on Errbit", app_problem_url(@app, @notice.problem), :class => "bold") << "." | |
| 18 | 18 | %tr |
| 19 | 19 | %td.section |
| 20 | 20 | %table(cellpadding="0" cellspacing="0" border="0" align="left") |
| ... | ... | @@ -27,16 +27,15 @@ |
| 27 | 27 | %p.heading WHERE: |
| 28 | 28 | %p.monospace |
| 29 | 29 | = @notice.where |
| 30 | - - if (app_backtrace = @notice.app_backtrace).any? | |
| 31 | - - app_backtrace.map {|l| "#{l['file']}:#{l['number']}" }.each do |line| | |
| 32 | - %p.backtrace= line | |
| 30 | + - @notice.in_app_backtrace_lines.each do |line| | |
| 31 | + %p.backtrace= line | |
| 33 | 32 | %br |
| 34 | 33 | %p.heading URL: |
| 35 | 34 | %p.monospace |
| 36 | 35 | - if @notice.request['url'].present? |
| 37 | 36 | = link_to @notice.request['url'], @notice.request['url'] |
| 38 | 37 | %p.heading BACKTRACE: |
| 39 | - - @notice.backtrace.map {|l| l ? "#{l['file']}:#{l['number']}" : nil }.compact.each do |line| | |
| 38 | + - @notice.backtrace_lines.each do |line| | |
| 40 | 39 | %p.backtrace= line |
| 41 | 40 | %br |
| 42 | 41 | ... | ... |
app/views/mailer/err_notification.text.erb
| ... | ... | @@ -2,7 +2,7 @@ An err has just occurred in <%= @notice.environment_name %>: <%= raw(@notice.mes |
| 2 | 2 | |
| 3 | 3 | This err has occurred <%= pluralize @notice.problem.notices_count, 'time' %>. You should really look into it here: |
| 4 | 4 | |
| 5 | - <%= app_err_url(@app, @notice.problem) %> | |
| 5 | + <%= app_problem_url(@app, @notice.problem) %> | |
| 6 | 6 | |
| 7 | 7 | |
| 8 | 8 | ERROR MESSAGE: |
| ... | ... | @@ -14,7 +14,7 @@ WHERE: |
| 14 | 14 | |
| 15 | 15 | <%= @notice.where %> |
| 16 | 16 | |
| 17 | -<% @notice.app_backtrace.map {|l| "#{l['file']}:#{l['number']}" }.each do |line| %> | |
| 17 | +<% @notice.in_app_backtrace_lines.each do |line| %> | |
| 18 | 18 | <%= line %> |
| 19 | 19 | <% end %> |
| 20 | 20 | |
| ... | ... | @@ -26,7 +26,7 @@ URL: |
| 26 | 26 | |
| 27 | 27 | BACKTRACE: |
| 28 | 28 | |
| 29 | -<% @notice.backtrace.map {|l| l ? "#{l['file']}:#{l['number']}" : nil }.compact.each do |line| %> | |
| 29 | +<% @notice.backtrace_lines.each do |line| %> | |
| 30 | 30 | <%= line %> |
| 31 | 31 | <% end %> |
| 32 | 32 | ... | ... |