Commit ac8d29a562cb5f44832106643398b9b4013f7da9

Authored by Stephen Crosby
2 parents ea168ac7 8dbfda45
Exists in master and in 1 other branch production

Merge pull request #612 from errbit/features/extract_issue_tracker

Extract all Issue Tracker in gems
Showing 68 changed files with 802 additions and 1416 deletions   Show diff stats
@@ -25,31 +25,18 @@ gem 'rails_autolink' @@ -25,31 +25,18 @@ gem 'rails_autolink'
25 # Please don't update hoptoad_notifier to airbrake. 25 # Please don't update hoptoad_notifier to airbrake.
26 # It's for internal use only, and we monkeypatch certain methods 26 # It's for internal use only, and we monkeypatch certain methods
27 gem 'hoptoad_notifier', "~> 2.4" 27 gem 'hoptoad_notifier', "~> 2.4"
28 -  
29 -  
30 -# Remove / comment out any of the gems below if you want to disable  
31 -# a given issue tracker, notification service, or authentication.  
32 -  
33 -# Issue Trackers  
34 -# ---------------------------------------  
35 -# Lighthouse  
36 -gem 'lighthouse-api'  
37 -# Redmine  
38 -gem 'oruen_redmine_client', :require => 'redmine_client'  
39 -# Pivotal Tracker  
40 -gem 'pivotal-tracker'  
41 -# Fogbugz  
42 -gem 'ruby-fogbugz', :require => 'fogbugz'  
43 -# Github Issues  
44 -gem 'octokit', '~> 2.0'  
45 -# Gitlab  
46 -gem 'gitlab', '~> 3.0.0'  
47 -  
48 -# Bitbucket Issues  
49 -gem 'bitbucket_rest_api', :require => false  
50 -  
51 -# Jira  
52 -gem 'jira-ruby', :require => 'jira' 28 +gem 'draper', :require => false
  29 +
  30 +gem 'errbit_plugin'
  31 +gem 'errbit_bitbucket_plugin'
  32 +gem 'errbit_fogbugz_plugin'
  33 +gem 'errbit_github_plugin'
  34 +gem 'errbit_gitlab_plugin'
  35 +gem 'errbit_jira_plugin'
  36 +gem 'errbit_lighthouse_plugin'
  37 +gem 'errbit_pivotal_plugin'
  38 +gem 'errbit_redmine_plugin'
  39 +gem 'errbit_unfuddle_plugin'
53 40
54 # Notification services 41 # Notification services
55 # --------------------------------------- 42 # ---------------------------------------
@@ -101,6 +88,7 @@ end @@ -101,6 +88,7 @@ end
101 88
102 group :test do 89 group :test do
103 gem 'capybara' 90 gem 'capybara'
  91 + gem 'poltergeist'
104 gem 'launchy' 92 gem 'launchy'
105 gem 'database_cleaner' 93 gem 'database_cleaner'
106 gem 'email_spec' 94 gem 'email_spec'
@@ -32,7 +32,7 @@ GEM @@ -32,7 +32,7 @@ GEM
32 activesupport (3.2.21) 32 activesupport (3.2.21)
33 i18n (~> 0.6, >= 0.6.4) 33 i18n (~> 0.6, >= 0.6.4)
34 multi_json (~> 1.0) 34 multi_json (~> 1.0)
35 - addressable (2.3.5) 35 + addressable (2.3.6)
36 airbrake (3.1.14) 36 airbrake (3.1.14)
37 builder 37 builder
38 json 38 json
@@ -66,6 +66,7 @@ GEM @@ -66,6 +66,7 @@ GEM
66 rack (>= 1.0.0) 66 rack (>= 1.0.0)
67 rack-test (>= 0.5.4) 67 rack-test (>= 0.5.4)
68 xpath (~> 2.0) 68 xpath (~> 2.0)
  69 + cliver (0.3.2)
69 coderay (1.0.9) 70 coderay (1.0.9)
70 coveralls (0.7.0) 71 coveralls (0.7.0)
71 multi_json (~> 1.3) 72 multi_json (~> 1.3)
@@ -73,8 +74,8 @@ GEM @@ -73,8 +74,8 @@ GEM
73 simplecov (>= 0.7) 74 simplecov (>= 0.7)
74 term-ansicolor 75 term-ansicolor
75 thor 76 thor
76 - crack (0.4.1)  
77 - safe_yaml (~> 0.9.0) 77 + crack (0.4.2)
  78 + safe_yaml (~> 1.0.0)
78 css_parser (1.3.5) 79 css_parser (1.3.5)
79 addressable 80 addressable
80 database_cleaner (1.2.0) 81 database_cleaner (1.2.0)
@@ -88,16 +89,49 @@ GEM @@ -88,16 +89,49 @@ GEM
88 warden (~> 1.2.3) 89 warden (~> 1.2.3)
89 diff-lcs (1.2.4) 90 diff-lcs (1.2.4)
90 dotenv (0.9.0) 91 dotenv (0.9.0)
  92 + draper (1.3.0)
  93 + actionpack (>= 3.0)
  94 + activemodel (>= 3.0)
  95 + activesupport (>= 3.0)
  96 + request_store (~> 1.0.3)
91 email_spec (1.5.0) 97 email_spec (1.5.0)
92 launchy (~> 2.1) 98 launchy (~> 2.1)
93 mail (~> 2.2) 99 mail (~> 2.2)
  100 + errbit_bitbucket_plugin (0.1.0)
  101 + bitbucket_rest_api
  102 + errbit_plugin
  103 + faraday (~> 0.8.1)
  104 + errbit_fogbugz_plugin (0.1.0)
  105 + errbit_plugin (~> 0.4, >= 0.4.0)
  106 + ruby-fogbugz (~> 0.1, >= 0.1)
  107 + errbit_github_plugin (0.1.0)
  108 + errbit_plugin
  109 + octokit
  110 + errbit_gitlab_plugin (0.1.0)
  111 + errbit_plugin (~> 0.4, >= 0.4.0)
  112 + gitlab (~> 3.0.0, >= 3.0.0)
  113 + errbit_jira_plugin (0.2.0)
  114 + errbit_plugin
  115 + jira-ruby
  116 + errbit_lighthouse_plugin (0.1.0)
  117 + errbit_plugin (~> 0)
  118 + lighthouse-api (~> 2)
  119 + errbit_pivotal_plugin (0.2.0)
  120 + errbit_plugin (~> 0.4, >= 0.4.0)
  121 + pivotal-tracker (~> 0.5, >= 0.5.0)
  122 + errbit_plugin (0.4.0)
  123 + errbit_redmine_plugin (0.2.0)
  124 + errbit_plugin (~> 0)
  125 + oruen_redmine_client (~> 0)
  126 + errbit_unfuddle_plugin (0.1.0)
  127 + errbit_plugin (~> 0.4, >= 0.4.0)
94 erubis (2.7.0) 128 erubis (2.7.0)
95 execjs (2.0.2) 129 execjs (2.0.2)
96 - fabrication (2.8.1) 130 + fabrication (2.9.0)
97 faraday (0.8.9) 131 faraday (0.8.9)
98 multipart-post (~> 1.2.0) 132 multipart-post (~> 1.2.0)
99 - faraday_middleware (0.9.0)  
100 - faraday (>= 0.7.4, < 0.9) 133 + faraday_middleware (0.9.1)
  134 + faraday (>= 0.7.4, < 0.10)
101 flowdock (0.3.1) 135 flowdock (0.3.1)
102 httparty (~> 0.7) 136 httparty (~> 0.7)
103 multi_json 137 multi_json
@@ -111,7 +145,7 @@ GEM @@ -111,7 +145,7 @@ GEM
111 happymapper (0.4.1) 145 happymapper (0.4.1)
112 libxml-ruby (~> 2.0) 146 libxml-ruby (~> 2.0)
113 hashie (2.0.5) 147 hashie (2.0.5)
114 - highline (1.6.19) 148 + highline (1.6.20)
115 hike (1.2.3) 149 hike (1.2.3)
116 hipchat (0.12.0) 150 hipchat (0.12.0)
117 httparty 151 httparty
@@ -127,10 +161,9 @@ GEM @@ -127,10 +161,9 @@ GEM
127 multi_xml (>= 0.5.2) 161 multi_xml (>= 0.5.2)
128 httpauth (0.2.0) 162 httpauth (0.2.0)
129 i18n (0.6.11) 163 i18n (0.6.11)
130 - jira-ruby (0.1.2) 164 + jira-ruby (0.1.10)
131 activesupport 165 activesupport
132 oauth 166 oauth
133 - railties  
134 journey (1.0.4) 167 journey (1.0.4)
135 jquery-rails (2.1.4) 168 jquery-rails (2.1.4)
136 railties (>= 3.0, < 5.0) 169 railties (>= 3.0, < 5.0)
@@ -158,7 +191,7 @@ GEM @@ -158,7 +191,7 @@ GEM
158 railties 191 railties
159 method_source (0.8.2) 192 method_source (0.8.2)
160 mime-types (1.25.1) 193 mime-types (1.25.1)
161 - mini_portile (0.5.3) 194 + mini_portile (0.6.1)
162 mongoid (3.1.5) 195 mongoid (3.1.5)
163 activemodel (~> 3.2) 196 activemodel (~> 3.2)
164 moped (~> 1.4) 197 moped (~> 1.4)
@@ -184,9 +217,9 @@ GEM @@ -184,9 +217,9 @@ GEM
184 net-ssh (2.7.0) 217 net-ssh (2.7.0)
185 net-ssh-gateway (1.2.0) 218 net-ssh-gateway (1.2.0)
186 net-ssh (>= 2.6.5) 219 net-ssh (>= 2.6.5)
187 - nokogiri (1.6.1)  
188 - mini_portile (~> 0.5.0)  
189 - nokogiri-happymapper (0.5.8) 220 + nokogiri (1.6.4.1)
  221 + mini_portile (~> 0.6.0)
  222 + nokogiri-happymapper (0.5.9)
190 nokogiri (~> 1.5) 223 nokogiri (~> 1.5)
191 oauth (0.4.7) 224 oauth (0.4.7)
192 oauth2 (0.8.1) 225 oauth2 (0.8.1)
@@ -195,8 +228,8 @@ GEM @@ -195,8 +228,8 @@ GEM
195 jwt (~> 0.1.4) 228 jwt (~> 0.1.4)
196 multi_json (~> 1.0) 229 multi_json (~> 1.0)
197 rack (~> 1.2) 230 rack (~> 1.2)
198 - octokit (2.7.1)  
199 - sawyer (~> 0.5.2) 231 + octokit (3.3.1)
  232 + sawyer (~> 0.5.3)
200 omniauth (1.1.4) 233 omniauth (1.1.4)
201 hashie (>= 1.2, < 3) 234 hashie (>= 1.2, < 3)
202 rack 235 rack
@@ -222,6 +255,11 @@ GEM @@ -222,6 +255,11 @@ GEM
222 rest-client (~> 1.6.0) 255 rest-client (~> 1.6.0)
223 pjax_rails (0.3.4) 256 pjax_rails (0.3.4)
224 jquery-rails 257 jquery-rails
  258 + poltergeist (1.5.1)
  259 + capybara (~> 2.1)
  260 + cliver (~> 0.3.1)
  261 + multi_json (~> 1.0)
  262 + websocket-driver (>= 0.2.0)
225 polyglot (0.3.5) 263 polyglot (0.3.5)
226 premailer (1.7.3) 264 premailer (1.7.3)
227 css_parser (>= 1.1.9) 265 css_parser (>= 1.1.9)
@@ -268,8 +306,10 @@ GEM @@ -268,8 +306,10 @@ GEM
268 rdoc (3.12.2) 306 rdoc (3.12.2)
269 json (~> 1.4) 307 json (~> 1.4)
270 ref (1.0.5) 308 ref (1.0.5)
271 - rest-client (1.6.7)  
272 - mime-types (>= 1.16) 309 + request_store (1.0.6)
  310 + rest-client (1.6.8)
  311 + mime-types (~> 1.16)
  312 + rdoc (>= 2.4.2)
273 ri_cal (0.8.8) 313 ri_cal (0.8.8)
274 rspec (2.14.1) 314 rspec (2.14.1)
275 rspec-core (~> 2.14.0) 315 rspec-core (~> 2.14.0)
@@ -291,11 +331,11 @@ GEM @@ -291,11 +331,11 @@ GEM
291 rushover (0.3.0) 331 rushover (0.3.0)
292 json 332 json
293 rest-client 333 rest-client
294 - safe_yaml (0.9.7)  
295 - sawyer (0.5.3) 334 + safe_yaml (1.0.4)
  335 + sawyer (0.5.5)
296 addressable (~> 2.3.5) 336 addressable (~> 2.3.5)
297 faraday (~> 0.8, < 0.10) 337 faraday (~> 0.8, < 0.10)
298 - simple_oauth (0.2.0) 338 + simple_oauth (0.3.0)
299 simplecov (0.7.1) 339 simplecov (0.7.1)
300 multi_json (~> 1.0) 340 multi_json (~> 1.0)
301 simplecov-html (~> 0.7.1) 341 simplecov-html (~> 0.7.1)
@@ -330,18 +370,19 @@ GEM @@ -330,18 +370,19 @@ GEM
330 tzinfo (0.3.41) 370 tzinfo (0.3.41)
331 uglifier (2.2.1) 371 uglifier (2.2.1)
332 execjs (>= 0.3.0) 372 execjs (>= 0.3.0)
333 - multi_json (~> 1.0, >= 1.0.2) 373 + json (>= 1.8.0)
334 underscore-rails (1.5.2) 374 underscore-rails (1.5.2)
335 unicorn (4.6.3) 375 unicorn (4.6.3)
336 kgio (~> 2.6) 376 kgio (~> 2.6)
337 rack 377 rack
338 raindrops (~> 0.7) 378 raindrops (~> 0.7)
339 - useragent (0.8.3) 379 + useragent (0.9.0)
340 warden (1.2.3) 380 warden (1.2.3)
341 rack (>= 1.0) 381 rack (>= 1.0)
342 webmock (1.15.0) 382 webmock (1.15.0)
343 addressable (>= 2.2.7) 383 addressable (>= 2.2.7)
344 crack (>= 0.3.2) 384 crack (>= 0.3.2)
  385 + websocket-driver (0.3.3)
345 xmpp4r (0.5.5) 386 xmpp4r (0.5.5)
346 xpath (2.0.0) 387 xpath (2.0.0)
347 nokogiri (~> 1.3) 388 nokogiri (~> 1.3)
@@ -357,7 +398,6 @@ DEPENDENCIES @@ -357,7 +398,6 @@ DEPENDENCIES
357 airbrake 398 airbrake
358 better_errors 399 better_errors
359 binding_of_caller 400 binding_of_caller
360 - bitbucket_rest_api  
361 campy 401 campy
362 capistrano (~> 2.0) 402 capistrano (~> 2.0)
363 capybara 403 capybara
@@ -365,32 +405,38 @@ DEPENDENCIES @@ -365,32 +405,38 @@ DEPENDENCIES
365 database_cleaner 405 database_cleaner
366 decent_exposure 406 decent_exposure
367 devise 407 devise
  408 + draper
368 email_spec 409 email_spec
  410 + errbit_bitbucket_plugin
  411 + errbit_fogbugz_plugin
  412 + errbit_github_plugin
  413 + errbit_gitlab_plugin
  414 + errbit_jira_plugin
  415 + errbit_lighthouse_plugin
  416 + errbit_pivotal_plugin
  417 + errbit_plugin
  418 + errbit_redmine_plugin
  419 + errbit_unfuddle_plugin
369 execjs 420 execjs
370 fabrication 421 fabrication
371 flowdock 422 flowdock
372 foreman 423 foreman
373 - gitlab (~> 3.0.0)  
374 haml 424 haml
375 hipchat 425 hipchat
376 hoi 426 hoi
377 hoptoad_notifier (~> 2.4) 427 hoptoad_notifier (~> 2.4)
378 htmlentities 428 htmlentities
379 httparty 429 httparty
380 - jira-ruby  
381 jquery-rails (~> 2.1.4) 430 jquery-rails (~> 2.1.4)
382 kaminari (>= 0.14.1) 431 kaminari (>= 0.14.1)
383 launchy 432 launchy
384 - lighthouse-api  
385 meta_request 433 meta_request
386 mongoid 434 mongoid
387 mongoid-rspec 435 mongoid-rspec
388 mongoid_rails_migrations 436 mongoid_rails_migrations
389 - octokit (~> 2.0)  
390 omniauth-github 437 omniauth-github
391 - oruen_redmine_client  
392 - pivotal-tracker  
393 pjax_rails 438 pjax_rails
  439 + poltergeist
394 pry-rails 440 pry-rails
395 puma 441 puma
396 quiet_assets 442 quiet_assets
@@ -400,7 +446,6 @@ DEPENDENCIES @@ -400,7 +446,6 @@ DEPENDENCIES
400 railties (~> 3.2.21) 446 railties (~> 3.2.21)
401 ri_cal 447 ri_cal
402 rspec-rails 448 rspec-rails
403 - ruby-fogbugz  
404 rushover 449 rushover
405 strong_parameters 450 strong_parameters
406 therubyracer 451 therubyracer
app/assets/images/github_create.png

2.14 KB

app/assets/images/github_inactive.png

1.05 KB

app/assets/stylesheets/issue_tracker_icons.css.erb
1 /* Issue Tracker inactive, select, create and goto icons */ 1 /* Issue Tracker inactive, select, create and goto icons */
2 -<% trackers = IssueTracker.subclasses.map{|t| t.label } << 'none' %>  
3 2
4 -<% trackers.each do |tracker| %>  
5 -div.issue_tracker.nested label.<%= tracker %> {  
6 - background: url(<%= asset_path "#{ tracker }_inactive.png" %>) no-repeat;  
7 -}  
8 -div.issue_tracker.nested label.r_on.<%= tracker %> {  
9 - background: url(<%= asset_path "#{ tracker }_create.png" %>) no-repeat;  
10 -}  
11 -#action-bar a.<%= tracker %>_create {  
12 - background: transparent url(<%= asset_path "#{ tracker }_create.png" %>) 6px 5px no-repeat;  
13 -}  
14 -#action-bar a.<%= tracker %>_goto {  
15 - background: transparent url(<%= asset_path "#{ tracker }_goto.png" %>) 6px 5px no-repeat;  
16 -} 3 +<% ErrbitPlugin::Registry.issue_trackers.keys.each do |label| %>
  4 +div.issue_tracker.nested label.<%= label %> { background: url(<%= asset_path "#{ label }_inactive.png" %>) no-repeat; }
  5 +div.issue_tracker.nested label.r_on.<%= label %> { background: url(<%= asset_path "#{ label }_create.png" %>) no-repeat; }
  6 +#action-bar a.<%= label %>_create { background: transparent url(<%= asset_path "#{ label }_create.png" %>) 6px 5px no-repeat; }
  7 +#action-bar a.<%= label %>_goto { background: transparent url(<%= asset_path "#{ label }_goto.png" %>) 6px 5px no-repeat; }
17 <% end %> 8 <% end %>
18 9
app/controllers/apps_controller.rb
@@ -16,6 +16,9 @@ class AppsController &lt; ApplicationController @@ -16,6 +16,9 @@ class AppsController &lt; ApplicationController
16 } 16 }
17 17
18 expose(:app, :ancestor => :app_scope) 18 expose(:app, :ancestor => :app_scope)
  19 + expose(:app_decorate) do
  20 + AppDecorator.new(app)
  21 + end
19 22
20 expose(:all_errs) { 23 expose(:all_errs) {
21 !!params[:all_errs] 24 !!params[:all_errs]
@@ -50,7 +53,6 @@ class AppsController &lt; ApplicationController @@ -50,7 +53,6 @@ class AppsController &lt; ApplicationController
50 end 53 end
51 54
52 def create 55 def create
53 - initialize_subclassed_issue_tracker  
54 initialize_subclassed_notification_service 56 initialize_subclassed_notification_service
55 if app.save 57 if app.save
56 redirect_to app_url(app), :flash => { :success => I18n.t('controllers.apps.flash.create.success') } 58 redirect_to app_url(app), :flash => { :success => I18n.t('controllers.apps.flash.create.success') }
@@ -61,7 +63,6 @@ class AppsController &lt; ApplicationController @@ -61,7 +63,6 @@ class AppsController &lt; ApplicationController
61 end 63 end
62 64
63 def update 65 def update
64 - initialize_subclassed_issue_tracker  
65 initialize_subclassed_notification_service 66 initialize_subclassed_notification_service
66 if app.save 67 if app.save
67 redirect_to app_url(app), :flash => { :success => I18n.t('controllers.apps.flash.update.success') } 68 redirect_to app_url(app), :flash => { :success => I18n.t('controllers.apps.flash.update.success') }
@@ -91,17 +92,6 @@ class AppsController &lt; ApplicationController @@ -91,17 +92,6 @@ class AppsController &lt; ApplicationController
91 92
92 protected 93 protected
93 94
94 - def initialize_subclassed_issue_tracker  
95 - # set the app's issue tracker  
96 - if params[:app][:issue_tracker_attributes] && tracker_type = params[:app][:issue_tracker_attributes][:type]  
97 - available_tracker_classes = [IssueTracker] + IssueTracker.subclasses  
98 - tracker_class = available_tracker_classes.detect{|c| c.name == tracker_type}  
99 - if !tracker_class.nil?  
100 - app.issue_tracker = tracker_class.new(params[:app][:issue_tracker_attributes])  
101 - end  
102 - end  
103 - end  
104 -  
105 def initialize_subclassed_notification_service 95 def initialize_subclassed_notification_service
106 # set the app's notification service 96 # set the app's notification service
107 if params[:app][:notification_service_attributes] && notification_type = params[:app][:notification_service_attributes][:type] 97 if params[:app][:notification_service_attributes] && notification_type = params[:app][:notification_service_attributes][:type]
@@ -115,7 +105,7 @@ class AppsController &lt; ApplicationController @@ -115,7 +105,7 @@ class AppsController &lt; ApplicationController
115 105
116 def plug_params app 106 def plug_params app
117 app.watchers.build if app.watchers.none? 107 app.watchers.build if app.watchers.none?
118 - app.issue_tracker = IssueTracker.new unless app.issue_tracker_configured? 108 + app.issue_tracker ||= IssueTracker.new
119 app.notification_service = NotificationService.new unless app.notification_service_configured? 109 app.notification_service = NotificationService.new unless app.notification_service_configured?
120 app.copy_attributes_from(params[:copy_attributes_from]) if params[:copy_attributes_from] 110 app.copy_attributes_from(params[:copy_attributes_from]) if params[:copy_attributes_from]
121 end 111 end
app/controllers/problems_controller.rb
@@ -62,8 +62,7 @@ class ProblemsController &lt; ApplicationController @@ -62,8 +62,7 @@ class ProblemsController &lt; ApplicationController
62 end 62 end
63 63
64 def create_issue 64 def create_issue
65 - IssueTracker.update_url_options(request)  
66 - issue_creation = IssueCreation.new(problem, current_user, params[:tracker]) 65 + issue_creation = IssueCreation.new(problem, current_user, params[:tracker], request)
67 66
68 unless issue_creation.execute 67 unless issue_creation.execute
69 flash[:error] = issue_creation.errors.full_messages.join(', ') 68 flash[:error] = issue_creation.errors.full_messages.join(', ')
app/decorators/app_decorator.rb 0 → 100644
@@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
  1 +class AppDecorator < Draper::Decorator
  2 +
  3 + decorates_association :watchers
  4 + decorates_association :issue_tracker, :with => IssueTrackerDecorator
  5 + delegate_all
  6 +
  7 + def email_at_notices
  8 + object.email_at_notices.join(', ')
  9 + end
  10 +
  11 + def notify_user_display
  12 + object.notify_all_users ? 'display: none;' : ''
  13 + end
  14 +
  15 + def notify_err_display
  16 + object.notify_on_errs ? '' : 'display: none;'
  17 + end
  18 +
  19 +end
app/decorators/issue_tracker_decorator.rb 0 → 100644
@@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
  1 +class IssueTrackerDecorator < Draper::Decorator
  2 +
  3 + def initialize(object, key)
  4 + @object = object
  5 + @key = key
  6 + end
  7 + attr_reader :key
  8 +
  9 + delegate_all
  10 +
  11 + def issue_trackers
  12 + ErrbitPlugin::Registry.issue_trackers.each do |key, object|
  13 + yield IssueTrackerDecorator.new(object, key)
  14 + end
  15 + end
  16 +
  17 + def note
  18 + object.note.html_safe
  19 + end
  20 +
  21 + def fields
  22 + object.fields.each do |field, field_info|
  23 + yield IssueTrackerFieldDecorator.new(field, field_info)
  24 + end
  25 + end
  26 +
  27 + def params_class(tracker)
  28 + [chosen?(tracker), label].join(" ").strip
  29 + end
  30 +
  31 + private
  32 +
  33 + def chosen?(issue_tracker)
  34 + key == issue_tracker.type_tracker.to_s ? 'chosen' : ''
  35 + end
  36 +
  37 +end
app/decorators/issue_tracker_field_decorator.rb 0 → 100644
@@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
  1 +class IssueTrackerFieldDecorator < Draper::Decorator
  2 +
  3 + def initialize(field, field_info)
  4 + @object = field
  5 + @field_info = field_info
  6 + end
  7 + attr_reader :object, :field_info
  8 +
  9 + alias :key :object
  10 +
  11 + def label
  12 + field_info[:label] || object.to_s.titleize
  13 + end
  14 +
  15 +
  16 + def input(form, issue_tracker)
  17 + form.send(input_field, key.to_s,
  18 + :placeholder => field_info[:placeholder],
  19 + :value => issue_tracker.options[key.to_s])
  20 + end
  21 +
  22 + private
  23 +
  24 + def input_field
  25 + object == :password ? :password_field : :text_field
  26 + end
  27 +end
app/decorators/watcher_decorator.rb 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +class WatcherDecorator < Draper::Decorator
  2 +
  3 + delegate_all
  4 +
  5 + def email_choosen
  6 + object.email.blank? ? 'chosen' : ''
  7 + end
  8 +
  9 +end
app/interactors/issue_creation.rb
@@ -5,12 +5,24 @@ class IssueCreation @@ -5,12 +5,24 @@ class IssueCreation
5 5
6 delegate :app, :to => :problem 6 delegate :app, :to => :problem
7 7
8 - def initialize(problem, user, tracker_name) 8 + def initialize(problem, user, tracker_name, request)
9 @problem = problem 9 @problem = problem
10 @user = user 10 @user = user
11 @tracker_name = tracker_name 11 @tracker_name = tracker_name
  12 + IssueTracker.update_url_options(request)
12 end 13 end
13 14
  15 + def execute
  16 + tracker.create_issue(problem, user) if tracker
  17 + errors.empty?
  18 + rescue => ex
  19 + Rails.logger.error "Error during issue creation: " << ex.message
  20 + errors.add :base, "There was an error during issue creation: #{ex.message}"
  21 + false
  22 + end
  23 +
  24 + private
  25 +
14 def tracker 26 def tracker
15 return @tracker if @tracker 27 return @tracker if @tracker
16 28
@@ -21,10 +33,11 @@ class IssueCreation @@ -21,10 +33,11 @@ class IssueCreation
21 elsif !user.github_account? 33 elsif !user.github_account?
22 errors.add :base, "You haven't linked your Github account." 34 errors.add :base, "You haven't linked your Github account."
23 else 35 else
24 - @tracker = GithubIssuesTracker.new(  
25 - :app => app,  
26 - :username => user.github_login,  
27 - :oauth_token => user.github_oauth_token 36 + @tracker = ErrbitGithubPlugin::IssueTracker.new(
  37 + app, {
  38 + :username => user.github_login,
  39 + :oauth_token => user.github_oauth_token
  40 + }
28 ) 41 )
29 end 42 end
30 43
@@ -39,13 +52,4 @@ class IssueCreation @@ -39,13 +52,4 @@ class IssueCreation
39 52
40 @tracker 53 @tracker
41 end 54 end
42 -  
43 - def execute  
44 - tracker.create_issue problem, user if tracker  
45 - errors.empty?  
46 - rescue => ex  
47 - Rails.logger.error "Error during issue creation: " << ex.message  
48 - errors.add :base, "There was an error during issue creation: #{ex.message}"  
49 - false  
50 - end  
51 end 55 end
app/models/app.rb
@@ -26,7 +26,7 @@ class App @@ -26,7 +26,7 @@ class App
26 26
27 embeds_many :watchers 27 embeds_many :watchers
28 embeds_many :deploys 28 embeds_many :deploys
29 - embeds_one :issue_tracker 29 + embeds_one :issue_tracker, :class_name => 'IssueTracker'
30 embeds_one :notification_service 30 embeds_one :notification_service
31 31
32 has_many :problems, :inverse_of => :app, :dependent => :destroy 32 has_many :problems, :inverse_of => :app, :dependent => :destroy
@@ -44,7 +44,7 @@ class App @@ -44,7 +44,7 @@ class App
44 accepts_nested_attributes_for :watchers, :allow_destroy => true, 44 accepts_nested_attributes_for :watchers, :allow_destroy => true,
45 :reject_if => proc { |attrs| attrs[:user_id].blank? && attrs[:email].blank? } 45 :reject_if => proc { |attrs| attrs[:user_id].blank? && attrs[:email].blank? }
46 accepts_nested_attributes_for :issue_tracker, :allow_destroy => true, 46 accepts_nested_attributes_for :issue_tracker, :allow_destroy => true,
47 - :reject_if => proc { |attrs| !IssueTracker.subclasses.map(&:to_s).include?(attrs[:type].to_s) } 47 + :reject_if => proc { |attrs| !ErrbitPlugin::Registry.issue_trackers.keys.map(&:to_s).include?(attrs[:type_tracker].to_s) }
48 accepts_nested_attributes_for :notification_service, :allow_destroy => true, 48 accepts_nested_attributes_for :notification_service, :allow_destroy => true,
49 :reject_if => proc { |attrs| !NotificationService.subclasses.map(&:to_s).include?(attrs[:type].to_s) } 49 :reject_if => proc { |attrs| !NotificationService.subclasses.map(&:to_s).include?(attrs[:type].to_s) }
50 50
@@ -120,7 +120,7 @@ class App @@ -120,7 +120,7 @@ class App
120 120
121 121
122 def issue_tracker_configured? 122 def issue_tracker_configured?
123 - !!(issue_tracker.class < IssueTracker && issue_tracker.configured?) 123 + !!issue_tracker && !!(issue_tracker.configured?)
124 end 124 end
125 125
126 def notification_service_configured? 126 def notification_service_configured?
@@ -175,6 +175,13 @@ class App @@ -175,6 +175,13 @@ class App
175 set(:api_key, SecureRandom.hex) 175 set(:api_key, SecureRandom.hex)
176 end 176 end
177 177
  178 + ##
  179 + # Check if comments can be allowed on this application
  180 + #
  181 + def comments_allowed?
  182 + !issue_tracker || issue_tracker.comments_allowed?
  183 + end
  184 +
178 protected 185 protected
179 186
180 def store_cached_attributes_on_problems 187 def store_cached_attributes_on_problems
@@ -202,5 +209,6 @@ class App @@ -202,5 +209,6 @@ class App
202 github_repo.sub!(/(git@|https?:\/\/)#{github_host}(\/|:)/, '') 209 github_repo.sub!(/(git@|https?:\/\/)#{github_host}(\/|:)/, '')
203 github_repo.sub!(/\.git$/, '') 210 github_repo.sub!(/\.git$/, '')
204 end 211 end
  212 +
205 end 213 end
206 214
app/models/issue_tracker.rb
1 class IssueTracker 1 class IssueTracker
2 include Mongoid::Document 2 include Mongoid::Document
3 include Mongoid::Timestamps 3 include Mongoid::Timestamps
4 - include HashHelper 4 +
5 include Rails.application.routes.url_helpers 5 include Rails.application.routes.url_helpers
  6 +
6 default_url_options[:host] = ActionMailer::Base.default_url_options[:host] 7 default_url_options[:host] = ActionMailer::Base.default_url_options[:host]
7 default_url_options[:port] = ActionMailer::Base.default_url_options[:port] 8 default_url_options[:port] = ActionMailer::Base.default_url_options[:port]
8 9
9 embedded_in :app, :inverse_of => :issue_tracker 10 embedded_in :app, :inverse_of => :issue_tracker
10 11
11 - field :project_id, :type => String  
12 - field :alt_project_id, :type => String # Specify an alternative project id. e.g. for viewing files  
13 - field :api_token, :type => String  
14 - field :account, :type => String  
15 - field :username, :type => String  
16 - field :password, :type => String  
17 - field :ticket_properties, :type => String  
18 - field :subdomain, :type => String  
19 - field :milestone_id, :type => String  
20 -  
21 - # Is there any better way to enhance the props? Putting them into the subclass leads to  
22 - # an error while rendering the form fields -.-  
23 - field :base_url, :type => String  
24 - field :context_path, :type => String  
25 - field :issue_type, :type => String  
26 - field :issue_component, :type => String  
27 - field :issue_priority, :type => String  
28 -  
29 - validate :check_params  
30 -  
31 - # Subclasses are responsible for overwriting this method.  
32 - def check_params; true; end  
33 -  
34 - def issue_title(problem)  
35 - "[#{ problem.environment }][#{ problem.where }] #{problem.message.to_s.truncate(100)}"  
36 - end  
37 -  
38 - # Allows us to set the issue tracker class from a single form.  
39 - def type; self._type; end  
40 - def type=(t); self._type=t; end 12 + field :type_tracker, :type => String
  13 + field :options, :type => Hash, :default => {}
41 14
42 - def url; nil; end  
43 -  
44 - # Retrieve tracker label from either class or instance.  
45 - Label = ''  
46 - def self.label; self::Label; end  
47 - def label; self.class.label; end  
48 -  
49 - def configured?  
50 - project_id.present?  
51 - end 15 + validate :validate_tracker
52 16
53 ## 17 ##
54 # Update default_url_option with valid data from the request information 18 # Update default_url_option with valid data from the request information
@@ -60,4 +24,23 @@ class IssueTracker @@ -60,4 +24,23 @@ class IssueTracker
60 IssueTracker.default_url_options[:port] = request.port 24 IssueTracker.default_url_options[:port] = request.port
61 IssueTracker.default_url_options[:protocol] = request.scheme 25 IssueTracker.default_url_options[:protocol] = request.scheme
62 end 26 end
  27 +
  28 + def tracker
  29 + klass = ErrbitPlugin::Registry.issue_trackers[self.type_tracker]
  30 + klass = ErrbitPlugin::NoneIssueTracker unless klass
  31 +
  32 + @tracker = klass.new(app, self.options)
  33 + end
  34 +
  35 + # Allow the tracker to validate its own params
  36 + def validate_tracker
  37 + (tracker.errors || {}).each do |k,v|
  38 + errors.add k, v
  39 + end
  40 + end
  41 +
  42 + delegate :configured?, :to => :tracker
  43 + delegate :create_issue, :to => :tracker
  44 + delegate :comments_allowed?, :to => :tracker
  45 + delegate :url, :to => :tracker
63 end 46 end
app/models/issue_trackers/bitbucket_issues_tracker.rb
@@ -1,55 +0,0 @@ @@ -1,55 +0,0 @@
1 -begin  
2 - require 'bitbucket_rest_api'  
3 -rescue LoadError  
4 -end  
5 -  
6 -if defined? BitBucket  
7 - class IssueTrackers::BitbucketIssuesTracker < IssueTracker  
8 - Label = "bitbucket"  
9 - Note = 'Please configure your Bitbucket repository in the <strong>BITBUCKET REPO</strong> field above.'  
10 - Fields = [  
11 - [:api_token, {  
12 - :placeholder => "Your username on Bitbucket account",  
13 - :label => "Username"  
14 - }],  
15 - [:project_id, {  
16 - :placeholder => "Password for your Bitbucket account",  
17 - :label => "Password"  
18 - }]  
19 - ]  
20 -  
21 - def check_params  
22 - if Fields.detect {|f| self[f[0]].blank? }  
23 - errors.add :base, 'You must specify your Bitbucket username and password'  
24 - end  
25 - end  
26 -  
27 - def repo_name  
28 - app.bitbucket_repo  
29 - end  
30 -  
31 - def create_issue(problem, reported_by = nil)  
32 - bitbucket = BitBucket.new :basic_auth => "#{api_token}:#{project_id}"  
33 -  
34 - begin  
35 - r_user = repo_name.split('/')[0]  
36 - r_name = repo_name.split('/')[1]  
37 - issue = bitbucket.issues.create r_user, r_name, :title => issue_title(problem), :content => body_template.result(binding), :priority => 'critical'  
38 - problem.update_attributes(  
39 - :issue_link => "https://bitbucket.org/#{repo_name}/issue/#{issue.local_id}/",  
40 - :issue_type => Label  
41 - )  
42 - rescue BitBucket::Error::Unauthorized  
43 - raise IssueTrackers::AuthenticationError, "Could not authenticate with BitBucket. Please check your username and password."  
44 - end  
45 - end  
46 -  
47 - def body_template  
48 - @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/bitbucket_issues_body.txt.erb"))  
49 - end  
50 -  
51 - def url  
52 - "https://www.bitbucket.org/#{repo_name}/issues"  
53 - end  
54 - end  
55 -end  
app/models/issue_trackers/fogbugz_tracker.rb
@@ -1,53 +0,0 @@ @@ -1,53 +0,0 @@
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  
24 - end  
25 -  
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  
29 -  
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(',')  
36 -  
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 - )  
42 -  
43 - end  
44 -  
45 - def body_template  
46 - @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/fogbugz_body.txt.erb"))  
47 - end  
48 -  
49 - def url  
50 - "http://#{account}.fogbugz.com/"  
51 - end  
52 - end  
53 -end  
app/models/issue_trackers/github_issues_tracker.rb
@@ -1,61 +0,0 @@ @@ -1,61 +0,0 @@
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  
21 - end  
22 -  
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  
27 - end  
28 -  
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, :access_token => oauth_token)  
33 - else  
34 - client = Octokit::Client.new(:login => username, :password => password)  
35 - end  
36 -  
37 - begin  
38 - issue = client.create_issue(  
39 - project_id,  
40 - issue_title(problem),  
41 - body_template.result(binding).unpack('C*').pack('U*')  
42 - )  
43 - problem.update_attributes(  
44 - :issue_link => issue.rels[:html].href,  
45 - :issue_type => Label  
46 - )  
47 -  
48 - rescue Octokit::Unauthorized  
49 - raise IssueTrackers::AuthenticationError, "Could not authenticate with GitHub. Please check your username and password."  
50 - end  
51 - end  
52 -  
53 - def body_template  
54 - @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/github_issues_body.txt.erb").gsub(/^\s*/, ''))  
55 - end  
56 -  
57 - def url  
58 - "https://github.com/#{project_id}/issues"  
59 - end  
60 - end  
61 -end  
app/models/issue_trackers/gitlab_tracker.rb
@@ -1,53 +0,0 @@ @@ -1,53 +0,0 @@
1 -if defined? Gitlab  
2 - class IssueTrackers::GitlabTracker < IssueTracker  
3 - Label = "gitlab"  
4 - Fields = [  
5 - [:account, {  
6 - :label => "Gitlab URL",  
7 - :placeholder => "e.g. https://example.net"  
8 - }],  
9 - [:api_token, {  
10 - :placeholder => "API Token for your account"  
11 - }],  
12 - [:project_id, {  
13 - :label => "Ticket Project ID (use Number)",  
14 - :placeholder => "Gitlab Project where issues will be created"  
15 - }],  
16 - [:alt_project_id, {  
17 - :label => "Project Name (namespace/project)",  
18 - :placeholder => "Gitlab Project where issues will be created"  
19 - }]  
20 - ]  
21 -  
22 - def check_params  
23 - if Fields.detect {|f| self[f[0]].blank?}  
24 - errors.add :base, 'You must specify your Gitlab URL, API token, Project ID and Project Name'  
25 - end  
26 - end  
27 -  
28 - def create_issue(problem, reported_by = nil)  
29 - Gitlab.configure do |config|  
30 - config.endpoint = "#{account}/api/v3"  
31 - config.private_token = api_token  
32 - config.user_agent = 'Errbit User Agent'  
33 - end  
34 - title = issue_title problem  
35 - description_summary = summary_template.result(binding)  
36 - description_body = body_template.result(binding)  
37 - ticket = Gitlab.create_issue(project_id, title, { :description => description_summary, :labels => "errbit" } )  
38 - Gitlab.create_issue_note(project_id, ticket.id, description_body)  
39 - end  
40 -  
41 - def summary_template  
42 - @@summary_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/gitlab_summary.txt.erb").gsub(/^\s*/, ''))  
43 - end  
44 -  
45 - def body_template  
46 - @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/gitlab_body.txt.erb").gsub(/^\s*/, ''))  
47 - end  
48 -  
49 - def url  
50 - "#{account}/#{alt_project_id}/issues"  
51 - end  
52 - end  
53 -end  
app/models/issue_trackers/jira_tracker.rb
@@ -1,110 +0,0 @@ @@ -1,110 +0,0 @@
1 -if defined? JIRA  
2 - class IssueTrackers::JiraTracker < IssueTracker  
3 - Label = 'jira'  
4 -  
5 - Fields = [  
6 - [:base_url, {  
7 - :label => 'Jira URL without trailing slash',  
8 - :placeholder => 'https://jira.example.org/'  
9 - }],  
10 - [:context_path, {  
11 - :optional => true,  
12 - :label => 'Context Path (Just "/" if empty otherwise with leading slash)',  
13 - :placeholder => "/jira"  
14 - }],  
15 - [:username, {  
16 - :optional => true,  
17 - :label => 'HTTP Basic Auth User',  
18 - :placeholder => 'johndoe'  
19 - }],  
20 - [:password, {  
21 - :optional => true,  
22 - :label => 'HTTP Basic Auth Password',  
23 - :placeholder => 'p@assW0rd'  
24 - }],  
25 - [:project_id, {  
26 - :label => 'Project Key',  
27 - :placeholder => 'The project Key where the issue will be created'  
28 - }],  
29 - [:account, {  
30 - :optional => true,  
31 - :label => 'Assign to this user. If empty, Jira takes the project default.',  
32 - :placeholder => "username"  
33 - }],  
34 - [:issue_component, {  
35 - :label => 'Issue category',  
36 - :placeholder => 'Website - Other'  
37 - }],  
38 - [:issue_type, {  
39 - :label => 'Issue type',  
40 - :placeholder => 'Bug'  
41 - }],  
42 - [:issue_priority, {  
43 - :label => 'Priority',  
44 - :placeholder => 'Normal'  
45 - }]  
46 - ]  
47 -  
48 - def check_params  
49 - if Fields.detect { |f| self[f[0]].blank? && !f[1][:optional] }  
50 - errors.add :base, 'You must specify all non optional values!'  
51 - end  
52 - end  
53 -  
54 -  
55 - #  
56 - # @param problem Problem  
57 - def create_issue(problem, reported_by = nil)  
58 - options = {  
59 - :username => username,  
60 - :password => password,  
61 - :site => base_url,  
62 - :context_path => context_path,  
63 - :auth_type => :basic,  
64 - :use_ssl => base_url.match(/^https/) ? true : false  
65 - }  
66 - client = JIRA::Client.new(options)  
67 -  
68 - issue = {  
69 - :fields => {  
70 - :project => {  
71 - :key => project_id  
72 - },  
73 - :summary => issue_title(problem),  
74 - :description => body_template.result(binding),  
75 - :issuetype => {  
76 - :name => issue_type  
77 - },  
78 - :priority => {  
79 - :name => issue_priority,  
80 - },  
81 -  
82 - :components => [{:name => issue_component}]  
83 - }  
84 - }  
85 -  
86 - issue[:fields][:assignee] = {:name => account} if account  
87 -  
88 - issue_build = client.Issue.build  
89 - issue_build.save(issue)  
90 - issue_build.fetch  
91 -  
92 - problem.update_attributes(  
93 - :issue_link => "#{base_url}#{context_path}browse/#{issue_build.key}",  
94 - :issue_type => Label  
95 - )  
96 -  
97 - # Maybe in a later version?  
98 - #remote_link = {  
99 - # :url => app_problem_url(problem.app, problem),  
100 - # :name => "Link to Errbit Issue"  
101 - #}  
102 - #remote_link_build = issue_build.remotelink.build  
103 - #remote_link_build.save(remote_link)  
104 - end  
105 -  
106 - def body_template  
107 - @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/jira_body.txt.erb"))  
108 - end  
109 - end  
110 -end  
111 \ No newline at end of file 0 \ No newline at end of file
app/models/issue_trackers/lighthouse_tracker.rb
@@ -1,53 +0,0 @@ @@ -1,53 +0,0 @@
1 -if defined? Lighthouse  
2 - class IssueTrackers::LighthouseTracker < IssueTracker  
3 - Label = "lighthouseapp"  
4 - Fields = [  
5 - [:account, {  
6 - :label => "Subdomain",  
7 - :placeholder => "subdomain from http://{{subdomain}}.lighthouseapp.com"  
8 - }],  
9 - [:api_token, {  
10 - :label => "API Token",  
11 - :placeholder => "123456789abcdef123456789abcdef"  
12 - }],  
13 - [:project_id, {  
14 - :label => "Project ID",  
15 - :placeholder => "123456"  
16 - }]  
17 - ]  
18 -  
19 - def check_params  
20 - if Fields.detect {|f| self[f[0]].blank? }  
21 - errors.add :base, 'You must specify your Lighthouseapp Subdomain, API token and Project ID'  
22 - end  
23 - end  
24 -  
25 - def create_issue(problem, reported_by = nil)  
26 - Lighthouse.account = account  
27 - Lighthouse.token = api_token  
28 - # updating lighthouse account  
29 - Lighthouse::Ticket.site  
30 - Lighthouse::Ticket.format = :xml  
31 - ticket = Lighthouse::Ticket.new(:project_id => project_id)  
32 - ticket.title = issue_title problem  
33 -  
34 - ticket.body = body_template.result(binding)  
35 -  
36 - ticket.tags << "errbit"  
37 - ticket.save!  
38 - problem.update_attributes(  
39 - :issue_link => "#{Lighthouse::Ticket.site.to_s.sub(/#{Lighthouse::Ticket.site.path}$/, '')}#{Lighthouse::Ticket.element_path(ticket.id, :project_id => project_id)}".sub(/\.xml$/, ''),  
40 - :issue_type => Label  
41 - )  
42 -  
43 - end  
44 -  
45 - def body_template  
46 - @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/lighthouseapp_body.txt.erb").gsub(/^\s*/, ''))  
47 - end  
48 -  
49 - def url  
50 - "http://#{account}.lighthouseapp.com"  
51 - end  
52 - end  
53 -end  
54 \ No newline at end of file 0 \ No newline at end of file
app/models/issue_trackers/mingle_tracker.rb
@@ -1,71 +0,0 @@ @@ -1,71 +0,0 @@
1 -class IssueTrackers::MingleTracker < IssueTracker  
2 - Label = "mingle"  
3 - Note = "Note: <strong>CARD PROPERTIES</strong> must be comma separated <em>key = value</em> pairs"  
4 -  
5 - Fields = [  
6 - [:account, {  
7 - :label => "Mingle URL",  
8 - :placeholder => "http://mingle.example.com/"  
9 - }],  
10 - [:project_id, {  
11 - :placeholder => "Mingle project"  
12 - }],  
13 - [:ticket_properties, {  
14 - :label => "Card Properties",  
15 - :placeholder => "card_type = Defect, defect_status = Open, priority = Essential"  
16 - }],  
17 - [:username, {  
18 - :label => "Sign-in name",  
19 - :placeholder => "Sign-in name for your account"  
20 - }],  
21 - [:password, {  
22 - :placeholder => "Password for your account"  
23 - }]  
24 - ]  
25 -  
26 - def check_params  
27 - if Fields.detect {|f| self[f[0]].blank? } or !ticket_properties_hash["card_type"]  
28 - errors.add :base, 'You must specify your Mingle URL, Project ID, Card Type (in default card properties), Sign-in name, and Password'  
29 - end  
30 - end  
31 -  
32 - def create_issue(problem, reported_by = nil)  
33 - properties = ticket_properties_hash  
34 - basic_auth = account.gsub(/https?:\/\//, "https://#{username}:#{password}@")  
35 - Mingle.set_site "#{basic_auth}/api/v1/projects/#{project_id}/"  
36 -  
37 - card = Mingle::Card.new  
38 - card.card_type_name = properties.delete("card_type")  
39 - card.name = issue_title(problem)  
40 - card.description = body_template.result(binding)  
41 - properties.each do |property, value|  
42 - card.send("cp_#{property}=", value)  
43 - end  
44 -  
45 - card.save!  
46 - problem.update_attributes(  
47 - :issue_link => URI.parse("#{account}/projects/#{project_id}/cards/#{card.id}").to_s,  
48 - :issue_type => Label  
49 - )  
50 - end  
51 -  
52 - def body_template  
53 - @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/textile_body.txt.erb"))  
54 - end  
55 -  
56 - def ticket_properties_hash  
57 - # Parses 'key=value, key2=value2' from ticket_properties into a ruby hash.  
58 - self.ticket_properties.to_s.split(",").inject({}) do |hash, pair|  
59 - key, value = pair.split("=").map(&:strip)  
60 - hash[key] = value  
61 - hash  
62 - end  
63 - end  
64 -  
65 - def url  
66 - acc_url = account.start_with?('http') ? account : "http://#{account}"  
67 - URI.parse("#{acc_url}/projects/#{project_id}").to_s  
68 - rescue URI::InvalidURIError  
69 - end  
70 -end  
71 -  
app/models/issue_trackers/pivotal_labs_tracker.rb
@@ -1,43 +0,0 @@ @@ -1,43 +0,0 @@
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 - ]  
10 -  
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  
15 - end  
16 -  
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  
24 -  
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  
33 - end  
34 -  
35 - def body_template  
36 - @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/pivotal_body.txt.erb"))  
37 - end  
38 -  
39 - def url  
40 - "https://www.pivotaltracker.com/"  
41 - end  
42 - end  
43 -end  
44 \ No newline at end of file 0 \ No newline at end of file
app/models/issue_trackers/redmine_tracker.rb
@@ -1,75 +0,0 @@ @@ -1,75 +0,0 @@
1 -if defined? RedmineClient  
2 - class IssueTrackers::RedmineTracker < IssueTracker  
3 - Label = "redmine"  
4 - Fields = [  
5 - [:account, {  
6 - :label => "Redmine URL",  
7 - :placeholder => "http://www.redmine.org/"  
8 - }],  
9 - [:api_token, {  
10 - :placeholder => "API Token for your account"  
11 - }],  
12 - [:username, {  
13 - :placeholder => "Your username"  
14 - }],  
15 - [:password, {  
16 - :placeholder => "Your password"  
17 - }],  
18 - [:project_id, {  
19 - :label => "Ticket Project",  
20 - :placeholder => "Redmine Project where tickets will be created"  
21 - }],  
22 - [:alt_project_id, {  
23 - :optional => true,  
24 - :label => "App Project",  
25 - :placeholder => "Where app's files & revisions can be viewed. (Leave blank to use the above project by default)"  
26 - }]  
27 - ]  
28 -  
29 - def check_params  
30 - if Fields.detect {|f| self[f[0]].blank? && !f[1][:optional]}  
31 - errors.add :base, 'You must specify your Redmine URL, API token, Username, Password and Project ID'  
32 - end  
33 - end  
34 -  
35 - def create_issue(problem, reported_by = nil)  
36 - token = api_token  
37 - acc = account  
38 - user = username  
39 - passwd = password  
40 - RedmineClient::Base.configure do  
41 - self.token = token  
42 - self.user = user  
43 - self.password = passwd  
44 - self.site = acc  
45 - self.format = :xml  
46 - end  
47 - issue = RedmineClient::Issue.new(:project_id => project_id)  
48 - issue.subject = issue_title problem  
49 - issue.description = body_template.result(binding)  
50 - issue.save!  
51 - problem.update_attributes(  
52 - :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}"),  
53 - :issue_type => Label  
54 - )  
55 - end  
56 -  
57 - def url_to_file(file_path, line_number = nil)  
58 - # alt_project_id let's users specify a different project for tickets / app files.  
59 - project = self.alt_project_id.present? ? self.alt_project_id : self.project_id  
60 - url = "#{self.account.gsub(/\/$/, '')}/projects/#{project}/repository/revisions/#{app.repository_branch}/entry/#{file_path.sub(/\[PROJECT_ROOT\]/, '').sub(/^\//,'')}"  
61 - line_number ? url << "#L#{line_number}" : url  
62 - end  
63 -  
64 - def body_template  
65 - @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/redmine_body.txt.erb"))  
66 - end  
67 -  
68 - def url  
69 - acc_url = account.start_with?('http') ? account : "http://#{account}"  
70 - acc_url = acc_url.gsub(/\/$/, '')  
71 - URI.parse("#{acc_url}/projects/#{project_id}").to_s  
72 - rescue URI::InvalidURIError  
73 - end  
74 - end  
75 -end  
app/models/issue_trackers/unfuddle_tracker.rb
@@ -1,69 +0,0 @@ @@ -1,69 +0,0 @@
1 -class IssueTrackers::UnfuddleTracker < IssueTracker  
2 - Label = "unfuddle"  
3 - Fields = [  
4 -  
5 - [:account, {  
6 - :placeholder => "Your domain"  
7 - }],  
8 -  
9 -  
10 - [:username, {  
11 - :placeholder => "Your username"  
12 - }],  
13 -  
14 - [:password, {  
15 - :placeholder => "Your password"  
16 - }],  
17 -  
18 - [:project_id, {  
19 - :label => "Ticket Project",  
20 - :placeholder => "Project where tickets will be created"  
21 - }],  
22 -  
23 - [:milestone_id, {  
24 - :optional => true,  
25 - :label => "Ticket Milestone",  
26 - :placeholder => "Milestone where tickets will be created"  
27 - }]  
28 -  
29 -  
30 - ]  
31 -  
32 - def check_params  
33 - if Fields.detect {|f| self[f[0]].blank? && !f[1][:optional]}  
34 - errors.add :base, 'You must specify your Account, Username, Password and Project ID'  
35 - end  
36 - end  
37 -  
38 - def create_issue(problem, reported_by = nil)  
39 -  
40 - Unfuddle.config(account, username, password)  
41 - begin  
42 - issue_options = {:project_id => project_id,  
43 - :summary => issue_title(problem),  
44 - :priority => '5',  
45 - :status => "new",  
46 - :description => body_template.result(binding),  
47 - 'description-format' => 'textile' }  
48 -  
49 - issue_options[:milestone_id] = milestone_id if milestone_id.present?  
50 -  
51 - issue = Unfuddle::Ticket.create(issue_options)  
52 - problem.update_attributes(  
53 - :issue_link => File.join("#{url}/tickets/#{issue.id}"),  
54 - :issue_type => Label  
55 - )  
56 - rescue ActiveResource::UnauthorizedAccess  
57 - raise ActiveResource::UnauthorizedAccess, "Could not authenticate with Unfuddle. Please check your username and password."  
58 - end  
59 -  
60 - end  
61 -  
62 - def body_template  
63 - @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/textile_body.txt.erb"))  
64 - end  
65 -  
66 - def url  
67 - "https://#{account}.unfuddle.com/projects/#{project_id}"  
68 - end  
69 -end  
app/models/problem.rb
@@ -62,12 +62,19 @@ class Problem @@ -62,12 +62,19 @@ class Problem
62 env.present? ? where(:environment => env) : scoped 62 env.present? ? where(:environment => env) : scoped
63 end 63 end
64 64
  65 + def url
  66 + Rails.application.routes.url_helpers.app_problem_url(app, self,
  67 + :host => Errbit::Config.host,
  68 + :port => Errbit::Config.port
  69 + )
  70 + end
  71 +
65 def notices 72 def notices
66 Notice.for_errs(errs).ordered 73 Notice.for_errs(errs).ordered
67 end 74 end
68 75
69 def comments_allowed? 76 def comments_allowed?
70 - Errbit::Config.allow_comments_with_issue_tracker || !app.issue_tracker_configured? 77 + Errbit::Config.allow_comments_with_issue_tracker || app.comments_allowed?
71 end 78 end
72 79
73 def resolve! 80 def resolve!
@@ -148,7 +155,7 @@ class Problem @@ -148,7 +155,7 @@ class Problem
148 def issue_type 155 def issue_type
149 # Return issue_type if configured, but fall back to detecting app's issue tracker 156 # Return issue_type if configured, but fall back to detecting app's issue tracker
150 attributes['issue_type'] ||= 157 attributes['issue_type'] ||=
151 - (app.issue_tracker_configured? && app.issue_tracker.label) || nil 158 + (app.issue_tracker_configured? && app.issue_tracker.type_tracker) || nil
152 end 159 end
153 160
154 def self.search(value) 161 def self.search(value)
app/views/apps/_fields.html.haml
@@ -32,9 +32,9 @@ @@ -32,9 +32,9 @@
32 = f.check_box :notify_on_errs, 'data-show-when-checked' => '.email_at_notices_nested' 32 = f.check_box :notify_on_errs, 'data-show-when-checked' => '.email_at_notices_nested'
33 = f.label :notify_on_errs, 'Notify on errors' 33 = f.label :notify_on_errs, 'Notify on errors'
34 - if Errbit::Config.per_app_email_at_notices 34 - if Errbit::Config.per_app_email_at_notices
35 - %div.email_at_notices_nested{:style => f.object.notify_on_errs ? '' : 'display: none;'} 35 + %div.email_at_notices_nested{:style => app_decorate.notify_err_display}
36 .field-helpertext Send a notification every 36 .field-helpertext Send a notification every
37 - = f.text_field :email_at_notices, :value => f.object.email_at_notices.join(", ") 37 + = f.text_field :email_at_notices, :value => app_decorate.email_at_notices
38 .field-helpertext times an error occurs (comma separated). 38 .field-helpertext times an error occurs (comma separated).
39 %div.checkbox 39 %div.checkbox
40 = f.check_box :notify_on_deploys 40 = f.check_box :notify_on_deploys
@@ -45,13 +45,13 @@ @@ -45,13 +45,13 @@
45 = f.label :notify_all_users, 'Send notifications to all users' 45 = f.label :notify_all_users, 'Send notifications to all users'
46 46
47 47
48 -%fieldset.watchers.nested-wrapper{:style => f.object.notify_all_users ? 'display: none;' : ''} 48 +%fieldset.watchers.nested-wrapper{:style => app_decorate.notify_user_display}
49 %legend Watchers 49 %legend Watchers
50 = f.fields_for :watchers do |w| 50 = f.fields_for :watchers do |w|
51 %div.watcher.nested 51 %div.watcher.nested
52 %div.choose 52 %div.choose
53 = w.radio_button :watcher_type, :user 53 = w.radio_button :watcher_type, :user
54 - = label_tag :watcher_type_user, 'User', :for => label_for_attr(w, 'watcher_type_user') 54 + = w.label :watcher_type_user, 'User'
55 = w.radio_button :watcher_type, :email 55 = w.radio_button :watcher_type, :email
56 = label_tag :watcher_type_email, 'Email Address', :for => label_for_attr(w, 'watcher_type_email') 56 = label_tag :watcher_type_email, 'Email Address', :for => label_for_attr(w, 'watcher_type_email')
57 %div.watcher_params.user{:class => w.object.email.blank? ? 'chosen' : nil} 57 %div.watcher_params.user{:class => w.object.email.blank? ? 'chosen' : nil}
app/views/apps/_issue_tracker_fields.html.haml
1 %fieldset 1 %fieldset
2 - %legend Issue tracker  
3 - = f.fields_for :issue_tracker do |w| 2 + %legend= t('.legend')
  3 + = f.fields_for :issue_tracker do |issue_tracker_form|
4 %div.issue_tracker.nested 4 %div.issue_tracker.nested
5 %div.choose 5 %div.choose
6 - = label_tag :type_none, :for => label_for_attr(w, 'type_issuetracker'), :class => "label_radio none" do  
7 - = w.radio_button :type, "IssueTracker", 'data-section' => 'none'  
8 - (None)  
9 - - IssueTracker.subclasses.each do |tracker|  
10 - = label_tag "type_#{tracker.label}:", :for => label_for_attr(w, "type_#{tracker.name.downcase.gsub(':','')}"), :class => "label_radio #{tracker.label}" do  
11 - = w.radio_button :type, tracker.name, 'data-section' => tracker.label  
12 - = tracker.name[/::(.*)Tracker/,1].titleize 6 + - issue_tracker_form.object.issue_trackers do |tracker|
  7 + = issue_tracker_form.label :type_tracker, :class => "label_radio #{tracker.label}", :value => tracker.key do
  8 + = issue_tracker_form.radio_button :type_tracker, tracker.key, 'data-section' => tracker.label
  9 + = tracker.label
13 10
14 - %div.tracker_params.none{:class => (w.object && !(w.object.class < IssueTracker)) ? 'chosen' : nil}  
15 - %p When no issue tracker has been configured, you will be able to leave comments on errors.  
16 - - IssueTracker.subclasses.each do |tracker|  
17 - %div.tracker_params{:class => (w.object.is_a?(tracker) ? 'chosen ' : '') << tracker.label}  
18 - - if defined?(tracker::Note)  
19 - %p= tracker::Note.html_safe  
20 - - tracker::Fields.each do |field, field_info|  
21 - = w.label field, field_info[:label] || field.to_s.titleize  
22 - - field_type = field == :password ? :password_field : :text_field  
23 - = w.send field_type, field, :placeholder => field_info[:placeholder], :value => w.object.send(field) 11 + - issue_tracker_form.object.issue_trackers do |tracker|
  12 + %div.tracker_params{:class => tracker.params_class(issue_tracker_form.object)}
  13 + = issue_tracker_form.fields_for(:options) do |options_form|
  14 + %p= tracker.note
  15 + - tracker.fields do |field|
  16 + = options_form.label field.key, field.label
  17 + = field.input(options_form, issue_tracker_form.object)
24 18
25 .image_preloader 19 .image_preloader
26 - - (IssueTracker.subclasses.map{|t| t.label } << 'none').each do |tracker|  
27 - = image_tag "#{tracker}_inactive.png"  
28 - = image_tag "#{tracker}_create.png" 20 + - issue_tracker_form.object.issue_trackers do |tracker|
  21 + = image_tag "#{tracker.label}_inactive.png"
  22 + = image_tag "#{tracker.label}_create.png"
29 23
app/views/apps/edit.html.haml
1 -- content_for :title, 'Edit App' 1 +- content_for :title, t('.title')
2 - content_for :action_bar do 2 - content_for :action_bar do
3 = link_to_copy_attributes_from_other_app 3 = link_to_copy_attributes_from_other_app
4 = link_to 'delete all errs', destroy_all_app_problems_path(app), :method => :post, 4 = link_to 'delete all errs', destroy_all_app_problems_path(app), :method => :post,
5 :data => { :confirm => t('apps.confirm_destroy_all_problems') }, :class => 'button' 5 :data => { :confirm => t('apps.confirm_destroy_all_problems') }, :class => 'button'
6 = link_to 'delete application', app_path(app), :method => :delete, 6 = link_to 'delete application', app_path(app), :method => :delete,
7 :data => { :confirm => t('apps.confirm_delete') }, :class => 'button' 7 :data => { :confirm => t('apps.confirm_delete') }, :class => 'button'
8 - = link_to('cancel', app_path(app), :class => 'button') 8 + = link_to(t('.cancel'), app_path(app), :class => 'button')
9 9
10 -= form_for app do |f| 10 += form_for app_decorate do |f|
11 11
12 = render 'fields', :f => f 12 = render 'fields', :f => f
13 13
14 - %div.buttons= f.submit 'Update App' 14 + %div.buttons= f.submit t('.update')
15 15
app/views/apps/index.html.haml
@@ -36,7 +36,7 @@ @@ -36,7 +36,7 @@
36 - if any_issue_trackers? 36 - if any_issue_trackers?
37 %td.issue_tracker 37 %td.issue_tracker
38 - if app.issue_tracker_configured? 38 - if app.issue_tracker_configured?
39 - - tracker_img = image_tag("#{app.issue_tracker.label}_goto.png") 39 + - tracker_img = image_tag("#{app.issue_tracker.type_tracker}_goto.png")
40 - if app.issue_tracker.url 40 - if app.issue_tracker.url
41 = link_to( tracker_img, app.issue_tracker.url ) 41 = link_to( tracker_img, app.issue_tracker.url )
42 - else 42 - else
app/views/apps/new.html.haml
1 -- content_for :title, 'Add App' 1 +- content_for :title, t('.title')
2 - content_for :action_bar do 2 - content_for :action_bar do
3 = link_to_copy_attributes_from_other_app 3 = link_to_copy_attributes_from_other_app
4 - = link_to('cancel', apps_path, :class => 'button') 4 + = link_to(t('.cancel'), apps_path, :class => 'button')
5 5
6 -= form_for app do |f| 6 += form_for app_decorate do |f|
7 7
8 = render 'fields', :f => f 8 = render 'fields', :f => f
9 9
10 - %div.buttons= f.submit 'Add App' 10 + %div.buttons= f.submit t('.add_app')
11 11
app/views/problems/_issue_tracker_links.html.haml
@@ -6,10 +6,10 @@ @@ -6,10 +6,10 @@
6 %span.disabled= link_to 'creating...', '#', :class => "#{problem.issue_type}_inactive create-issue" 6 %span.disabled= link_to 'creating...', '#', :class => "#{problem.issue_type}_inactive create-issue"
7 = link_to 'retry', create_issue_app_problem_path(app, problem), :method => :post 7 = link_to 'retry', create_issue_app_problem_path(app, problem), :method => :post
8 - else 8 - else
9 - - if app.github_repo? 9 + - if app.issue_tracker_configured? && !app.issue_tracker.type_tracker.eql?('github')
  10 + %span= link_to 'create issue', create_issue_app_problem_path(app, problem), :method => :post, :class => "#{app.issue_tracker.type_tracker}_create create-issue"
  11 + - elsif app.github_repo?
10 - if current_user.can_create_github_issues? 12 - if current_user.can_create_github_issues?
11 %span= link_to 'create issue', create_issue_app_problem_path(app, problem, :tracker => 'user_github'), :method => :post, :class => "github_create create-issue" 13 %span= link_to 'create issue', create_issue_app_problem_path(app, problem, :tracker => 'user_github'), :method => :post, :class => "github_create create-issue"
12 - - elsif app.issue_tracker_configured? && app.issue_tracker.label.eql?('github') 14 + - elsif app.issue_tracker_configured? && app.issue_tracker.type_tracker.eql?('github')
13 %span= link_to 'create issue', create_issue_app_problem_path(app, problem), :method => :post, :class => "github_create create-issue" 15 %span= link_to 'create issue', create_issue_app_problem_path(app, problem), :method => :post, :class => "github_create create-issue"
14 - - if app.issue_tracker_configured? && !app.issue_tracker.label.eql?('github')  
15 - %span= link_to 'create issue', create_issue_app_problem_path(app, problem), :method => :post, :class => "#{app.issue_tracker.label}_create create-issue"  
config/application.rb
@@ -9,6 +9,8 @@ require &quot;action_mailer/railtie&quot; @@ -9,6 +9,8 @@ require &quot;action_mailer/railtie&quot;
9 require 'mongoid/railtie' 9 require 'mongoid/railtie'
10 require "sprockets/railtie" 10 require "sprockets/railtie"
11 11
  12 +require 'draper'
  13 +
12 if defined?(Bundler) 14 if defined?(Bundler)
13 # If you precompile assets before deploying to production, use this line 15 # If you precompile assets before deploying to production, use this line
14 Bundler.require(*Rails.groups(:assets => %w(development test))) 16 Bundler.require(*Rails.groups(:assets => %w(development test)))
config/initializers/issue_trackers.rb
@@ -1,7 +0,0 @@ @@ -1,7 +0,0 @@
1 -# Require all issue tracker apis in lib/issue_tracker_apis  
2 -Dir.glob(Rails.root.join('lib/issue_trackers/apis/*.rb')).each {|t| require t }  
3 -# Require issue tracker error classes  
4 -require Rails.root.join('lib/issue_trackers/errors')  
5 -  
6 -# Include nested issue tracker models  
7 -include IssueTrackers  
config/locales/en.yml
@@ -127,4 +127,16 @@ en: @@ -127,4 +127,16 @@ en:
127 watchers: Watchers 127 watchers: Watchers
128 when: When 128 when: When
129 who: Who 129 who: Who
  130 + issue_tracker_fields:
  131 + legend: Issue Tracker
  132 + new:
  133 + add_app: "Add App"
  134 + cancel: "cancel"
  135 + title: "Add App"
  136 + edit:
  137 + title: 'Edit App'
  138 + destroy: 'destroy application'
  139 + cancel: 'cancel'
  140 + seriously: 'Seriously?'
  141 + update: 'Update App'
130 142
db/migrate/20110812135951_move_issue_trackers_to_sti.rb
@@ -5,21 +5,27 @@ class MoveIssueTrackersToSti &lt; Mongoid::Migration @@ -5,21 +5,27 @@ class MoveIssueTrackersToSti &lt; Mongoid::Migration
5 # All issue trackers now subclass the IssueTracker model, 5 # All issue trackers now subclass the IssueTracker model,
6 # and their class is stored in the '_type' field, which is 6 # and their class is stored in the '_type' field, which is
7 # also aliased to 'type'. 7 # also aliased to 'type'.
8 - if app.issue_tracker && app.issue_tracker.attributes["issue_tracker_type"]  
9 - app.issue_tracker._type = case app.issue_tracker.issue_tracker_type  
10 - when 'lighthouseapp'; "LighthouseTracker"  
11 - when 'redmine'; "RedmineTracker"  
12 - when 'pivotal'; "PivotalLabsTracker"  
13 - when 'fogbugz'; "FogbugzTracker"  
14 - when 'mingle'; "MingleTracker" 8 + tracker = app.attributes['issue_tracker']
  9 + if tracker && tracker['issue_tracker_type']
  10 + tracker['_type'] = case tracker['issue_tracker_type']
  11 + when 'lighthouseapp'; "IssueTrackers::LighthouseTracker"
  12 + when 'redmine'; "IssueTrackers::RedmineTracker"
  13 + when 'pivotal'; "IssueTrackers::PivotalLabsTracker"
  14 + when 'fogbugz'; "IssueTrackers::FogbugzTracker"
  15 + when 'mingle'; "IssueTrackers::MingleTracker"
15 else; nil 16 else; nil
16 end 17 end
17 - if app.issue_tracker.issue_tracker_type == "none"  
18 - app.issue_tracker = nil 18 +
  19 + if tracker['issue_tracker_type'] == "none"
  20 + App.collection.where({ _id: app.id }).update({
  21 + "$unset" => { :issue_tracker => 1 }
  22 + })
19 else 23 else
20 - app.issue_tracker.issue_tracker_type = nil 24 + tracker.delete('issue_tracker_type')
  25 + App.collection.where({ _id: app.id }).update({
  26 + "$set" => { :issue_tracker => tracker }
  27 + })
21 end 28 end
22 - app.save  
23 end 29 end
24 end 30 end
25 end 31 end
db/migrate/20120603112130_change_github_url_to_github_repo.rb
1 class ChangeGithubUrlToGithubRepo < Mongoid::Migration 1 class ChangeGithubUrlToGithubRepo < Mongoid::Migration
  2 + def self.normalize_github_repo(repo)
  3 + return if repo.blank?
  4 + github_host = URI.parse(Errbit::Config.github_url).host
  5 + github_host = Regexp.escape(github_host)
  6 + repo.strip!
  7 + repo.sub!(/(git@|https?:\/\/)#{github_host}(\/|:)/, '')
  8 + repo.sub!(/\.git$/, '')
  9 + repo
  10 + end
  11 +
2 def self.up 12 def self.up
3 App.collection.find.update({'$rename' => {'github_url' => 'github_repo'}}, :multi => true, :safe => true) 13 App.collection.find.update({'$rename' => {'github_url' => 'github_repo'}}, :multi => true, :safe => true)
4 App.all.each do |app| 14 App.all.each do |app|
5 - app.send :normalize_github_repo  
6 - app.save 15 + normalized_repo = self.normalize_github_repo(app.attributes['github_repo'])
  16 + App.collection.where({ _id: app.id }).update({
  17 + "$set" => { :github_repo => normalized_repo }
  18 + })
7 end 19 end
8 end 20 end
9 21
db/migrate/20131011155638_extract_issue_tracker.rb 0 → 100644
@@ -0,0 +1,48 @@ @@ -0,0 +1,48 @@
  1 +class ExtractIssueTracker < Mongoid::Migration
  2 +
  3 + TRACKER_MAPPING = {
  4 + 'ErrbitTracPlugin::IssueTracker' => 'trac',
  5 + 'IssueTrackers::BitbucketIssuesTracker' => 'bitbucket',
  6 + 'IssueTrackers::FogbugzTracker' => 'fogbugz',
  7 + 'IssueTrackers::GithubIssuesTracker' => 'github',
  8 + 'IssueTrackers::GitlabTracker' => 'gitlab',
  9 + 'IssueTrackers::JiraTracker' => 'jira',
  10 + 'IssueTrackers::LighthouseTracker' => 'lighthouse',
  11 + 'IssueTrackers::PivotalLabsTracker' => 'pivotal',
  12 + 'IssueTrackers::RedmineTracker' => 'redmine',
  13 + 'IssueTrackers::UnfuddleTracker' => 'unfuddle'
  14 + }
  15 +
  16 + def self.up
  17 + App.all.each do |app|
  18 + require 'pry'
  19 + binding.pry
  20 + next unless app.attributes['issue_tracker'].present?
  21 + next unless app.attributes['issue_tracker']['_type'].present?
  22 +
  23 + options = app['issue_tracker'].dup
  24 + options.delete('_type')
  25 + options.delete('_id')
  26 +
  27 + _type = app.attributes['issue_tracker']['_type']
  28 + updated_at = options.delete('updated_at')
  29 + created_at = options.delete('created_at')
  30 +
  31 + if TRACKER_MAPPING.include?(_type)
  32 + tracker = {
  33 + 'type_tracker' => TRACKER_MAPPING[_type],
  34 + 'options' => options,
  35 + 'updated_at' => updated_at,
  36 + 'created_at' => created_at
  37 + }
  38 +
  39 + App.collection.where({ _id: app.id }).update({
  40 + "$set" => { :issue_tracker => tracker }
  41 + })
  42 + end
  43 + end
  44 + end
  45 +
  46 + def self.down
  47 + end
  48 +end
docs/DEVELOPER-ADVANCED.md
@@ -20,3 +20,12 @@ After you just need launch the script with adapting runner of mongoid. @@ -20,3 +20,12 @@ After you just need launch the script with adapting runner of mongoid.
20 20
21 In my case, the complete test suite down to 2min after a 16min long 21 In my case, the complete test suite down to 2min after a 16min long
22 before. 22 before.
  23 +
  24 +## Avoid running acceptance test with phantomjs
  25 +
  26 +Some acceptance test use phantomjs to interpret the Javascript in page.
  27 +To avoid this test you can launch your test by skipping js tag
  28 +
  29 +```
  30 +bundle exec rspec spec --tag="~js"
  31 +```
lib/issue_trackers/apis/mingle.rb
@@ -1,17 +0,0 @@ @@ -1,17 +0,0 @@
1 -require 'active_resource'  
2 -  
3 -module Mingle  
4 - class Card < ActiveResource::Base  
5 - # site template ~> "https://username:password@mingle.example.com/api/v1/projects/:project_id/"  
6 - self.format = :xml  
7 - end  
8 - def self.set_site(site)  
9 - # ActiveResource seems to clone and freeze the @site variable  
10 - # after the first use. It seems that the only way to change @site  
11 - # is to drop the subclass, and then reload it.  
12 - Mingle.send(:remove_const, :Card)  
13 - load File.join(Rails.root,'lib','issue_trackers', 'apis','mingle.rb')  
14 - Mingle::Card.site = site  
15 - end  
16 -end  
17 -  
lib/issue_trackers/apis/unfuddle.rb
@@ -1,13 +0,0 @@ @@ -1,13 +0,0 @@
1 -require 'active_resource'  
2 -  
3 -module Unfuddle  
4 - class Ticket < ActiveResource::Base  
5 - self.format = :xml  
6 - end  
7 -  
8 - def self.config(account, username, password)  
9 - Unfuddle::Ticket.site = "https://#{account}.unfuddle.com/api/v1/projects/:project_id"  
10 - Unfuddle::Ticket.user = username  
11 - Unfuddle::Ticket.password = password  
12 - end  
13 -end  
lib/issue_trackers/errors.rb
@@ -1,4 +0,0 @@ @@ -1,4 +0,0 @@
1 -module IssueTrackers  
2 - class IssueTrackerError < StandardError; end  
3 - class AuthenticationError < IssueTrackerError; end  
4 -end  
spec/acceptance/acceptance_helper.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 require 'capybara/rspec' 2 require 'capybara/rspec'
  3 +require 'capybara/poltergeist'
  4 +
  5 +Capybara.javascript_driver = :poltergeist
3 6
4 OmniAuth.config.test_mode = true 7 OmniAuth.config.test_mode = true
5 8
spec/acceptance/app_regenerate_api_key_spec.rb
1 require 'acceptance/acceptance_helper' 1 require 'acceptance/acceptance_helper'
2 2
3 feature "Regeneration api_Key" do 3 feature "Regeneration api_Key" do
4 - let!(:app) { Fabricate(:app) }  
5 - let!(:admin) { Fabricate(:admin) } 4 + let(:app) { Fabricate(:app) }
  5 + let(:admin) { Fabricate(:admin) }
6 let(:user) { 6 let(:user) {
7 Fabricate(:user_watcher, :app => app).user 7 Fabricate(:user_watcher, :app => app).user
8 } 8 }
9 9
  10 + before do
  11 + app && admin
  12 + end
  13 +
10 scenario "an admin change api_key" do 14 scenario "an admin change api_key" do
11 visit '/' 15 visit '/'
12 log_in admin 16 log_in admin
@@ -28,5 +32,65 @@ feature &quot;Regeneration api_Key&quot; do @@ -28,5 +32,65 @@ feature &quot;Regeneration api_Key&quot; do
28 click_link app.name if page.current_url != app_url(app) 32 click_link app.name if page.current_url != app_url(app)
29 expect(page).to_not have_button I18n.t('apps.show.edit') 33 expect(page).to_not have_button I18n.t('apps.show.edit')
30 end 34 end
  35 +end
  36 +
  37 +feature "Create an application" do
  38 +
  39 + let(:admin) { Fabricate(:admin) }
  40 + let(:user) {
  41 + Fabricate(:user_watcher, :app => app).user
  42 + }
  43 +
  44 + before do
  45 + admin
  46 + end
31 47
  48 + scenario "create an apps without issue tracker and edit it" do
  49 + visit '/'
  50 + log_in admin
  51 + click_on I18n.t('apps.index.new_app')
  52 + fill_in 'app_name', :with => 'My new app'
  53 + click_on I18n.t('apps.new.add_app')
  54 + page.has_content?(I18n.t('controllers.apps.flash.create.success'))
  55 + expect(App.where(:name => 'My new app').count).to eql 1
  56 + expect(App.where(:name => 'My new app 2').count).to eql 0
  57 +
  58 +
  59 + click_on I18n.t('shared.navigation.apps')
  60 + click_on 'My new app'
  61 + click_link I18n.t('apps.show.edit')
  62 + fill_in 'app_name', :with => 'My new app 2'
  63 + click_on I18n.t('apps.edit.update')
  64 + page.has_content?(I18n.t('controllers.apps.flash.update.success'))
  65 + expect(App.where(:name => 'My new app').count).to eql 0
  66 + expect(App.where(:name => 'My new app 2').count).to eql 1
  67 +
  68 + end
  69 +
  70 + scenario "create an apps with issue tracker and edit it", :js => true do
  71 + visit '/'
  72 + log_in admin
  73 + click_on I18n.t('apps.index.new_app')
  74 + fill_in 'app_name', :with => 'My new app'
  75 + find('.label_radio.github').click
  76 + within ".github.tracker_params" do
  77 + fill_in 'app_issue_tracker_attributes_options_username', :with => 'token'
  78 + fill_in 'app_issue_tracker_attributes_options_password', :with => 'pass'
  79 + end
  80 + click_on I18n.t('apps.new.add_app')
  81 + expect(page.has_content?(I18n.t('controllers.apps.flash.create.success'))).to eql true
  82 + app = App.where(:name => 'My new app').first
  83 + expect(app.issue_tracker.type_tracker).to eql 'github'
  84 + expect(app.issue_tracker.options['username']).to eql 'token'
  85 + expect(app.issue_tracker.options['password']).to eql 'pass'
  86 +
  87 + click_on I18n.t('shared.navigation.apps')
  88 + click_on 'My new app'
  89 + click_link I18n.t('apps.show.edit')
  90 + find('.issue_tracker .label_radio.none').click
  91 + click_on I18n.t('apps.edit.update')
  92 + expect(page.has_content?(I18n.t('controllers.apps.flash.update.success'))).to eql true
  93 + app = App.where(:name => 'My new app').first
  94 + expect(app.issue_tracker.tracker).to be_a ErrbitPlugin::NoneIssueTracker
  95 + end
32 end 96 end
spec/controllers/apps_controller_spec.rb
@@ -299,7 +299,7 @@ describe AppsController do @@ -299,7 +299,7 @@ describe AppsController do
299 context "unknown tracker type" do 299 context "unknown tracker type" do
300 before(:each) do 300 before(:each) do
301 put :update, :id => @app.id, :app => { :issue_tracker_attributes => { 301 put :update, :id => @app.id, :app => { :issue_tracker_attributes => {
302 - :type => 'unknown', :project_id => '1234', :api_token => '123123', :account => 'myapp' 302 + :type_tracker => 'unknown', :options => {:project_id => '1234', :api_token => '123123', :account => 'myapp'}
303 } } 303 } }
304 @app.reload 304 @app.reload
305 end 305 end
@@ -309,33 +309,35 @@ describe AppsController do @@ -309,33 +309,35 @@ describe AppsController do
309 end 309 end
310 end 310 end
311 311
312 - IssueTracker.subclasses.each do |tracker_klass|  
313 - context tracker_klass do 312 + ErrbitPlugin::Registry.issue_trackers.each do |key, klass|
  313 + context key do
314 it "should save tracker params" do 314 it "should save tracker params" do
315 - params = tracker_klass::Fields.inject({}){|hash,f| hash[f[0]] = "test_value"; hash }  
316 - params[:ticket_properties] = "card_type = defect" if tracker_klass == MingleTracker  
317 - params[:type] = tracker_klass.to_s 315 + params = {
  316 + :options => klass.fields.inject({}){|hash,f| hash[f[0]] = "test_value"; hash },
  317 + :type_tracker => key.dup.to_s
  318 + }
318 put :update, :id => @app.id, :app => {:issue_tracker_attributes => params} 319 put :update, :id => @app.id, :app => {:issue_tracker_attributes => params}
319 320
320 @app.reload 321 @app.reload
321 322
322 tracker = @app.issue_tracker 323 tracker = @app.issue_tracker
323 - expect(tracker).to be_a(tracker_klass)  
324 - tracker_klass::Fields.each do |field, field_info| 324 + expect(tracker.tracker).to be_a(ErrbitPlugin::Registry.issue_trackers[key])
  325 + klass.fields.each do |field, field_info|
325 case field 326 case field
326 - when :ticket_properties  
327 - expect(tracker.send(field.to_sym)).to eq 'card_type = defect'  
328 - else  
329 - expect(tracker.send(field.to_sym)).to eq 'test_value' 327 + when :ticket_properties; tracker.send(field.to_sym).should == 'card_type = defect'
  328 + else tracker.options[field.to_s].should == 'test_value'
330 end 329 end
331 end 330 end
332 end 331 end
333 332
334 it "should show validation notice when sufficient params are not present" do 333 it "should show validation notice when sufficient params are not present" do
335 # Leave out one required param 334 # Leave out one required param
336 - params = tracker_klass::Fields[1..-1].inject({}){|hash,f| hash[f[0]] = "test_value"; hash }  
337 - params[:type] = tracker_klass.to_s  
338 - put :update, :id => @app.id, :app => {:issue_tracker_attributes => params} 335 + # TODO. previous test was not relevant because one params can be enough. So put noone
  336 + put :update, :id => @app.id, :app => {
  337 + :issue_tracker_attributes => {
  338 + :type_tracker => key.dup.to_s
  339 + }
  340 + }
339 341
340 @app.reload 342 @app.reload
341 expect(@app.issue_tracker_configured?).to eq false 343 expect(@app.issue_tracker_configured?).to eq false
spec/controllers/problems_controller_spec.rb
@@ -8,15 +8,16 @@ describe ProblemsController do @@ -8,15 +8,16 @@ describe ProblemsController do
8 :params => {:app_id => 'dummyid', :id => 'dummyid'} 8 :params => {:app_id => 'dummyid', :id => 'dummyid'}
9 9
10 let(:app) { Fabricate(:app) } 10 let(:app) { Fabricate(:app) }
11 - let(:err) { Fabricate(:err, :problem => Fabricate(:problem, :app => app, :environment => "production")) } 11 + let(:err) { Fabricate(:err, :problem => problem) }
  12 + let(:admin) { Fabricate(:admin) }
  13 + let(:problem) { Fabricate(:problem, :app => app, :environment => "production") }
12 14
13 15
14 describe "GET /problems" do 16 describe "GET /problems" do
15 #render_views 17 #render_views
16 context 'when logged in as an admin' do 18 context 'when logged in as an admin' do
17 before(:each) do 19 before(:each) do
18 - @user = Fabricate(:admin)  
19 - sign_in @user 20 + sign_in admin
20 @problem = Fabricate(:notice, :err => Fabricate(:err, :problem => Fabricate(:problem, :app => app, :environment => "production"))).problem 21 @problem = Fabricate(:notice, :err => Fabricate(:err, :problem => Fabricate(:problem, :app => app, :environment => "production"))).problem
21 end 22 end
22 23
@@ -31,7 +32,7 @@ describe ProblemsController do @@ -31,7 +32,7 @@ describe ProblemsController do
31 end 32 end
32 33
33 it "should be able to override default per_page value" do 34 it "should be able to override default per_page value" do
34 - @user.update_attribute :per_page, 10 35 + admin.update_attribute :per_page, 10
35 get :index 36 get :index
36 expect(controller.problems.to_a.size).to eq 10 37 expect(controller.problems.to_a.size).to eq 10
37 end 38 end
@@ -98,7 +99,7 @@ describe ProblemsController do @@ -98,7 +99,7 @@ describe ProblemsController do
98 describe "GET /problems - previously all" do 99 describe "GET /problems - previously all" do
99 context 'when logged in as an admin' do 100 context 'when logged in as an admin' do
100 it "gets a paginated list of all problems" do 101 it "gets a paginated list of all problems" do
101 - sign_in Fabricate(:admin) 102 + sign_in admin
102 problems = Kaminari.paginate_array((1..30).to_a) 103 problems = Kaminari.paginate_array((1..30).to_a)
103 3.times { problems << Fabricate(:err).problem } 104 3.times { problems << Fabricate(:err).problem }
104 3.times { problems << Fabricate(:err, :problem => Fabricate(:problem, :resolved => true)).problem } 105 3.times { problems << Fabricate(:err, :problem => Fabricate(:problem, :resolved => true)).problem }
@@ -153,7 +154,7 @@ describe ProblemsController do @@ -153,7 +154,7 @@ describe ProblemsController do
153 154
154 context 'when logged in as an admin' do 155 context 'when logged in as an admin' do
155 before do 156 before do
156 - sign_in Fabricate(:admin) 157 + sign_in admin
157 end 158 end
158 159
159 it "finds the app" do 160 it "finds the app" do
@@ -217,7 +218,7 @@ describe ProblemsController do @@ -217,7 +218,7 @@ describe ProblemsController do
217 218
218 describe "PUT /apps/:app_id/problems/:id/resolve" do 219 describe "PUT /apps/:app_id/problems/:id/resolve" do
219 before do 220 before do
220 - sign_in Fabricate(:admin) 221 + sign_in admin
221 222
222 @problem = Fabricate(:err) 223 @problem = Fabricate(:err)
223 App.stub(:find).with(@problem.app.id.to_s).and_return(@problem.app) 224 App.stub(:find).with(@problem.app.id.to_s).and_return(@problem.app)
@@ -256,76 +257,50 @@ describe ProblemsController do @@ -256,76 +257,50 @@ describe ProblemsController do
256 end 257 end
257 258
258 describe "POST /apps/:app_id/problems/:id/create_issue" do 259 describe "POST /apps/:app_id/problems/:id/create_issue" do
259 - #render_views  
260 260
261 before(:each) do 261 before(:each) do
262 - sign_in Fabricate(:admin) 262 + sign_in admin
263 end 263 end
264 264
265 context "successful issue creation" do 265 context "successful issue creation" do
266 context "lighthouseapp tracker" do 266 context "lighthouseapp tracker" do
267 let(:notice) { Fabricate :notice } 267 let(:notice) { Fabricate :notice }
268 - let(:tracker) { Fabricate :lighthouse_tracker, :app => notice.app }  
269 let(:problem) { notice.problem } 268 let(:problem) { notice.problem }
270 269
271 before(:each) do 270 before(:each) do
272 - number = 5  
273 - @issue_link = "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets/#{number}.xml"  
274 - body = "<ticket><number type=\"integer\">#{number}</number></ticket>"  
275 - stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml").  
276 - to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )  
277 - 271 + controller.stub(:problem).and_return(problem)
  272 + controller.stub(:current_user).and_return(admin)
  273 + IssueCreation.should_receive(:new).with(problem, admin, nil, request).and_return(double(:execute => true))
278 post :create_issue, :app_id => problem.app.id, :id => problem.id 274 post :create_issue, :app_id => problem.app.id, :id => problem.id
279 - problem.reload  
280 end 275 end
281 276
282 it "should redirect to problem page" do 277 it "should redirect to problem page" do
283 expect(response).to redirect_to( app_problem_path(problem.app, problem) ) 278 expect(response).to redirect_to( app_problem_path(problem.app, problem) )
  279 + expect(flash[:error]).to be_blank
284 end 280 end
285 end 281 end
286 end 282 end
287 283
288 - context "absent issue tracker" do  
289 - let(:problem) { Fabricate :problem }  
290 - 284 + context "error during request to a tracker" do
291 before(:each) do 285 before(:each) do
  286 + IssueCreation.should_receive(:new).with(problem, admin, nil, request).and_return(
  287 + double(:execute => false, :errors => double(:full_messages => ['not create']))
  288 + )
  289 + controller.stub(:problem).and_return(problem)
292 post :create_issue, :app_id => problem.app.id, :id => problem.id 290 post :create_issue, :app_id => problem.app.id, :id => problem.id
293 end 291 end
294 292
295 it "should redirect to problem page" do 293 it "should redirect to problem page" do
296 expect(response).to redirect_to( app_problem_path(problem.app, problem) ) 294 expect(response).to redirect_to( app_problem_path(problem.app, problem) )
297 - end  
298 -  
299 - it "should set flash error message telling issue tracker of the app doesn't exist" do  
300 - expect(flash[:error]).to eq "This app has no issue tracker setup." 295 + expect(flash[:error]).to eql 'not create'
301 end 296 end
302 end 297 end
303 298
304 - context "error during request to a tracker" do  
305 - context "lighthouseapp tracker" do  
306 - let(:tracker) { Fabricate :lighthouse_tracker }  
307 - let(:err) { Fabricate(:err, :problem => Fabricate(:problem, :app => tracker.app)) }  
308 -  
309 - before(:each) do  
310 - stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml").to_return(:status => 500)  
311 -  
312 - post :create_issue, :app_id => err.app.id, :id => err.problem.id  
313 - end  
314 -  
315 - it "should redirect to problem page" do  
316 - expect(response).to redirect_to( app_problem_path(err.app, err.problem) )  
317 - end  
318 -  
319 - it "should notify of connection error" do  
320 - expect(flash[:error]).to include("There was an error during issue creation:")  
321 - end  
322 - end  
323 - end  
324 end 299 end
325 300
326 describe "DELETE /apps/:app_id/problems/:id/unlink_issue" do 301 describe "DELETE /apps/:app_id/problems/:id/unlink_issue" do
327 before(:each) do 302 before(:each) do
328 - sign_in Fabricate(:admin) 303 + sign_in admin
329 end 304 end
330 305
331 context "problem with issue" do 306 context "problem with issue" do
@@ -361,7 +336,7 @@ describe ProblemsController do @@ -361,7 +336,7 @@ describe ProblemsController do
361 336
362 describe "Bulk Actions" do 337 describe "Bulk Actions" do
363 before(:each) do 338 before(:each) do
364 - sign_in Fabricate(:admin) 339 + sign_in admin
365 @problem1 = Fabricate(:err, :problem => Fabricate(:problem, :resolved => true)).problem 340 @problem1 = Fabricate(:err, :problem => Fabricate(:problem, :resolved => true)).problem
366 @problem2 = Fabricate(:err, :problem => Fabricate(:problem, :resolved => false)).problem 341 @problem2 = Fabricate(:err, :problem => Fabricate(:problem, :resolved => false)).problem
367 end 342 end
spec/decorators/app_decorator_spec.rb 0 → 100644
@@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
  1 +require 'spec_helper'
  2 +
  3 +describe AppDecorator do
  4 +
  5 + describe "#email_at_notices" do
  6 +
  7 + it 'return the list separate by comma' do
  8 + expect(AppDecorator.new(double(:email_at_notices => [2,3])).email_at_notices).to eql '2, 3'
  9 + end
  10 +
  11 + end
  12 +
  13 + describe "#notify_user_display" do
  14 +
  15 + it 'return display:none if notify' do
  16 + expect(AppDecorator.new(double(:notify_all_users => true)).notify_user_display).to eql 'display: none;'
  17 + end
  18 +
  19 + it 'return blank if no notify' do
  20 + expect(AppDecorator.new(double(:notify_all_users => false)).notify_user_display).to eql ''
  21 + end
  22 +
  23 + end
  24 +
  25 + describe "#notify_err_display" do
  26 +
  27 + it 'return display:none if no notify' do
  28 + expect(AppDecorator.new(double(:notify_on_errs => false)).notify_err_display).to eql 'display: none;'
  29 + end
  30 +
  31 + it 'return blank if no notify' do
  32 + expect(AppDecorator.new(double(:notify_on_errs => true)).notify_err_display).to eql ''
  33 + end
  34 +
  35 + end
  36 +
  37 +end
spec/decorators/issue_tracker_decorator_spec.rb 0 → 100644
@@ -0,0 +1,55 @@ @@ -0,0 +1,55 @@
  1 +require 'spec_helper'
  2 +
  3 +describe IssueTrackerDecorator do
  4 + let(:fake_tracker) do
  5 + Class.new(ErrbitPlugin::IssueTracker) do
  6 + def self.label; 'fake'; end
  7 + def self.note; 'a note'; end
  8 + def self.fields
  9 + {
  10 + :foo => {:label => 'foo'},
  11 + :bar => {:label => 'bar'}
  12 + }
  13 + end
  14 +
  15 + def configured?; true; end
  16 + end
  17 + end
  18 +
  19 + let(:decorator) do
  20 + IssueTrackerDecorator.new(fake_tracker, 'fake')
  21 + end
  22 +
  23 + describe "#note" do
  24 + it 'return the html_safe of Note' do
  25 + expect(decorator.note).to eql fake_tracker.note
  26 + end
  27 + end
  28 +
  29 + describe "#issue_trackers" do
  30 + it 'return an array of IssueTrackerDecorator' do
  31 + decorator.issue_trackers do |it|
  32 + expect(it).to be_a(IssueTrackerDecorator)
  33 + end
  34 + end
  35 + end
  36 +
  37 + describe "#fields" do
  38 + it 'return all Fields define decorate' do
  39 + decorator.fields do |itf|
  40 + expect(itf).to be_a(IssueTrackerFieldDecorator)
  41 + expect([:foo, :bar]).to be_include(itf.object)
  42 + expect([{:label => 'foo'}, {:label => 'bar'}]).to be_include(itf.field_info)
  43 + end
  44 + end
  45 + end
  46 +
  47 + describe "#params_class" do
  48 + it 'add the label in class' do
  49 + expect(decorator.params_class(IssueTracker.new(:type_tracker => 'none'))).to eql 'fake'
  50 + end
  51 + it 'add chosen class if _type is same' do
  52 + expect(decorator.params_class(IssueTracker.new(:type_tracker => 'fake'))).to eql 'chosen fake'
  53 + end
  54 + end
  55 +end
spec/decorators/issue_tracker_field_decorator.rb 0 → 100644
@@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
  1 +require 'spec_helper'
  2 +
  3 +describe IssueTrackerFieldDecorator do
  4 +
  5 + describe "#label" do
  6 + it 'return the label of field_info by default' do
  7 + expect(IssueTrackerFieldDecorator.new(:foo, {:label => 'hello'}).label).to eq 'hello'
  8 + end
  9 + it 'return the key of field if no label define' do
  10 + expect(IssueTrackerFieldDecorator.new(:foo, {}).label).to eq 'foo'
  11 + end
  12 + end
  13 +
  14 +end
spec/decorators/watcher_decorator_spec.rb 0 → 100644
@@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
  1 +require 'spec_helper'
  2 +
  3 +describe WatcherDecorator do
  4 + describe "#email_choosen" do
  5 + context "with email define" do
  6 + it 'return blank' do
  7 + expect(WatcherDecorator.new(double(:email => 'foo')).email_choosen).to eql ''
  8 + end
  9 + end
  10 +
  11 + context "without email define" do
  12 + it 'return choosen' do
  13 + expect(WatcherDecorator.new(double(:email => '')).email_choosen).to eql 'chosen'
  14 + end
  15 + end
  16 + end
  17 +end
spec/fabricators/issue_tracker_fabricator.rb
1 Fabricator :issue_tracker do 1 Fabricator :issue_tracker do
2 - app  
3 - api_token { sequence :word }  
4 - project_id { sequence :word }  
5 - account { sequence :word }  
6 - username { sequence :word }  
7 - password { sequence :word }  
8 -end  
9 -  
10 -%w(lighthouse pivotal_labs fogbugz).each do |t|  
11 - Fabricator "#{t}_tracker".to_sym, :from => :issue_tracker, :class_name => "IssueTrackers::#{t.camelcase}Tracker"  
12 -end  
13 -  
14 -Fabricator :redmine_tracker, :from => :issue_tracker, :class_name => "IssueTrackers::RedmineTracker" do  
15 - account 'http://redmine.example.com'  
16 -end  
17 -  
18 -Fabricator :gitlab_tracker, :from => :issue_tracker, :class_name => "IssueTrackers::GitlabTracker" do  
19 - account 'http://gitlab.example.com'  
20 - alt_project_id 'foo'  
21 -end 2 + type_tracker 'fake'
  3 + options {{
  4 + :foo => 'one',
  5 + :bar => 'two'
  6 + }}
22 7
23 -Fabricator :mingle_tracker, :from => :issue_tracker, :class_name => "IssueTrackers::MingleTracker" do  
24 - account 'https://mingle.example.com'  
25 - ticket_properties 'card_type = Defect, defect_status = open, priority = essential'  
26 -end  
27 -  
28 -Fabricator :github_issues_tracker, :from => :issue_tracker, :class_name => "IssueTrackers::GithubIssuesTracker" do  
29 - project_id 'test_account/test_project'  
30 - username 'test_username'  
31 -end  
32 -  
33 -Fabricator :bitbucket_issues_tracker, :from => :issue_tracker, :class_name => "IssueTrackers::BitbucketIssuesTracker" do  
34 - project_id 'password'  
35 - api_token 'test_username'  
36 -end  
37 -  
38 -Fabricator :unfuddle_issues_tracker, :from => :issue_tracker, :class_name => "IssueTrackers::UnfuddleTracker" do  
39 - account 'test'  
40 - project_id 15 8 + app
41 end 9 end
spec/interactors/issue_creation_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 describe IssueCreation do 3 describe IssueCreation do
4 - subject(:issue_creation) { IssueCreation.new(problem, user, tracker_name) } 4 + subject(:issue_creation) do
  5 + IssueCreation.new(problem, user, tracker_name, request)
  6 + end
5 7
  8 + let(:request) do
  9 + double(:request,
  10 + :host => 'github.com',
  11 + :port => '80',
  12 + :scheme => 'http'
  13 + )
  14 + end
6 let(:problem) { notice.problem } 15 let(:problem) { notice.problem }
7 let(:notice) { Fabricate(:notice) } 16 let(:notice) { Fabricate(:notice) }
8 let(:user) { Fabricate(:admin) } 17 let(:user) { Fabricate(:admin) }
@@ -15,8 +24,7 @@ describe IssueCreation do @@ -15,8 +24,7 @@ describe IssueCreation do
15 end 24 end
16 25
17 it 'creates an issue if issue tracker is configured' do 26 it 'creates an issue if issue tracker is configured' do
18 - tracker = Fabricate(:lighthouse_tracker, :app => notice.app)  
19 - expect(tracker).to receive(:create_issue) 27 + problem.app.issue_tracker = Fabricate(:issue_tracker)
20 issue_creation.execute 28 issue_creation.execute
21 expect(errors).to be_empty 29 expect(errors).to be_empty
22 end 30 end
@@ -44,7 +52,9 @@ describe IssueCreation do @@ -44,7 +52,9 @@ describe IssueCreation do
44 user.github_oauth_token = 'oauthtoken' 52 user.github_oauth_token = 'oauthtoken'
45 user.save! 53 user.save!
46 54
47 - GithubIssuesTracker.any_instance.should_receive(:create_issue) 55 + ErrbitGithubPlugin::IssueTracker.should_receive(:new).and_return(
  56 + double(:create_issue => true)
  57 + )
48 issue_creation.execute 58 issue_creation.execute
49 expect(errors).to be_empty 59 expect(errors).to be_empty
50 end 60 end
spec/interactors/problem_updater_cache_spec.rb
@@ -74,7 +74,7 @@ describe ProblemUpdaterCache do @@ -74,7 +74,7 @@ describe ProblemUpdaterCache do
74 end 74 end
75 75
76 it 'update last_notice_at' do 76 it 'update last_notice_at' do
77 - expect(problem.last_notice_at.to_i).to be_within(1).of(notice.created_at.to_i) 77 + expect(problem.last_notice_at.to_i).to be_within(2).of(notice.created_at.to_i)
78 end 78 end
79 79
80 it 'update stats messages' do 80 it 'update stats messages' do
spec/models/app_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 describe App do 3 describe App do
  4 +
4 context "Attributes" do 5 context "Attributes" do
5 it { should have_field(:_id).of_type(String) } 6 it { should have_field(:_id).of_type(String) }
6 it { should have_field(:name).of_type(String) } 7 it { should have_field(:name).of_type(String) }
@@ -220,5 +221,26 @@ describe App do @@ -220,5 +221,26 @@ describe App do
220 end 221 end
221 end 222 end
222 223
  224 + describe "#comments_allowed?" do
  225 + context "without issue_tracker" do
  226 + it 'return true' do
  227 + expect(App.new.comments_allowed?).to be_true
  228 + end
  229 + end
  230 +
  231 + context "with issue_tracker" do
  232 + let(:issue_tracker) do
  233 + ist = IssueTracker.new
  234 + ist.stub(:comments_allowed?).and_return('foo')
  235 + ist
  236 + end
  237 + it 'delegate to issue_tracker' do
  238 + expect(App.new(
  239 + :issue_tracker => issue_tracker
  240 + ).comments_allowed?).to eql 'foo'
  241 + end
  242 + end
  243 + end
  244 +
223 end 245 end
224 246
spec/models/issue_tracker_spec.rb 0 → 100644
@@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
  1 +require 'spec_helper'
  2 +
  3 +describe IssueTracker do
  4 + describe "Association" do
  5 + it { should be_embedded_in(:app) }
  6 + end
  7 +
  8 + describe "Attributes" do
  9 + it { should have_field(:type_tracker).of_type(String) }
  10 + it { should have_field(:options).of_type(Hash).with_default_value_of({}) }
  11 + end
  12 +
  13 + describe "#tracker" do
  14 + context "with type_tracker class not exist" do
  15 + it 'return NullIssueTracker' do
  16 + expect(IssueTracker.new(:type_tracker => 'Foo').tracker).to be_a ErrbitPlugin::NoneIssueTracker
  17 + end
  18 + end
  19 + end
  20 +end
spec/models/issue_trackers/bitbucket_issues_tracker_spec.rb
@@ -1,40 +0,0 @@ @@ -1,40 +0,0 @@
1 -require 'spec_helper'  
2 -  
3 -describe IssueTrackers::BitbucketIssuesTracker do  
4 - it "should create an issue on BitBucket Issues with problem params, and set issue link for problem" do  
5 - repo = "test_user/test_repo"  
6 - notice = Fabricate :notice  
7 - notice.app.bitbucket_repo = repo  
8 - tracker = Fabricate :bitbucket_issues_tracker, :app => notice.app  
9 - problem = notice.problem  
10 -  
11 - number = 123  
12 - @issue_link = "https://bitbucket.org/#{repo}/issue/#{number}/"  
13 - body = <<EOF  
14 -{  
15 - "status": "new",  
16 - "priority": "critical",  
17 - "title": "[production][foo#bar] FooError: Too Much Bar",  
18 - "comment_count": 0,  
19 - "content": "This is the content",  
20 - "created_on": "2012-07-29 04:35:38",  
21 - "local_id": 123,  
22 - "follower_count": 0,  
23 - "utc_created_on": "2012-07-29 02:35:38+00:00",  
24 - "resource_uri": "/1.0/repositories/test_user/test_repo/issue/123/",  
25 - "is_spam": false  
26 -}  
27 -EOF  
28 -  
29 - stub_request(:post, "https://#{tracker.api_token}:#{tracker.project_id}@bitbucket.org/api/1.0/repositories/test_user/test_repo/issues/").to_return(:status => 200, :headers => {}, :body => body )  
30 -  
31 - problem.app.issue_tracker.create_issue(problem)  
32 - problem.reload  
33 -  
34 - requested = have_requested(:post, "https://#{tracker.api_token}:#{tracker.project_id}@bitbucket.org/api/1.0/repositories/test_user/test_repo/issues/")  
35 - expect(WebMock).to requested.with(:title => /[production][foo#bar] FooError: Too Much Bar/)  
36 - expect(WebMock).to requested.with(:content => /See this exception on Errbit/)  
37 -  
38 - expect(problem.issue_link).to eq @issue_link  
39 - end  
40 -end  
spec/models/issue_trackers/fogbugz_tracker_spec.rb
@@ -1,23 +0,0 @@ @@ -1,23 +0,0 @@
1 -require 'spec_helper'  
2 -  
3 -describe IssueTrackers::FogbugzTracker do  
4 - it "should create an issue on Fogbugz with problem params, and set issue link for problem" do  
5 - notice = Fabricate :notice  
6 - tracker = Fabricate :fogbugz_tracker, :app => notice.app  
7 - problem = notice.problem  
8 -  
9 - number = 123  
10 - @issue_link = "https://#{tracker.account}.fogbugz.com/default.asp?#{number}"  
11 - response = "<response><token>12345</token><case><ixBug>123</ixBug></case></response>"  
12 - http_mock = double  
13 - expect(http_mock).to receive(:new).and_return(http_mock)  
14 - expect(http_mock).to receive(:request).twice.and_return(response)  
15 - Fogbugz.adapter[:http] = http_mock  
16 -  
17 - problem.app.issue_tracker.create_issue(problem)  
18 - problem.reload  
19 -  
20 - expect(problem.issue_link).to eq @issue_link  
21 - end  
22 -end  
23 -  
spec/models/issue_trackers/github_issues_tracker_spec.rb
@@ -1,73 +0,0 @@ @@ -1,73 +0,0 @@
1 -require 'spec_helper'  
2 -  
3 -describe IssueTrackers::GithubIssuesTracker do  
4 -  
5 - let(:repo) { "test_user/test_repo" }  
6 -  
7 - let(:notice) do  
8 - Fabricate :notice  
9 - end  
10 -  
11 - let(:problem) do  
12 - notice.problem  
13 - end  
14 -  
15 - let!(:tracker) do  
16 - notice.app.github_repo = repo  
17 - Fabricate :github_issues_tracker, app: notice.app  
18 - end  
19 -  
20 - let(:number) { 5 }  
21 - let(:issue_link) { "https://github.com/#{repo}/issues/#{number}" }  
22 - let(:body) do  
23 - <<EOF  
24 -{  
25 - "position": 1.0,  
26 - "number": #{number},  
27 - "votes": 0,  
28 - "created_at": "2010/01/21 13:45:59 -0800",  
29 - "comments": 0,  
30 - "body": "Test Body",  
31 - "title": "Test Issue",  
32 - "user": "test_user",  
33 - "state": "open",  
34 - "html_url": "#{issue_link}"  
35 -}  
36 -EOF  
37 -end  
38 -  
39 - it "should create an issue on GitHub Issues with problem params, and set issue link for problem" do  
40 - stub_request(:post,  
41 - "https://#{tracker.username}:#{tracker.password}@api.github.com/repos/#{repo}/issues").  
42 - to_return(:status => 201,  
43 - :headers => {  
44 - 'Location' => issue_link,  
45 - 'Content-Type' => 'application/json',  
46 - },  
47 - :body => body )  
48 -  
49 - problem.app.issue_tracker.create_issue(problem)  
50 - problem.reload  
51 -  
52 - requested = have_requested(:post, "https://#{tracker.username}:#{tracker.password}@api.github.com/repos/#{repo}/issues")  
53 - expect(WebMock).to requested.with(:body => /[production][foo#bar] FooError: Too Much Bar/)  
54 - expect(WebMock).to requested.with(:body => /See this exception on Errbit/)  
55 -  
56 - expect(problem.issue_link).to eq issue_link  
57 - end  
58 -  
59 - it "should create an issue with oauth token" do  
60 - issue_tracker = problem.app.issue_tracker  
61 - issue_tracker.oauth_token = 'secret_token'  
62 -  
63 - stub_request(:post, "https://api.github.com/repos/#{repo}/issues").  
64 - to_return({  
65 - status: 201,  
66 - headers: {'Location' => issue_link, 'Content-Type' => 'application/json' },  
67 - body: body })  
68 -  
69 - issue_tracker.create_issue(problem)  
70 - requested = have_requested(:post, "https://api.github.com/repos/#{repo}/issues")  
71 - expect(WebMock).to requested.with(headers: {'Authorization'=>'token secret_token'})  
72 - end  
73 -end  
spec/models/issue_trackers/gitlab_issues_tracker_spec.rb
@@ -1,32 +0,0 @@ @@ -1,32 +0,0 @@
1 -require 'spec_helper'  
2 -  
3 -describe IssueTrackers::GitlabTracker do  
4 - it "should create an issue on Gitlab with problem params" do  
5 - notice = Fabricate :notice  
6 - tracker = Fabricate :gitlab_tracker, :app => notice.app  
7 - problem = notice.problem  
8 -  
9 - issue_id = 5  
10 - note_id = 10  
11 - issue_body = {:id => issue_id, :title => "[production][foo#bar] FooError: Too Much Bar", :description => "[See this exception on Errbit]"}.to_json  
12 - note_body = {:id => note_id, :body => "Example note body"}.to_json  
13 -  
14 - stub_request(:post, "#{tracker.account}/api/v3/projects/#{tracker.project_id}/issues?private_token=#{tracker.api_token}").  
15 - with(:body => /.+/, :headers => {'Accept'=>'application/json'}).  
16 - to_return(:status => 200, :body => issue_body, :headers => {'Accept'=>'application/json'})  
17 -  
18 - stub_request(:post, "#{tracker.account}/api/v3/projects/#{tracker.project_id}/issues/#{issue_id}/notes?private_token=#{tracker.api_token}").  
19 - with(:body => /.+/, :headers => {'Accept'=>'application/json'}).  
20 - to_return(:status => 200, :body => note_body, :headers => {'Accept'=>'application/json'})  
21 -  
22 - problem.app.issue_tracker.create_issue(problem)  
23 - problem.reload  
24 -  
25 - requested_issue = have_requested(:post, "#{tracker.account}/api/v3/projects/#{tracker.project_id}/issues?private_token=#{tracker.api_token}").with(:body => /.+/, :headers => {'Accept'=>'application/json'})  
26 - requested_note = have_requested(:post, "#{tracker.account}/api/v3/projects/#{tracker.project_id}/issues/#{issue_id}/notes?private_token=#{tracker.api_token}")  
27 - expect(WebMock).to requested_issue.with(:body => /%5Bproduction%5D%5Bfoo%23bar%5D%20FooError%3A%20Too%20Much%20Bar/)  
28 - expect(WebMock).to requested_issue.with(:body => /See%20this%20exception%20on%20Errbit/)  
29 -  
30 - end  
31 -end  
32 -  
spec/models/issue_trackers/lighthouse_tracker_spec.rb
@@ -1,27 +0,0 @@ @@ -1,27 +0,0 @@
1 -require 'spec_helper'  
2 -  
3 -describe IssueTrackers::LighthouseTracker do  
4 - it "should create an issue on Lighthouse with problem params, and set issue link for problem" do  
5 - notice = Fabricate :notice  
6 - tracker = Fabricate :lighthouse_tracker, :app => notice.app  
7 - problem = notice.problem  
8 -  
9 - number = 5  
10 - @issue_link = "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets/#{number}.xml"  
11 - body = "<ticket><number type=\"integer\">#{number}</number></ticket>"  
12 - stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml").  
13 - to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )  
14 -  
15 - problem.app.issue_tracker.create_issue(problem)  
16 - problem.reload  
17 -  
18 - requested = have_requested(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml")  
19 - expect(WebMock).to requested.with(:headers => {'X-Lighthousetoken' => tracker.api_token})  
20 - expect(WebMock).to requested.with(:body => /<tag>errbit<\/tag>/)  
21 - expect(WebMock).to requested.with(:body => /<title>\[#{ problem.environment }\]\[#{problem.where}\] #{problem.message.to_s.truncate(100)}<\/title>/)  
22 - expect(WebMock).to requested.with(:body => /<body>.+<\/body>/m)  
23 -  
24 - expect(problem.issue_link).to eq @issue_link.sub(/\.xml$/, '')  
25 - end  
26 -end  
27 -  
spec/models/issue_trackers/mingle_tracker_spec.rb
@@ -1,28 +0,0 @@ @@ -1,28 +0,0 @@
1 -require 'spec_helper'  
2 -  
3 -describe IssueTrackers::MingleTracker do  
4 - it "should create an issue on Mingle with problem params, and set issue link for problem" do  
5 - notice = Fabricate :notice  
6 - tracker = Fabricate :mingle_tracker, :app => notice.app  
7 - problem = notice.problem  
8 -  
9 - number = 5  
10 - @issue_link = "#{tracker.account}/projects/#{tracker.project_id}/cards/#{number}.xml"  
11 - @basic_auth = tracker.account.gsub("://", "://#{tracker.username}:#{tracker.password}@")  
12 - body = "<card><id type=\"integer\">#{number}</id></card>"  
13 - stub_request(:post, "#{@basic_auth}/api/v1/projects/#{tracker.project_id}/cards.xml").  
14 - to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )  
15 -  
16 - problem.app.issue_tracker.create_issue(problem)  
17 - problem.reload  
18 -  
19 - requested = have_requested(:post, "#{@basic_auth}/api/v1/projects/#{tracker.project_id}/cards.xml")  
20 - expect(WebMock).to requested.with(:headers => {'Content-Type' => 'application/xml'})  
21 - expect(WebMock).to requested.with(:body => /FooError: Too Much Bar/)  
22 - expect(WebMock).to requested.with(:body => /See this exception on Errbit/)  
23 - expect(WebMock).to requested.with(:body => /<card-type-name>Defect<\/card-type-name>/)  
24 -  
25 - expect(problem.issue_link).to eq @issue_link.sub(/\.xml$/, '')  
26 - end  
27 -end  
28 -  
spec/models/issue_trackers/pivotal_labs_tracker_spec.rb
@@ -1,46 +0,0 @@ @@ -1,46 +0,0 @@
1 -require 'spec_helper'  
2 -  
3 -describe IssueTrackers::PivotalLabsTracker do  
4 -  
5 - let(:user) { Fabricate(:user) }  
6 - let(:notice) { Fabricate(:notice) }  
7 - let(:tracker) { Fabricate :pivotal_labs_tracker, :app => notice.app, :project_id => 10 }  
8 - let(:problem) { notice.problem }  
9 - let(:story_id) { 5 }  
10 - let(:issue_link) { "https://www.pivotaltracker.com/story/show/#{story_id}" }  
11 -  
12 - it "creates an issue on Pivotal Tracker with problem params, and set issue link for problem" do  
13 - project_body = "<project><id>#{tracker.project_id}</id><name>TestProject</name></project>"  
14 - stub_request(:get, "https://www.pivotaltracker.com/services/v3/projects/#{tracker.project_id}").  
15 - to_return(:status => 200, :headers => {'Location' => issue_link}, :body => project_body )  
16 - story_body = "<story><name>Test Story</name><id>#{story_id}</id></story>"  
17 - stub_request(:post, "https://www.pivotaltracker.com/services/v3/projects/#{tracker.project_id}/stories").  
18 - to_return(:status => 201, :headers => {'Location' => issue_link}, :body => story_body )  
19 -  
20 - problem.app.issue_tracker.create_issue(problem, user)  
21 - problem.reload  
22 -  
23 - requested = have_requested(:post, "https://www.pivotaltracker.com/services/v3/projects/#{tracker.project_id}/stories")  
24 - expect(WebMock).to requested.with(:headers => {'X-Trackertoken' => tracker.api_token})  
25 - expect(WebMock).to requested.with(:body => /See this exception on Errbit/)  
26 - expect(WebMock).to requested.with(:body => /<name>\[#{ problem.environment }\]\[#{problem.where}\] #{problem.message.to_s.truncate(100)}<\/name>/)  
27 - expect(WebMock).to requested.with(:body => /<description>.+<\/description>/m)  
28 -  
29 - expect(problem.issue_link).to eq issue_link  
30 - end  
31 -  
32 - it "raises IssueTrackers::IssueTrackerError exception when invalid params and does not set issue link for problem" do  
33 - project_body = "<project><id>#{tracker.project_id}</id><name>TestProject</name></project>"  
34 - stub_request(:get, "https://www.pivotaltracker.com/services/v3/projects/#{tracker.project_id}").  
35 - to_return(:status => 200, :body => project_body )  
36 - story_body = "<errors><error>Requested by can't be blank</error><error>Requested by can't be blank</error></errors>"  
37 - stub_request(:post, "https://www.pivotaltracker.com/services/v3/projects/#{tracker.project_id}/stories").  
38 - to_return(:status => 422, :body => story_body )  
39 -  
40 - expect{  
41 - problem.app.issue_tracker.create_issue(problem, user)  
42 - }.to raise_exception(IssueTrackers::IssueTrackerError, "Requested by can't be blank")  
43 - expect(problem.issue_link).to be_nil  
44 - end  
45 -end  
46 -  
spec/models/issue_trackers/redmine_tracker_spec.rb
@@ -1,45 +0,0 @@ @@ -1,45 +0,0 @@
1 -require 'spec_helper'  
2 -  
3 -describe IssueTrackers::RedmineTracker do  
4 - it "should create an issue on Redmine with problem params, and set issue link for problem" do  
5 - notice = Fabricate(:notice)  
6 - tracker = Fabricate(:redmine_tracker, :app => notice.app, :project_id => 10)  
7 - problem = notice.problem  
8 - number = 5  
9 - @issue_link = "#{tracker.account}/issues/#{number}.xml?project_id=#{tracker.project_id}"  
10 - body = "<issue><subject>my subject</subject><id>#{number}</id></issue>"  
11 -  
12 - # Build base url with account URL, and username/password basic auth  
13 - base_url = tracker.account.gsub 'http://', "http://#{tracker.username}:#{tracker.password}@"  
14 -  
15 - stub_request(:post, "#{base_url}/issues.xml").  
16 - to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )  
17 -  
18 - problem.app.issue_tracker.create_issue(problem)  
19 - problem.reload  
20 -  
21 - requested = have_requested(:post, "#{base_url}/issues.xml")  
22 - expect(WebMock).to requested.with(:headers => {'X-Redmine-API-Key' => tracker.api_token})  
23 - expect(WebMock).to requested.with(:body => /<project-id>#{tracker.project_id}<\/project-id>/)  
24 - expect(WebMock).to requested.with(:body => /<subject>\[#{ problem.environment }\]\[#{problem.where}\] #{problem.message.to_s.truncate(100)}<\/subject>/)  
25 - expect(WebMock).to requested.with(:body => /<description>.+<\/description>/m)  
26 -  
27 - expect(problem.issue_link).to eq @issue_link.sub(/\.xml/, '')  
28 - end  
29 -  
30 - it "should generate a url where a file with line number can be viewed" do  
31 - t = Fabricate(:redmine_tracker, :account => 'http://redmine.example.com', :project_id => "errbit")  
32 - expect(t.url_to_file("/example/file")).  
33 - to eq 'http://redmine.example.com/projects/errbit/repository/revisions/master/entry/example/file'  
34 - expect(t.url_to_file("/example/file", 25)).  
35 - to eq 'http://redmine.example.com/projects/errbit/repository/revisions/master/entry/example/file#L25'  
36 - end  
37 -  
38 - it "should use the alt_project_id to generate a file/linenumber url, if given" do  
39 - t = Fabricate(:redmine_tracker, :account => 'http://redmine.example.com',  
40 - :project_id => "errbit",  
41 - :alt_project_id => "actual_project")  
42 - expect(t.url_to_file("/example/file", 25)).  
43 - to eq 'http://redmine.example.com/projects/actual_project/repository/revisions/master/entry/example/file#L25'  
44 - end  
45 -end  
spec/models/issue_trackers/unfuddle_issues_tracker_spec.rb
@@ -1,92 +0,0 @@ @@ -1,92 +0,0 @@
1 -require 'spec_helper'  
2 -  
3 -describe IssueTrackers::UnfuddleTracker do  
4 -  
5 - let(:issue_link) { "https://test.unfuddle.com/projects/15/tickets/2436" }  
6 - let(:notice) { Fabricate :notice }  
7 - let(:tracker) { Fabricate :unfuddle_issues_tracker, :app => notice.app }  
8 - let(:problem) { notice.problem }  
9 -  
10 - it "should create an issue on Unfuddle Issues with problem params, and set issue link for problem" do  
11 -project_xml = <<EOF  
12 -<?xml version="1.0" encoding="UTF-8"?>  
13 -<project>  
14 - <account-id type="integer">1</account-id>  
15 - <archived type="boolean">false</archived>  
16 - <assignee-on-resolve>reporter</assignee-on-resolve>  
17 - <backup-frequency type="integer">0</backup-frequency>  
18 - <close-ticket-simultaneously-default type="boolean">false</close-ticket-simultaneously-default>  
19 - <default-ticket-report-id type="integer" nil="true"></default-ticket-report-id>  
20 - <description nil="true"></description>  
21 - <disk-usage type="integer">27932</disk-usage>  
22 - <enable-time-tracking type="boolean">true</enable-time-tracking>  
23 - <id type="integer">#{tracker.project_id}</id>  
24 - <s3-access-key-id></s3-access-key-id>  
25 - <s3-backup-enabled type="boolean">false</s3-backup-enabled>  
26 - <s3-bucket-name></s3-bucket-name>  
27 - <short-name>test-project</short-name>  
28 - <theme>blue</theme>  
29 - <ticket-field1-active type="boolean">false</ticket-field1-active>  
30 - <ticket-field1-disposition>text</ticket-field1-disposition>  
31 - <ticket-field1-title>Field 1</ticket-field1-title>  
32 - <ticket-field2-active type="boolean">false</ticket-field2-active>  
33 - <ticket-field2-disposition>text</ticket-field2-disposition>  
34 - <ticket-field2-title>Field 2</ticket-field2-title>  
35 - <ticket-field3-active type="boolean">false</ticket-field3-active>  
36 - <ticket-field3-disposition>text</ticket-field3-disposition>  
37 - <ticket-field3-title>Field 3</ticket-field3-title>  
38 - <title>test-project</title>  
39 - <created-at>2011-04-25T09:21:43Z</created-at>  
40 - <updated-at>2013-03-08T08:03:02Z</updated-at>  
41 -</project>  
42 -EOF  
43 -  
44 - ticket_xml =<<EOF  
45 -<?xml version="1.0" encoding="UTF-8"?>  
46 -<ticket>  
47 - <assignee-id type="integer">40</assignee-id>  
48 - <component-id type="integer" nil="true"></component-id>  
49 - <description nil="true"></description>  
50 - <description-format>markdown</description-format>  
51 - <due-on type="date" nil="true"></due-on>  
52 - <field1-value-id type="integer" nil="true"></field1-value-id>  
53 - <field2-value-id type="integer" nil="true"></field2-value-id>  
54 - <field3-value-id type="integer" nil="true"></field3-value-id>  
55 - <hours-estimate-current type="float">1268.7</hours-estimate-current>  
56 - <hours-estimate-initial type="float">0.0</hours-estimate-initial>  
57 - <id type="integer">2436</id>  
58 - <milestone-id type="integer">78</milestone-id>  
59 - <number type="integer">119</number>  
60 - <priority>3</priority>  
61 - <project-id type="integer">15</project-id>  
62 - <reporter-id type="integer">40</reporter-id>  
63 - <resolution></resolution>  
64 - <resolution-description></resolution-description>  
65 - <resolution-description-format>markdown</resolution-description-format>  
66 - <severity-id type="integer" nil="true"></severity-id>  
67 - <status>reopened</status>  
68 - <summary>TEST-ticket.</summary>  
69 - <version-id type="integer" nil="true"></version-id>  
70 - <created-at>2012-06-27T17:49:06Z</created-at>  
71 - <updated-at>2013-03-07T16:04:05Z</updated-at>  
72 -</ticket>  
73 -EOF  
74 -  
75 - stub_request(:get, "https://#{tracker.username}:#{tracker.password}@test.unfuddle.com/api/v1/projects/#{tracker.project_id}.xml").  
76 - to_return(:status => 200, :body => project_xml, :headers => {})  
77 -  
78 -  
79 - stub_request(:post, "https://#{tracker.username}:#{tracker.password}@test.unfuddle.com/api/v1/projects/#{tracker.project_id}/tickets.xml").  
80 - to_return(:status => 200, :body => ticket_xml, :headers => {})  
81 -  
82 - problem.app.issue_tracker.create_issue(problem)  
83 - problem.reload  
84 -  
85 - requested = have_requested(:post,"https://#{tracker.username}:#{tracker.password}@test.unfuddle.com/api/v1/projects/#{tracker.project_id}/tickets.xml" )  
86 - expect(WebMock).to requested.with(:title => /[production][foo#bar] FooError: Too Much Bar/)  
87 - expect(WebMock).to requested.with(:content => /See this exception on Errbit/)  
88 -  
89 - expect(problem.issue_link).to eq issue_link  
90 - expect(problem.issue_type).to eq IssueTrackers::UnfuddleTracker::Label  
91 - end  
92 -end  
spec/models/problem_spec.rb
@@ -370,6 +370,53 @@ describe Problem do @@ -370,6 +370,53 @@ describe Problem do
370 end 370 end
371 end 371 end
372 372
  373 + describe "#issue_type" do
  374 + context "without issue_type fill in Problem" do
  375 + let(:problem) do
  376 + Problem.new(:app => app)
  377 + end
  378 +
  379 + let(:app) do
  380 + App.new(:issue_tracker => issue_tracker)
  381 + end
  382 +
  383 + context "without issue_tracker associate to app" do
  384 + let(:issue_tracker) do
  385 + nil
  386 + end
  387 + it 'return nil' do
  388 + expect(problem.issue_type).to be_nil
  389 + end
  390 + end
  391 +
  392 + context "with issue_tracker valid associate to app" do
  393 + let(:issue_tracker) do
  394 + Fabricate(:issue_tracker)
  395 + end
  396 +
  397 + it 'return the issue_tracker label' do
  398 + expect(problem.issue_type).to eql 'fake'
  399 + end
  400 + end
  401 +
  402 + context "with issue_tracker not valid associate to app" do
  403 + let(:issue_tracker) do
  404 + IssueTracker.new(:type_tracker => 'fake')
  405 + end
  406 +
  407 + it 'return nil' do
  408 + expect(problem.issue_type).to be_nil
  409 + end
  410 + end
  411 + end
  412 +
  413 + context "with issue_type fill in Problem" do
  414 + it 'return the value associate' do
  415 + expect(Problem.new(:issue_type => 'foo').issue_type).to eql 'foo'
  416 + end
  417 + end
  418 + end
  419 +
373 420
374 end 421 end
375 422
spec/spec_helper.rb
@@ -19,11 +19,13 @@ end @@ -19,11 +19,13 @@ end
19 19
20 require File.expand_path("../../config/environment", __FILE__) 20 require File.expand_path("../../config/environment", __FILE__)
21 require 'rspec/rails' 21 require 'rspec/rails'
  22 +require 'email_spec'
22 require 'database_cleaner' 23 require 'database_cleaner'
23 require 'webmock/rspec' 24 require 'webmock/rspec'
24 require 'xmpp4r' 25 require 'xmpp4r'
25 require 'xmpp4r/muc' 26 require 'xmpp4r/muc'
26 require 'mongoid-rspec' 27 require 'mongoid-rspec'
  28 +require 'errbit_plugin/issue_trackers/fake'
27 29
28 30
29 # Requires supporting files with custom matchers and macros, etc, 31 # Requires supporting files with custom matchers and macros, etc,
@@ -54,8 +56,8 @@ RSpec.configure do |config| @@ -54,8 +56,8 @@ RSpec.configure do |config|
54 init_haml_helpers 56 init_haml_helpers
55 end 57 end
56 58
57 - config.after(:all) do  
58 - WebMock.disable_net_connect! :allow => /coveralls\.io/ 59 + config.before(:all) do
  60 + WebMock.disable_net_connect! :allow => /coveralls\.io|127\.0\.0\.1/
59 end 61 end
60 end 62 end
61 63
spec/views/apps/edit.html.haml_spec.rb
@@ -2,8 +2,10 @@ require &#39;spec_helper&#39; @@ -2,8 +2,10 @@ require &#39;spec_helper&#39;
2 2
3 describe "apps/edit.html.haml" do 3 describe "apps/edit.html.haml" do
4 let(:app) { stub_model(App) } 4 let(:app) { stub_model(App) }
  5 + let(:app_decorate) { AppDecorator.new(app) }
5 before do 6 before do
6 view.stub(:app).and_return(app) 7 view.stub(:app).and_return(app)
  8 + view.stub(:app_decorate).and_return(app_decorate)
7 controller.stub(:current_user) { stub_model(User) } 9 controller.stub(:current_user) { stub_model(User) }
8 end 10 end
9 11
spec/views/apps/new.html.haml_spec.rb
@@ -2,8 +2,10 @@ require &#39;spec_helper&#39; @@ -2,8 +2,10 @@ require &#39;spec_helper&#39;
2 2
3 describe "apps/new.html.haml" do 3 describe "apps/new.html.haml" do
4 let(:app) { stub_model(App) } 4 let(:app) { stub_model(App) }
  5 + let(:app_decorate) { AppDecorator.new(app) }
5 before do 6 before do
6 view.stub(:app).and_return(app) 7 view.stub(:app).and_return(app)
  8 + view.stub(:app_decorate).and_return(app_decorate)
7 controller.stub(:current_user) { stub_model(User) } 9 controller.stub(:current_user) { stub_model(User) }
8 end 10 end
9 11
spec/views/problems/show.html.haml_spec.rb
@@ -3,6 +3,28 @@ require &#39;spec_helper&#39; @@ -3,6 +3,28 @@ require &#39;spec_helper&#39;
3 describe "problems/show.html.haml" do 3 describe "problems/show.html.haml" do
4 let(:problem) { Fabricate(:problem) } 4 let(:problem) { Fabricate(:problem) }
5 let(:comment) { Fabricate(:comment) } 5 let(:comment) { Fabricate(:comment) }
  6 + let(:pivotal_tracker) {
  7 + Class.new(ErrbitPlugin::IssueTracker) do
  8 + def self.label; 'pivotal'; end
  9 + def initialize(app, params); end
  10 + def configured?; true; end
  11 + def comments_allowed?; false; end
  12 + end
  13 + }
  14 + let(:github_tracker) {
  15 + Class.new(ErrbitPlugin::IssueTracker) do
  16 + def initialize(app, params); end
  17 + def label; 'github'; end
  18 + def configured?; true; end
  19 + def comments_allowed?; false; end
  20 + end
  21 + }
  22 + let(:trackers) {
  23 + {
  24 + 'github' => github_tracker,
  25 + 'pivotal' => pivotal_tracker
  26 + }
  27 + }
6 28
7 before do 29 before do
8 view.stub(:app).and_return(problem.app) 30 view.stub(:app).and_return(problem.app)
@@ -16,7 +38,8 @@ describe &quot;problems/show.html.haml&quot; do @@ -16,7 +38,8 @@ describe &quot;problems/show.html.haml&quot; do
16 end 38 end
17 39
18 def with_issue_tracker(tracker, problem) 40 def with_issue_tracker(tracker, problem)
19 - problem.app.issue_tracker = tracker.new :api_token => "token token token", :project_id => "1234" 41 + problem.app.issue_tracker = IssueTracker.new :type_tracker => tracker, :options => {:api_token => "token token token", :project_id => "1234"}
  42 + ErrbitPlugin::Registry.stub(:issue_trackers).and_return(trackers)
20 view.stub(:problem).and_return(problem) 43 view.stub(:problem).and_return(problem)
21 view.stub(:app).and_return(problem.app) 44 view.stub(:app).and_return(problem.app)
22 end 45 end
@@ -75,7 +98,7 @@ describe &quot;problems/show.html.haml&quot; do @@ -75,7 +98,7 @@ describe &quot;problems/show.html.haml&quot; do
75 98
76 it 'should allow creating issue for github if application has a github tracker' do 99 it 'should allow creating issue for github if application has a github tracker' do
77 problem = Fabricate(:problem_with_comments, :app => Fabricate(:app, :github_repo => "test_user/test_repo")) 100 problem = Fabricate(:problem_with_comments, :app => Fabricate(:app, :github_repo => "test_user/test_repo"))
78 - with_issue_tracker(GithubIssuesTracker, problem) 101 + with_issue_tracker("github", problem)
79 view.stub(:problem).and_return(problem) 102 view.stub(:problem).and_return(problem)
80 view.stub(:app).and_return(problem.app) 103 view.stub(:app).and_return(problem.app)
81 render 104 render
@@ -96,30 +119,53 @@ describe &quot;problems/show.html.haml&quot; do @@ -96,30 +119,53 @@ describe &quot;problems/show.html.haml&quot; do
96 119
97 end 120 end
98 121
99 - context "with lighthouse tracker on app" do  
100 - let(:app) { App.new(:new_record => false, :issue_tracker => tracker ) }  
101 - let(:tracker) {  
102 - IssueTrackers::LighthouseTracker.new(:project_id => 'x')  
103 - }  
104 - context "with problem without issue link" do 122 + context "with tracker associate on app" do
  123 + before do
  124 + with_issue_tracker("pivotal", problem)
  125 + end
  126 +
  127 + context "with app having github_repo" do
  128 + let(:app) { App.new(:new_record => false, :github_repo => 'foo/bar') }
105 let(:problem){ Problem.new(:new_record => false, :app => app) } 129 let(:problem){ Problem.new(:new_record => false, :app => app) }
106 - it 'not see link if no issue tracker' do  
107 - view.stub(:problem).and_return(problem)  
108 - view.stub(:app).and_return(problem.app) 130 +
  131 + before do
  132 + problem.issue_link = nil
  133 + user = Fabricate(:user, :github_login => 'test_user', :github_oauth_token => 'abcdef')
  134 + controller.stub(:current_user) { user }
  135 + end
  136 +
  137 + it 'links to the associated tracker' do
109 render 138 render
110 - expect(view.content_for(:action_bar)).to match(/create issue/) 139 + expect(view.content_for(:action_bar)).to match(".pivotal_create.create-issue")
111 end 140 end
112 141
  142 + it 'does not link to github' do
  143 + render
  144 + expect(view.content_for(:action_bar)).to_not match(".github_create.create-issue")
  145 + end
113 end 146 end
114 147
115 - context "with problem with issue link" do  
116 - let(:problem){ Problem.new(:new_record => false, :app => app, :issue_link => 'http://foo') } 148 + context "without app having github_repo" do
  149 + context "with problem without issue link" do
  150 + before do
  151 + problem.issue_link = nil
  152 + end
  153 + it 'not see link if no issue tracker' do
  154 + render
  155 + expect(view.content_for(:action_bar)).to match(/create issue/)
  156 + end
117 157
118 - it 'not see link if no issue tracker' do  
119 - view.stub(:problem).and_return(problem)  
120 - view.stub(:app).and_return(problem.app)  
121 - render  
122 - expect(view.content_for(:action_bar)).to_not match(/create issue/) 158 + end
  159 +
  160 + context "with problem with issue link" do
  161 + before do
  162 + problem.issue_link = 'http://foo'
  163 + end
  164 +
  165 + it 'not see link if no issue tracker' do
  166 + render
  167 + expect(view.content_for(:action_bar)).to_not match(/create issue/)
  168 + end
123 end 169 end
124 end 170 end
125 171
@@ -147,7 +193,7 @@ describe &quot;problems/show.html.haml&quot; do @@ -147,7 +193,7 @@ describe &quot;problems/show.html.haml&quot; do
147 context "with issue tracker" do 193 context "with issue tracker" do
148 it 'should not display the comments section' do 194 it 'should not display the comments section' do
149 problem = Fabricate(:problem) 195 problem = Fabricate(:problem)
150 - with_issue_tracker(PivotalLabsTracker, problem) 196 + with_issue_tracker("pivotal", problem)
151 render 197 render
152 expect(view.view_flow.get(:comments)).to be_blank 198 expect(view.view_flow.get(:comments)).to be_blank
153 end 199 end
@@ -155,7 +201,7 @@ describe &quot;problems/show.html.haml&quot; do @@ -155,7 +201,7 @@ describe &quot;problems/show.html.haml&quot; do
155 it 'should display existing comments' do 201 it 'should display existing comments' do
156 problem = Fabricate(:problem_with_comments) 202 problem = Fabricate(:problem_with_comments)
157 problem.reload 203 problem.reload
158 - with_issue_tracker(PivotalLabsTracker, problem) 204 + with_issue_tracker("pivotal", problem)
159 render 205 render
160 206
161 expect(view.content_for(:comments)).to include('Test comment') 207 expect(view.content_for(:comments)).to include('Test comment')