Commit 90033270a10b952161d1e545993761516d5e8566

Authored by Cyril Mougel
1 parent 826c3ea3
Exists in master and in 1 other branch production

Extract all IssueTracker system and migrate to errbit_plugin

This new system can avoid having all dependencies in Errbit directly.
Now if you want new issue tracker you need add it. Not delete it like
previous.

This work is in progress actually
Showing 44 changed files with 332 additions and 1356 deletions   Show diff stats
@@ -27,30 +27,13 @@ gem 'rails_autolink' @@ -27,30 +27,13 @@ gem 'rails_autolink'
27 gem 'hoptoad_notifier', "~> 2.4" 27 gem 'hoptoad_notifier', "~> 2.4"
28 gem 'draper', :require => false 28 gem 'draper', :require => false
29 29
  30 +gem 'errbit_plugin',
  31 + :git => 'https://github.com/errbit/errbit_plugin.git'
  32 + # :path => 'vendor/gems/errbit_plugin'
  33 +gem 'errbit_github_plugin',
  34 + :git => 'https://github.com/errbit/errbit_github_plugin.git'
  35 + # :path => 'vendor/gems/errbit_github_plugin'
30 36
31 -# Remove / comment out any of the gems below if you want to disable  
32 -# a given issue tracker, notification service, or authentication.  
33 -  
34 -# Issue Trackers  
35 -# ---------------------------------------  
36 -# Lighthouse  
37 -gem 'lighthouse-api'  
38 -# Redmine  
39 -gem 'oruen_redmine_client', :require => 'redmine_client'  
40 -# Pivotal Tracker  
41 -gem 'pivotal-tracker'  
42 -# Fogbugz  
43 -gem 'ruby-fogbugz', :require => 'fogbugz'  
44 -# Github Issues  
45 -gem 'octokit', '~> 1.18'  
46 -# Gitlab  
47 -gem 'gitlab', '~> 3.0.0'  
48 -  
49 -# Bitbucket Issues  
50 -gem 'bitbucket_rest_api', :require => false  
51 -  
52 -# Jira  
53 -gem 'jira-ruby', :require => 'jira'  
54 37
55 # Notification services 38 # Notification services
56 # --------------------------------------- 39 # ---------------------------------------
@@ -80,7 +63,6 @@ group :development, :test do @@ -80,7 +63,6 @@ group :development, :test do
80 gem 'rspec-rails' 63 gem 'rspec-rails'
81 gem 'webmock', :require => false 64 gem 'webmock', :require => false
82 gem 'airbrake', :require => false 65 gem 'airbrake', :require => false
83 - gem 'ruby-debug', :platform => :mri_18  
84 gem 'debugger', :platform => :mri_19 66 gem 'debugger', :platform => :mri_19
85 gem 'pry-rails' 67 gem 'pry-rails'
86 # gem 'rpm_contrib' 68 # gem 'rpm_contrib'
@@ -117,7 +99,6 @@ group :heroku, :production do @@ -117,7 +99,6 @@ group :heroku, :production do
117 gem 'unicorn', :require => false 99 gem 'unicorn', :require => false
118 end 100 end
119 101
120 -  
121 # Gems used only for assets and not required 102 # Gems used only for assets and not required
122 # in production environments by default. 103 # in production environments by default.
123 group :assets do 104 group :assets do
  1 +PATH
  2 + remote: vendor/gems/errbit_github_plugin
  3 + specs:
  4 + errbit_github_plugin (0.0.1)
  5 + errbit_plugin
  6 + octokit
  7 +
  8 +PATH
  9 + remote: vendor/gems/errbit_plugin
  10 + specs:
  11 + errbit_plugin (0.0.1)
  12 +
1 GEM 13 GEM
2 remote: https://rubygems.org/ 14 remote: https://rubygems.org/
3 specs: 15 specs:
@@ -44,13 +56,6 @@ GEM @@ -44,13 +56,6 @@ GEM
44 erubis (>= 2.6.6) 56 erubis (>= 2.6.6)
45 binding_of_caller (0.7.2) 57 binding_of_caller (0.7.2)
46 debug_inspector (>= 0.0.1) 58 debug_inspector (>= 0.0.1)
47 - bitbucket_rest_api (0.1.4)  
48 - faraday (~> 0.8.1)  
49 - faraday_middleware (~> 0.9.0)  
50 - hashie (~> 2.0.5)  
51 - multi_json (~> 1.3)  
52 - nokogiri (~> 1.5.2)  
53 - simple_oauth  
54 builder (3.0.4) 59 builder (3.0.4)
55 callsite (0.0.11) 60 callsite (0.0.11)
56 campy (1.0.0) 61 campy (1.0.0)
@@ -96,8 +101,9 @@ GEM @@ -96,8 +101,9 @@ GEM
96 warden (~> 1.2.3) 101 warden (~> 1.2.3)
97 diff-lcs (1.2.4) 102 diff-lcs (1.2.4)
98 dotenv (0.9.0) 103 dotenv (0.9.0)
99 - draper (1.2.1) 104 + draper (1.3.0)
100 actionpack (>= 3.0) 105 actionpack (>= 3.0)
  106 + activemodel (>= 3.0)
101 activesupport (>= 3.0) 107 activesupport (>= 3.0)
102 request_store (~> 1.0.3) 108 request_store (~> 1.0.3)
103 email_spec (1.5.0) 109 email_spec (1.5.0)
@@ -105,25 +111,19 @@ GEM @@ -105,25 +111,19 @@ GEM
105 mail (~> 2.2) 111 mail (~> 2.2)
106 erubis (2.7.0) 112 erubis (2.7.0)
107 execjs (2.0.2) 113 execjs (2.0.2)
108 - fabrication (2.8.1) 114 + fabrication (2.9.0)
109 faraday (0.8.8) 115 faraday (0.8.8)
110 multipart-post (~> 1.2.0) 116 multipart-post (~> 1.2.0)
111 - faraday_middleware (0.9.0)  
112 - faraday (>= 0.7.4, < 0.9)  
113 flowdock (0.3.1) 117 flowdock (0.3.1)
114 httparty (~> 0.7) 118 httparty (~> 0.7)
115 multi_json 119 multi_json
116 foreman (0.63.0) 120 foreman (0.63.0)
117 dotenv (>= 0.7) 121 dotenv (>= 0.7)
118 thor (>= 0.13.6) 122 thor (>= 0.13.6)
119 - gitlab (3.0.0)  
120 - httparty  
121 haml (4.0.3) 123 haml (4.0.3)
122 tilt 124 tilt
123 - happymapper (0.4.1)  
124 - libxml-ruby (~> 2.0)  
125 hashie (2.0.5) 125 hashie (2.0.5)
126 - highline (1.6.19) 126 + highline (1.6.20)
127 hike (1.2.3) 127 hike (1.2.3)
128 hipchat (0.12.0) 128 hipchat (0.12.0)
129 httparty 129 httparty
@@ -138,11 +138,7 @@ GEM @@ -138,11 +138,7 @@ GEM
138 json (~> 1.8) 138 json (~> 1.8)
139 multi_xml (>= 0.5.2) 139 multi_xml (>= 0.5.2)
140 httpauth (0.2.0) 140 httpauth (0.2.0)
141 - i18n (0.6.9)  
142 - jira-ruby (0.1.2)  
143 - activesupport  
144 - oauth  
145 - railties 141 + i18n (0.6.5)
146 journey (1.0.4) 142 journey (1.0.4)
147 jquery-rails (2.1.4) 143 jquery-rails (2.1.4)
148 railties (>= 3.0, < 5.0) 144 railties (>= 3.0, < 5.0)
@@ -157,10 +153,6 @@ GEM @@ -157,10 +153,6 @@ GEM
157 launchy (2.3.0) 153 launchy (2.3.0)
158 addressable (~> 2.3) 154 addressable (~> 2.3)
159 libv8 (3.16.14.3) 155 libv8 (3.16.14.3)
160 - libxml-ruby (2.7.0)  
161 - lighthouse-api (2.0)  
162 - activeresource (>= 3.0.0)  
163 - activesupport (>= 3.0.0)  
164 linecache (0.46) 156 linecache (0.46)
165 rbx-require-relative (> 0.0.4) 157 rbx-require-relative (> 0.0.4)
166 mail (2.5.4) 158 mail (2.5.4)
@@ -197,24 +189,15 @@ GEM @@ -197,24 +189,15 @@ GEM
197 net-ssh (2.7.0) 189 net-ssh (2.7.0)
198 net-ssh-gateway (1.2.0) 190 net-ssh-gateway (1.2.0)
199 net-ssh (>= 2.6.5) 191 net-ssh (>= 2.6.5)
200 - netrc (0.7.7)  
201 nokogiri (1.5.10) 192 nokogiri (1.5.10)
202 - nokogiri-happymapper (0.5.8)  
203 - nokogiri (~> 1.5)  
204 - oauth (0.4.7)  
205 oauth2 (0.8.1) 193 oauth2 (0.8.1)
206 faraday (~> 0.8) 194 faraday (~> 0.8)
207 httpauth (~> 0.1) 195 httpauth (~> 0.1)
208 jwt (~> 0.1.4) 196 jwt (~> 0.1.4)
209 multi_json (~> 1.0) 197 multi_json (~> 1.0)
210 rack (~> 1.2) 198 rack (~> 1.2)
211 - octokit (1.25.0)  
212 - addressable (~> 2.2)  
213 - faraday (~> 0.8)  
214 - faraday_middleware (~> 0.9)  
215 - hashie (~> 2.0)  
216 - multi_json (~> 1.3)  
217 - netrc (~> 0.7.7) 199 + octokit (2.1.2)
  200 + sawyer (~> 0.5.0)
218 omniauth (1.1.4) 201 omniauth (1.1.4)
219 hashie (>= 1.2, < 3) 202 hashie (>= 1.2, < 3)
220 rack 203 rack
@@ -226,18 +209,6 @@ GEM @@ -226,18 +209,6 @@ GEM
226 omniauth (~> 1.0) 209 omniauth (~> 1.0)
227 origin (1.1.0) 210 origin (1.1.0)
228 orm_adapter (0.4.0) 211 orm_adapter (0.4.0)
229 - oruen_redmine_client (0.0.1)  
230 - activeresource (>= 2.3.0)  
231 - pivotal-tracker (0.5.12)  
232 - builder  
233 - builder  
234 - crack  
235 - happymapper (>= 0.3.2)  
236 - nokogiri (>= 1.4.3)  
237 - nokogiri (>= 1.5.5)  
238 - nokogiri-happymapper (>= 0.5.4)  
239 - rest-client (~> 1.6.0)  
240 - rest-client (~> 1.6.0)  
241 pjax_rails (0.3.4) 212 pjax_rails (0.3.4)
242 jquery-rails 213 jquery-rails
243 poltergeist (1.4.1) 214 poltergeist (1.4.1)
@@ -246,7 +217,7 @@ GEM @@ -246,7 +217,7 @@ GEM
246 multi_json (~> 1.0) 217 multi_json (~> 1.0)
247 websocket-driver (>= 0.2.0) 218 websocket-driver (>= 0.2.0)
248 polyglot (0.3.3) 219 polyglot (0.3.3)
249 - premailer (1.7.3) 220 + premailer (1.7.9)
250 css_parser (>= 1.1.9) 221 css_parser (>= 1.1.9)
251 htmlentities (>= 4.0.0) 222 htmlentities (>= 4.0.0)
252 pry (0.9.12.2) 223 pry (0.9.12.2)
@@ -276,8 +247,7 @@ GEM @@ -276,8 +247,7 @@ GEM
276 activeresource (= 3.2.16) 247 activeresource (= 3.2.16)
277 activesupport (= 3.2.16) 248 activesupport (= 3.2.16)
278 bundler (~> 1.0) 249 bundler (~> 1.0)
279 - railties (= 3.2.16)  
280 - rails_autolink (1.1.4) 250 + rails_autolink (1.1.5)
281 rails (> 3.1) 251 rails (> 3.1)
282 railties (3.2.16) 252 railties (3.2.16)
283 actionpack (= 3.2.16) 253 actionpack (= 3.2.16)
@@ -316,13 +286,13 @@ GEM @@ -316,13 +286,13 @@ GEM
316 ruby-debug-base (~> 0.10.4.0) 286 ruby-debug-base (~> 0.10.4.0)
317 ruby-debug-base (0.10.4) 287 ruby-debug-base (0.10.4)
318 linecache (>= 0.3) 288 linecache (>= 0.3)
319 - ruby-fogbugz (0.1.1)  
320 - crack  
321 rushover (0.3.0) 289 rushover (0.3.0)
322 json 290 json
323 rest-client 291 rest-client
324 safe_yaml (0.9.7) 292 safe_yaml (0.9.7)
325 - simple_oauth (0.2.0) 293 + sawyer (0.5.0)
  294 + addressable (~> 2.3.5)
  295 + faraday (~> 0.8, < 0.10)
326 simplecov (0.7.1) 296 simplecov (0.7.1)
327 multi_json (~> 1.0) 297 multi_json (~> 1.0)
328 simplecov-html (~> 0.7.1) 298 simplecov-html (~> 0.7.1)
@@ -355,15 +325,15 @@ GEM @@ -355,15 +325,15 @@ GEM
355 railties (> 3.2.8, < 4.0.0) 325 railties (> 3.2.8, < 4.0.0)
356 sprockets (>= 2.0.0) 326 sprockets (>= 2.0.0)
357 tzinfo (0.3.38) 327 tzinfo (0.3.38)
358 - uglifier (2.2.1) 328 + uglifier (2.3.0)
359 execjs (>= 0.3.0) 329 execjs (>= 0.3.0)
360 - multi_json (~> 1.0, >= 1.0.2) 330 + json (>= 1.8.0)
361 underscore-rails (1.5.2) 331 underscore-rails (1.5.2)
362 unicorn (4.6.3) 332 unicorn (4.6.3)
363 kgio (~> 2.6) 333 kgio (~> 2.6)
364 rack 334 rack
365 raindrops (~> 0.7) 335 raindrops (~> 0.7)
366 - useragent (0.8.3) 336 + useragent (0.9.0)
367 warden (1.2.3) 337 warden (1.2.3)
368 rack (>= 1.0) 338 rack (>= 1.0)
369 webmock (1.15.0) 339 webmock (1.15.0)
@@ -385,7 +355,6 @@ DEPENDENCIES @@ -385,7 +355,6 @@ DEPENDENCIES
385 airbrake 355 airbrake
386 better_errors 356 better_errors
387 binding_of_caller 357 binding_of_caller
388 - bitbucket_rest_api  
389 campy 358 campy
390 capistrano (~> 2.0) 359 capistrano (~> 2.0)
391 capybara 360 capybara
@@ -396,30 +365,26 @@ DEPENDENCIES @@ -396,30 +365,26 @@ DEPENDENCIES
396 devise 365 devise
397 draper 366 draper
398 email_spec 367 email_spec
  368 + errbit_github_plugin!
  369 + errbit_plugin!
399 execjs 370 execjs
400 fabrication 371 fabrication
401 flowdock 372 flowdock
402 foreman 373 foreman
403 - gitlab (~> 3.0.0)  
404 haml 374 haml
405 hipchat 375 hipchat
406 hoi 376 hoi
407 hoptoad_notifier (~> 2.4) 377 hoptoad_notifier (~> 2.4)
408 htmlentities 378 htmlentities
409 httparty 379 httparty
410 - jira-ruby  
411 jquery-rails (~> 2.1.4) 380 jquery-rails (~> 2.1.4)
412 kaminari (>= 0.14.1) 381 kaminari (>= 0.14.1)
413 launchy 382 launchy
414 - lighthouse-api  
415 meta_request 383 meta_request
416 mongoid 384 mongoid
417 mongoid-rspec 385 mongoid-rspec
418 mongoid_rails_migrations 386 mongoid_rails_migrations
419 - octokit (~> 1.18)  
420 omniauth-github 387 omniauth-github
421 - oruen_redmine_client  
422 - pivotal-tracker  
423 pjax_rails 388 pjax_rails
424 poltergeist 389 poltergeist
425 pry-rails 390 pry-rails
@@ -432,7 +397,6 @@ DEPENDENCIES @@ -432,7 +397,6 @@ DEPENDENCIES
432 ri_cal 397 ri_cal
433 rspec-rails 398 rspec-rails
434 ruby-debug 399 ruby-debug
435 - ruby-fogbugz  
436 rushover 400 rushover
437 strong_parameters 401 strong_parameters
438 therubyracer 402 therubyracer
app/controllers/apps_controller.rb
@@ -99,7 +99,7 @@ class AppsController &lt; ApplicationController @@ -99,7 +99,7 @@ class AppsController &lt; ApplicationController
99 99
100 def plug_params app 100 def plug_params app
101 app.watchers.build if app.watchers.none? 101 app.watchers.build if app.watchers.none?
102 - app.issue_tracker = IssueTracker.new unless app.issue_tracker_configured? 102 + app.issue_tracker ||= IssueTracker.new
103 app.notification_service = NotificationService.new unless app.notification_service_configured? 103 app.notification_service = NotificationService.new unless app.notification_service_configured?
104 app.copy_attributes_from(params[:copy_attributes_from]) if params[:copy_attributes_from] 104 app.copy_attributes_from(params[:copy_attributes_from]) if params[:copy_attributes_from]
105 end 105 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/issue_tracker_decorator.rb
1 class IssueTrackerDecorator < Draper::Decorator 1 class IssueTrackerDecorator < Draper::Decorator
2 2
  3 + def initialize(object, key)
  4 + @object = object
  5 + @key = key
  6 + end
  7 + attr_reader :key
  8 +
3 delegate_all 9 delegate_all
4 10
5 def issue_trackers 11 def issue_trackers
6 - @issue_trackers ||= [  
7 - IssueTracker::None,  
8 - IssueTracker.subclasses.select{|klass| klass != IssueTracker::None }  
9 - ].flatten  
10 - @issue_trackers.each do |it|  
11 - yield IssueTrackerDecorator.new(it) 12 + @issue_trackers ||= ErrbitPlugin::Register.issue_trackers
  13 + @issue_trackers.each do |key, it|
  14 + yield IssueTrackerDecorator.new(it.new(app, {}), key)
12 end 15 end
13 end 16 end
14 17
15 def note 18 def note
16 - object::Note.html_safe 19 + object.note.html_safe
17 end 20 end
18 21
19 def fields 22 def fields
20 - object::Fields.each do |field, field_info| 23 + object.fields.each do |field, field_info|
21 yield IssueTrackerFieldDecorator.new(field, field_info) 24 yield IssueTrackerFieldDecorator.new(field, field_info)
22 end 25 end
23 end 26 end
@@ -29,7 +32,7 @@ class IssueTrackerDecorator &lt; Draper::Decorator @@ -29,7 +32,7 @@ class IssueTrackerDecorator &lt; Draper::Decorator
29 private 32 private
30 33
31 def choosen?(issue_tracker) 34 def choosen?(issue_tracker)
32 - object.to_s == issue_tracker._type ? 'chosen' : '' 35 + key == issue_tracker.type_tracker.to_s ? 'chosen' : ''
33 end 36 end
34 37
35 end 38 end
app/decorators/issue_tracker_field_decorator.rb
@@ -13,10 +13,10 @@ class IssueTrackerFieldDecorator &lt; Draper::Decorator @@ -13,10 +13,10 @@ class IssueTrackerFieldDecorator &lt; Draper::Decorator
13 end 13 end
14 14
15 15
16 - def input(form)  
17 - form.send(input_field, object, 16 + def input(form, issue_tracker)
  17 + form.send(input_field, key.to_s,
18 :placeholder => field_info[:placeholder], 18 :placeholder => field_info[:placeholder],
19 - :value => form.object.send(object)) 19 + :value => issue_tracker.options[key.to_s])
20 end 20 end
21 21
22 private 22 private
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
@@ -25,7 +25,7 @@ class App @@ -25,7 +25,7 @@ class App
25 25
26 embeds_many :watchers 26 embeds_many :watchers
27 embeds_many :deploys 27 embeds_many :deploys
28 - embeds_one :issue_tracker 28 + embeds_one :issue_tracker, :class_name => 'IssueTracker'
29 embeds_one :notification_service 29 embeds_one :notification_service
30 30
31 has_many :problems, :inverse_of => :app, :dependent => :destroy 31 has_many :problems, :inverse_of => :app, :dependent => :destroy
@@ -43,7 +43,7 @@ class App @@ -43,7 +43,7 @@ class App
43 accepts_nested_attributes_for :watchers, :allow_destroy => true, 43 accepts_nested_attributes_for :watchers, :allow_destroy => true,
44 :reject_if => proc { |attrs| attrs[:user_id].blank? && attrs[:email].blank? } 44 :reject_if => proc { |attrs| attrs[:user_id].blank? && attrs[:email].blank? }
45 accepts_nested_attributes_for :issue_tracker, :allow_destroy => true, 45 accepts_nested_attributes_for :issue_tracker, :allow_destroy => true,
46 - :reject_if => proc { |attrs| !IssueTracker.subclasses.map(&:to_s).include?(attrs[:type].to_s) } 46 + :reject_if => proc { |attrs| !ErrbitPlugin::Register.issue_trackers.keys.map(&:to_s).include?(attrs[:type_tracker].to_s) }
47 accepts_nested_attributes_for :notification_service, :allow_destroy => true, 47 accepts_nested_attributes_for :notification_service, :allow_destroy => true,
48 :reject_if => proc { |attrs| !NotificationService.subclasses.map(&:to_s).include?(attrs[:type].to_s) } 48 :reject_if => proc { |attrs| !NotificationService.subclasses.map(&:to_s).include?(attrs[:type].to_s) }
49 49
@@ -119,7 +119,7 @@ class App @@ -119,7 +119,7 @@ class App
119 119
120 120
121 def issue_tracker_configured? 121 def issue_tracker_configured?
122 - !!(issue_tracker.class < IssueTracker && issue_tracker.configured?) 122 + !!issue_tracker && !!(issue_tracker.configured?)
123 end 123 end
124 124
125 def notification_service_configured? 125 def notification_service_configured?
@@ -174,6 +174,13 @@ class App @@ -174,6 +174,13 @@ class App
174 set(:api_key, SecureRandom.hex) 174 set(:api_key, SecureRandom.hex)
175 end 175 end
176 176
  177 + ##
  178 + # Check if comments can be allowed on this application
  179 + #
  180 + def comments_allowed?
  181 + !issue_tracker || issue_tracker.comments_allowed?
  182 + end
  183 +
177 protected 184 protected
178 185
179 def store_cached_attributes_on_problems 186 def store_cached_attributes_on_problems
@@ -199,5 +206,6 @@ class App @@ -199,5 +206,6 @@ class App
199 github_repo.sub!(/(git@|https?:\/\/)github\.com(\/|:)/, '') 206 github_repo.sub!(/(git@|https?:\/\/)github\.com(\/|:)/, '')
200 github_repo.sub!(/\.git$/, '') 207 github_repo.sub!(/\.git$/, '')
201 end 208 end
  209 +
202 end 210 end
203 211
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  
5 - include Rails.application.routes.url_helpers  
6 4
7 - Note = '' 5 + include Rails.application.routes.url_helpers
8 6
9 default_url_options[:host] = ActionMailer::Base.default_url_options[:host] 7 default_url_options[:host] = ActionMailer::Base.default_url_options[:host]
10 8
11 embedded_in :app, :inverse_of => :issue_tracker 9 embedded_in :app, :inverse_of => :issue_tracker
12 10
13 - field :project_id, :type => String  
14 - field :alt_project_id, :type => String # Specify an alternative project id. e.g. for viewing files  
15 - field :api_token, :type => String  
16 - field :account, :type => String  
17 - field :username, :type => String  
18 - field :password, :type => String  
19 - field :ticket_properties, :type => String  
20 - field :subdomain, :type => String  
21 - field :milestone_id, :type => String  
22 -  
23 - # Is there any better way to enhance the props? Putting them into the subclass leads to  
24 - # an error while rendering the form fields -.-  
25 - field :base_url, :type => String  
26 - field :context_path, :type => String  
27 - field :issue_type, :type => String  
28 - field :issue_component, :type => String  
29 - field :issue_priority, :type => String  
30 -  
31 - validate :check_params  
32 -  
33 - # Subclasses are responsible for overwriting this method.  
34 - def check_params; true; end  
35 -  
36 - def issue_title(problem)  
37 - "[#{ problem.environment }][#{ problem.where }] #{problem.message.to_s.truncate(100)}"  
38 - end  
39 -  
40 - # Allows us to set the issue tracker class from a single form.  
41 - def type; self._type; end  
42 - def type=(t); self._type=t; end  
43 -  
44 - def url; nil; end  
45 -  
46 - # Retrieve tracker label from either class or instance.  
47 - def self.label; self::Label; end  
48 - def label; self.class.label; end  
49 -  
50 - def configured?  
51 - project_id.present?  
52 - end 11 + field :type_tracker, :type => String
  12 + field :options, :type => Hash, :default => {}
53 13
54 ## 14 ##
55 # Update default_url_option with valid data from the request information 15 # Update default_url_option with valid data from the request information
@@ -61,4 +21,14 @@ class IssueTracker @@ -61,4 +21,14 @@ class IssueTracker
61 IssueTracker.default_url_options[:port] = request.port 21 IssueTracker.default_url_options[:port] = request.port
62 IssueTracker.default_url_options[:protocol] = request.scheme 22 IssueTracker.default_url_options[:protocol] = request.scheme
63 end 23 end
  24 +
  25 + def tracker
  26 + @tracker ||= ErrbitPlugin::Register.issue_tracker(self.type_tracker).new(app, self.options)
  27 + rescue NameError
  28 + ErrbitPlugin::NoneIssueTracker.new(app, {})
  29 + end
  30 + delegate :configured?, :to => :tracker
  31 + delegate :create_issue, :to => :tracker
  32 + delegate :label, :to => :tracker
  33 + delegate :comments_allowed?, :to => :tracker
64 end 34 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, :oauth_token => oauth_token)  
33 - else  
34 - client = Octokit::Client.new(:login => username, :password => password)  
35 - end  
36 -  
37 - begin  
38 - issue = client.create_issue(  
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.html_url,  
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/none.rb
@@ -1,5 +0,0 @@ @@ -1,5 +0,0 @@
1 -class IssueTrackers::None < IssueTracker  
2 - Fields = []  
3 - Note = 'When no issue tracker has been configured, you will be able to leave comments on errors.'  
4 - Label = 'none'  
5 -end  
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}/changes/#{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
@@ -68,7 +68,7 @@ class Problem @@ -68,7 +68,7 @@ class Problem
68 end 68 end
69 69
70 def comments_allowed? 70 def comments_allowed?
71 - Errbit::Config.allow_comments_with_issue_tracker || !app.issue_tracker_configured? 71 + Errbit::Config.allow_comments_with_issue_tracker || app.comments_allowed?
72 end 72 end
73 73
74 def resolve! 74 def resolve!
app/views/apps/_issue_tracker_fields.html.haml
1 %fieldset 1 %fieldset
2 %legend= t('.legend') 2 %legend= t('.legend')
3 - = f.fields_for :issue_tracker do |w| 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 - - w.object.issue_trackers do |tracker|  
7 - = w.label :type, :class => "label_radio #{tracker.label}", :value => tracker.name do  
8 - = w.radio_button :type, tracker.name, 'data-section' => tracker.label 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 9 = tracker.label
10 10
11 - - w.object.issue_trackers do |tracker|  
12 - %div.tracker_params{:class => tracker.params_class(w.object)}  
13 - %p= tracker.note  
14 - - tracker.fields do |field|  
15 - = w.label field.key, field.label  
16 - = field.input(w) 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)
17 18
18 .image_preloader 19 .image_preloader
19 - - w.object.issue_trackers do |tracker| 20 + - issue_tracker_form.object.issue_trackers do |tracker|
20 = image_tag "#{tracker.label}_inactive.png" 21 = image_tag "#{tracker.label}_inactive.png"
21 = image_tag "#{tracker.label}_create.png" 22 = image_tag "#{tracker.label}_create.png"
22 23
db/migrate/20131011155638_extract_issue_tracker.rb 0 → 100644
@@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
  1 +class ExtractIssueTracker < Mongoid::Migration
  2 +
  3 + def self.up
  4 + App.collection.find.each do |app|
  5 + if app['issue_tracker'] && !app['issue_tracker'].empty?
  6 + it = app['issue_tracker']
  7 + it['type_tracker'] = 'IssueTrackers::BitbucketIssuesTracker'
  8 + it['options'] = app['issue_tracker'].dup
  9 + it.delete('_type')
  10 + App.collection.find(
  11 + :_id => app['_id']
  12 + ).update(app)
  13 + end
  14 + end
  15 + end
  16 +
  17 + def self.down
  18 + end
  19 +end
spec/acceptance/app_regenerate_api_key_spec.rb
@@ -72,17 +72,17 @@ feature &quot;Create an application&quot; do @@ -72,17 +72,17 @@ feature &quot;Create an application&quot; do
72 log_in admin 72 log_in admin
73 click_on I18n.t('apps.index.new_app') 73 click_on I18n.t('apps.index.new_app')
74 fill_in 'app_name', :with => 'My new app' 74 fill_in 'app_name', :with => 'My new app'
75 - find('.label_radio.bitbucket').click  
76 - within ".bitbucket.tracker_params" do  
77 - fill_in 'app_issue_tracker_attributes_api_token', :with => 'token'  
78 - fill_in 'app_issue_tracker_attributes_project_id', :with => 'pass' 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 79 end
80 click_on I18n.t('apps.new.add_app') 80 click_on I18n.t('apps.new.add_app')
81 expect(page.has_content?(I18n.t('controllers.apps.flash.create.success'))).to eql true 81 expect(page.has_content?(I18n.t('controllers.apps.flash.create.success'))).to eql true
82 app = App.where(:name => 'My new app').first 82 app = App.where(:name => 'My new app').first
83 - expect(app.issue_tracker).to be_a IssueTracker::BitbucketIssuesTracker  
84 - expect(app.issue_tracker.api_token).to eql 'token'  
85 - expect(app.issue_tracker.project_id).to eql 'pass' 83 + expect(app.issue_tracker.type_tracker).to eql 'IssueTrackers::GithubIssuesTracker'
  84 + expect(app.issue_tracker.options['username']).to eql 'token'
  85 + expect(app.issue_tracker.options['password']).to eql 'pass'
86 86
87 click_on I18n.t('shared.navigation.apps') 87 click_on I18n.t('shared.navigation.apps')
88 click_on 'My new app' 88 click_on 'My new app'
@@ -91,6 +91,6 @@ feature &quot;Create an application&quot; do @@ -91,6 +91,6 @@ feature &quot;Create an application&quot; do
91 click_on I18n.t('apps.edit.update') 91 click_on I18n.t('apps.edit.update')
92 expect(page.has_content?(I18n.t('controllers.apps.flash.update.success'))).to eql true 92 expect(page.has_content?(I18n.t('controllers.apps.flash.update.success'))).to eql true
93 app = App.where(:name => 'My new app').first 93 app = App.where(:name => 'My new app').first
94 - expect(app.issue_tracker).to be_a IssueTracker::None 94 + expect(app.issue_tracker.tracker).to be_a ErrbitPlugin::NoneIssueTracker
95 end 95 end
96 end 96 end
spec/controllers/apps_controller_spec.rb
@@ -289,7 +289,7 @@ describe AppsController do @@ -289,7 +289,7 @@ describe AppsController do
289 context "unknown tracker type" do 289 context "unknown tracker type" do
290 before(:each) do 290 before(:each) do
291 put :update, :id => @app.id, :app => { :issue_tracker_attributes => { 291 put :update, :id => @app.id, :app => { :issue_tracker_attributes => {
292 - :type => 'unknown', :project_id => '1234', :api_token => '123123', :account => 'myapp' 292 + :type_tracker => 'unknown', :options => {:project_id => '1234', :api_token => '123123', :account => 'myapp'}
293 } } 293 } }
294 @app.reload 294 @app.reload
295 end 295 end
@@ -299,24 +299,24 @@ describe AppsController do @@ -299,24 +299,24 @@ describe AppsController do
299 end 299 end
300 end 300 end
301 301
302 - IssueTracker.subclasses.each do |tracker_klass|  
303 - context tracker_klass do 302 + ErrbitPlugin::Register.issue_trackers.each do |key, klass|
  303 + context key do
304 it "should save tracker params" do 304 it "should save tracker params" do
305 - params = tracker_klass::Fields.inject({}){|hash,f| hash[f[0]] = "test_value"; hash }  
306 - params[:ticket_properties] = "card_type = defect" if tracker_klass == MingleTracker  
307 - params[:type] = tracker_klass.to_s 305 + issue_tracker_klass = klass.new(@app, {})
  306 + params = {
  307 + :options => issue_tracker_klass.fields.inject({}){|hash,f| hash[f[0]] = "test_value"; hash },
  308 + :type_tracker => key.dup.to_s
  309 + }
308 put :update, :id => @app.id, :app => {:issue_tracker_attributes => params} 310 put :update, :id => @app.id, :app => {:issue_tracker_attributes => params}
309 311
310 @app.reload 312 @app.reload
311 313
312 tracker = @app.issue_tracker 314 tracker = @app.issue_tracker
313 - expect(tracker).to be_a(tracker_klass)  
314 - tracker_klass::Fields.each do |field, field_info| 315 + expect(tracker.tracker).to be_a(ErrbitPlugin::Register.issue_tracker(key))
  316 + issue_tracker_klass.fields.each do |field, field_info|
315 case field 317 case field
316 - when :ticket_properties  
317 - expect(tracker.send(field.to_sym)).to eq 'card_type = defect'  
318 - else  
319 - expect(tracker.send(field.to_sym)).to eq 'test_value' 318 + when :ticket_properties; tracker.send(field.to_sym).should == 'card_type = defect'
  319 + else tracker.options[field.to_s].should == 'test_value'
320 end 320 end
321 end 321 end
322 end 322 end
@@ -324,8 +324,11 @@ describe AppsController do @@ -324,8 +324,11 @@ describe AppsController do
324 it "should show validation notice when sufficient params are not present" do 324 it "should show validation notice when sufficient params are not present" do
325 # Leave out one required param 325 # Leave out one required param
326 # TODO. previous test was not relevant because one params can be enough. So put noone 326 # TODO. previous test was not relevant because one params can be enough. So put noone
327 - params = {:type => tracker_klass.to_s }  
328 - put :update, :id => @app.id, :app => {:issue_tracker_attributes => params} 327 + put :update, :id => @app.id, :app => {
  328 + :issue_tracker_attributes => {
  329 + :type_tracker => key.dup.to_s
  330 + }
  331 + }
329 332
330 @app.reload 333 @app.reload
331 expect(@app.issue_tracker_configured?).to eq false 334 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 }
@@ -128,7 +129,7 @@ describe ProblemsController do @@ -128,7 +129,7 @@ describe ProblemsController do
128 129
129 context 'when logged in as an admin' do 130 context 'when logged in as an admin' do
130 before do 131 before do
131 - sign_in Fabricate(:admin) 132 + sign_in admin
132 end 133 end
133 134
134 it "finds the app" do 135 it "finds the app" do
@@ -192,7 +193,7 @@ describe ProblemsController do @@ -192,7 +193,7 @@ describe ProblemsController do
192 193
193 describe "PUT /apps/:app_id/problems/:id/resolve" do 194 describe "PUT /apps/:app_id/problems/:id/resolve" do
194 before do 195 before do
195 - sign_in Fabricate(:admin) 196 + sign_in admin
196 197
197 @problem = Fabricate(:err) 198 @problem = Fabricate(:err)
198 App.stub(:find).with(@problem.app.id.to_s).and_return(@problem.app) 199 App.stub(:find).with(@problem.app.id.to_s).and_return(@problem.app)
@@ -231,76 +232,50 @@ describe ProblemsController do @@ -231,76 +232,50 @@ describe ProblemsController do
231 end 232 end
232 233
233 describe "POST /apps/:app_id/problems/:id/create_issue" do 234 describe "POST /apps/:app_id/problems/:id/create_issue" do
234 - #render_views  
235 235
236 before(:each) do 236 before(:each) do
237 - sign_in Fabricate(:admin) 237 + sign_in admin
238 end 238 end
239 239
240 context "successful issue creation" do 240 context "successful issue creation" do
241 context "lighthouseapp tracker" do 241 context "lighthouseapp tracker" do
242 let(:notice) { Fabricate :notice } 242 let(:notice) { Fabricate :notice }
243 - let(:tracker) { Fabricate :lighthouse_tracker, :app => notice.app }  
244 let(:problem) { notice.problem } 243 let(:problem) { notice.problem }
245 244
246 before(:each) do 245 before(:each) do
247 - number = 5  
248 - @issue_link = "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets/#{number}.xml"  
249 - body = "<ticket><number type=\"integer\">#{number}</number></ticket>"  
250 - stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml").  
251 - to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )  
252 - 246 + controller.stub(:problem).and_return(problem)
  247 + controller.stub(:current_user).and_return(admin)
  248 + IssueCreation.should_receive(:new).with(problem, admin, nil, request).and_return(double(:execute => true))
253 post :create_issue, :app_id => problem.app.id, :id => problem.id 249 post :create_issue, :app_id => problem.app.id, :id => problem.id
254 - problem.reload  
255 end 250 end
256 251
257 it "should redirect to problem page" do 252 it "should redirect to problem page" do
258 expect(response).to redirect_to( app_problem_path(problem.app, problem) ) 253 expect(response).to redirect_to( app_problem_path(problem.app, problem) )
  254 + expect(flash[:error]).to be_blank
259 end 255 end
260 end 256 end
261 end 257 end
262 258
263 - context "absent issue tracker" do  
264 - let(:problem) { Fabricate :problem }  
265 - 259 + context "error during request to a tracker" do
266 before(:each) do 260 before(:each) do
  261 + IssueCreation.should_receive(:new).with(problem, admin, nil, request).and_return(
  262 + double(:execute => false, :errors => double(:full_messages => ['not create']))
  263 + )
  264 + controller.stub(:problem).and_return(problem)
267 post :create_issue, :app_id => problem.app.id, :id => problem.id 265 post :create_issue, :app_id => problem.app.id, :id => problem.id
268 end 266 end
269 267
270 it "should redirect to problem page" do 268 it "should redirect to problem page" do
271 expect(response).to redirect_to( app_problem_path(problem.app, problem) ) 269 expect(response).to redirect_to( app_problem_path(problem.app, problem) )
272 - end  
273 -  
274 - it "should set flash error message telling issue tracker of the app doesn't exist" do  
275 - expect(flash[:error]).to eq "This app has no issue tracker setup." 270 + expect(flash[:error]).to eql 'not create'
276 end 271 end
277 end 272 end
278 273
279 - context "error during request to a tracker" do  
280 - context "lighthouseapp tracker" do  
281 - let(:tracker) { Fabricate :lighthouse_tracker }  
282 - let(:err) { Fabricate(:err, :problem => Fabricate(:problem, :app => tracker.app)) }  
283 -  
284 - before(:each) do  
285 - stub_request(:post, "http://#{tracker.account}.lighthouseapp.com/projects/#{tracker.project_id}/tickets.xml").to_return(:status => 500)  
286 -  
287 - post :create_issue, :app_id => err.app.id, :id => err.problem.id  
288 - end  
289 -  
290 - it "should redirect to problem page" do  
291 - expect(response).to redirect_to( app_problem_path(err.app, err.problem) )  
292 - end  
293 -  
294 - it "should notify of connection error" do  
295 - expect(flash[:error]).to include("There was an error during issue creation:")  
296 - end  
297 - end  
298 - end  
299 end 274 end
300 275
301 describe "DELETE /apps/:app_id/problems/:id/unlink_issue" do 276 describe "DELETE /apps/:app_id/problems/:id/unlink_issue" do
302 before(:each) do 277 before(:each) do
303 - sign_in Fabricate(:admin) 278 + sign_in admin
304 end 279 end
305 280
306 context "problem with issue" do 281 context "problem with issue" do
@@ -336,7 +311,7 @@ describe ProblemsController do @@ -336,7 +311,7 @@ describe ProblemsController do
336 311
337 describe "Bulk Actions" do 312 describe "Bulk Actions" do
338 before(:each) do 313 before(:each) do
339 - sign_in Fabricate(:admin) 314 + sign_in admin
340 @problem1 = Fabricate(:err, :problem => Fabricate(:problem, :resolved => true)).problem 315 @problem1 = Fabricate(:err, :problem => Fabricate(:problem, :resolved => true)).problem
341 @problem2 = Fabricate(:err, :problem => Fabricate(:problem, :resolved => false)).problem 316 @problem2 = Fabricate(:err, :problem => Fabricate(:problem, :resolved => false)).problem
342 end 317 end
spec/decorators/issue_tracker_decorator_spec.rb
@@ -2,37 +2,28 @@ require &#39;spec_helper&#39; @@ -2,37 +2,28 @@ require &#39;spec_helper&#39;
2 2
3 describe IssueTrackerDecorator do 3 describe IssueTrackerDecorator do
4 4
5 - class Foo  
6 - Note = 'hello <strong></strong>'  
7 - Fields = [  
8 - [:foo, :bar]  
9 - ]  
10 - Label = 'foo'  
11 - def self.label; Label; end  
12 - def _type  
13 - 'Foo'  
14 - end 5 + let(:none_tracker) do
  6 + ErrbitPlugin::NoneIssueTracker.new(Object.new, 'none')
15 end 7 end
16 8
17 - class Bar  
18 - Label = 'bar'  
19 - def self.label; Label; end  
20 - def _type  
21 - 'Bar'  
22 - end 9 + let(:tracker) do
  10 + ErrbitPlugin::FakeIssueTracker.new(Object.new, 'fake')
23 end 11 end
24 12
25 - describe "#note" do 13 + let(:decorator) do
  14 + IssueTrackerDecorator.new(tracker, 'fake')
  15 + end
26 16
  17 + describe "#note" do
27 18
28 it 'return the html_safe of Note' do 19 it 'return the html_safe of Note' do
29 - expect(IssueTrackerDecorator.new(Foo).note).to eql 'hello <strong></strong>'.html_safe 20 + expect(decorator.note).to eql tracker.note
30 end 21 end
31 end 22 end
32 23
33 describe "#issue_trackers" do 24 describe "#issue_trackers" do
34 it 'return an array of IssueTrackerDecorator' do 25 it 'return an array of IssueTrackerDecorator' do
35 - IssueTrackerDecorator.new(Foo).issue_trackers do |it| 26 + decorator.issue_trackers do |it|
36 expect(it).to be_a(IssueTrackerDecorator) 27 expect(it).to be_a(IssueTrackerDecorator)
37 end 28 end
38 end 29 end
@@ -40,20 +31,20 @@ describe IssueTrackerDecorator do @@ -40,20 +31,20 @@ describe IssueTrackerDecorator do
40 31
41 describe "#fields" do 32 describe "#fields" do
42 it 'return all Fields define decorate' do 33 it 'return all Fields define decorate' do
43 - IssueTrackerDecorator.new(Foo).fields do |itf| 34 + decorator.fields do |itf|
44 expect(itf).to be_a(IssueTrackerFieldDecorator) 35 expect(itf).to be_a(IssueTrackerFieldDecorator)
45 - expect(itf.object).to eql :foo  
46 - expect(itf.field_info).to eql :bar 36 + expect([:foo, :bar]).to be_include(itf.object)
  37 + expect([{:label => 'foo'}, {:label => 'bar'}]).to be_include(itf.field_info)
47 end 38 end
48 end 39 end
49 end 40 end
50 41
51 describe "#params_class" do 42 describe "#params_class" do
52 it 'add the label in class' do 43 it 'add the label in class' do
53 - expect(IssueTrackerDecorator.new(Bar).params_class(Foo.new)).to eql 'bar' 44 + expect(decorator.params_class(IssueTracker.new(:type_tracker => 'none'))).to eql 'fake'
54 end 45 end
55 it 'add chosen class if _type is same' do 46 it 'add chosen class if _type is same' do
56 - expect(IssueTrackerDecorator.new(Foo).params_class(Foo.new)).to eql 'chosen foo' 47 + expect(decorator.params_class(IssueTracker.new(:type_tracker => 'fake'))).to eql 'chosen fake'
57 end 48 end
58 end 49 end
59 end 50 end
spec/fabricators/issue_tracker_fabricator.rb
1 Fabricator :issue_tracker do 1 Fabricator :issue_tracker do
2 app 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  
22 -  
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  
41 end 3 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 + class FakeIssueTracker
  5 + def initialize(app, params); end
  6 + def configured?; true; end
  7 + def create_issue(problem,user) ; true; end
  8 + end
  9 + subject(:issue_creation) do
  10 + IssueCreation.new(problem, user, tracker_name, request)
  11 + end
5 12
  13 + let(:request) do
  14 + double(:request,
  15 + :host => 'github.com',
  16 + :port => '80',
  17 + :scheme => 'http'
  18 + )
  19 + end
6 let(:problem) { notice.problem } 20 let(:problem) { notice.problem }
7 let(:notice) { Fabricate(:notice) } 21 let(:notice) { Fabricate(:notice) }
8 let(:user) { Fabricate(:admin) } 22 let(:user) { Fabricate(:admin) }
@@ -15,8 +29,9 @@ describe IssueCreation do @@ -15,8 +29,9 @@ describe IssueCreation do
15 end 29 end
16 30
17 it 'creates an issue if issue tracker is configured' do 31 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) 32 + a = problem.app
  33 + a.build_issue_tracker
  34 + expect(ErrbitPlugin::Register).to receive(:issue_tracker).and_return(FakeIssueTracker)
20 issue_creation.execute 35 issue_creation.execute
21 expect(errors).to be_empty 36 expect(errors).to be_empty
22 end 37 end
@@ -44,7 +59,9 @@ describe IssueCreation do @@ -44,7 +59,9 @@ describe IssueCreation do
44 user.github_oauth_token = 'oauthtoken' 59 user.github_oauth_token = 'oauthtoken'
45 user.save! 60 user.save!
46 61
47 - GithubIssuesTracker.any_instance.should_receive(:create_issue) 62 + ErrbitGithubPlugin::IssueTracker.should_receive(:new).and_return(
  63 + double(:create_issue => true)
  64 + )
48 issue_creation.execute 65 issue_creation.execute
49 expect(errors).to be_empty 66 expect(errors).to be_empty
50 end 67 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,47 +0,0 @@ @@ -1,47 +0,0 @@
1 -require 'spec_helper'  
2 -  
3 -describe IssueTrackers::GithubIssuesTracker do  
4 - it "should create an issue on GitHub Issues with problem params, and set issue link for problem" do  
5 - repo = "test_user/test_repo"  
6 - notice = Fabricate :notice  
7 - notice.app.github_repo = repo  
8 - tracker = Fabricate :github_issues_tracker, :app => notice.app  
9 - problem = notice.problem  
10 -  
11 - number = 5  
12 - @issue_link = "https://github.com/#{repo}/issues/#{number}"  
13 - body = <<EOF  
14 -{  
15 - "position": 1.0,  
16 - "number": #{number},  
17 - "votes": 0,  
18 - "created_at": "2010/01/21 13:45:59 -0800",  
19 - "comments": 0,  
20 - "body": "Test Body",  
21 - "title": "Test Issue",  
22 - "user": "test_user",  
23 - "state": "open",  
24 - "html_url": "#{@issue_link}"  
25 -}  
26 -EOF  
27 -  
28 - stub_request(:post,  
29 - "https://#{tracker.username}:#{tracker.password}@api.github.com/repos/#{repo}/issues").  
30 - to_return(:status => 201,  
31 - :headers => {  
32 - 'Location' => @issue_link,  
33 - 'Content-Type' => 'application/json',  
34 - },  
35 - :body => body )  
36 -  
37 - problem.app.issue_tracker.create_issue(problem)  
38 - problem.reload  
39 -  
40 - requested = have_requested(:post, "https://#{tracker.username}:#{tracker.password}@api.github.com/repos/#{repo}/issues")  
41 - expect(WebMock).to requested.with(:body => /[production][foo#bar] FooError: Too Much Bar/)  
42 - expect(WebMock).to requested.with(:body => /See this exception on Errbit/)  
43 -  
44 - expect(problem.issue_link).to eq @issue_link  
45 - end  
46 -end  
47 -  
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/changes/example/file'  
34 - expect(t.url_to_file("/example/file", 25)).  
35 - to eq 'http://redmine.example.com/projects/errbit/repository/revisions/master/changes/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/changes/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,56 @@ describe Problem do @@ -370,6 +370,56 @@ 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 + it = IssueTracker.new
  395 + it.stub(:tracker).and_return(double(:configured? => true, :label => 'foo'))
  396 + it
  397 + end
  398 +
  399 + it 'return the issue_tracker label' do
  400 + expect(problem.issue_type).to eql 'foo'
  401 + end
  402 + end
  403 +
  404 + context "with issue_tracker not valid associate to app" do
  405 + let(:issue_tracker) do
  406 + it = IssueTracker.new
  407 + it.stub(:tracker).and_return(double(:configured? => false))
  408 + it
  409 + end
  410 + it 'return nil' do
  411 + expect(problem.issue_type).to be_nil
  412 + end
  413 + end
  414 + end
  415 +
  416 + context "with issue_type fill in Problem" do
  417 + it 'return the value associate' do
  418 + expect(Problem.new(:issue_type => 'foo').issue_type).to eql 'foo'
  419 + end
  420 + end
  421 + end
  422 +
373 423
374 end 424 end
375 425
spec/spec_helper.rb
@@ -24,6 +24,7 @@ require &#39;webmock/rspec&#39; @@ -24,6 +24,7 @@ require &#39;webmock/rspec&#39;
24 require 'xmpp4r' 24 require 'xmpp4r'
25 require 'xmpp4r/muc' 25 require 'xmpp4r/muc'
26 require 'mongoid-rspec' 26 require 'mongoid-rspec'
  27 +require 'errbit_plugin/issue_trackers/fake'
27 28
28 29
29 # Requires supporting files with custom matchers and macros, etc, 30 # Requires supporting files with custom matchers and macros, etc,
@@ -54,7 +55,7 @@ RSpec.configure do |config| @@ -54,7 +55,7 @@ RSpec.configure do |config|
54 init_haml_helpers 55 init_haml_helpers
55 end 56 end
56 57
57 - config.after(:all) do 58 + config.before(:all) do
58 WebMock.disable_net_connect! :allow => /coveralls\.io|127\.0\.0\.1/ 59 WebMock.disable_net_connect! :allow => /coveralls\.io|127\.0\.0\.1/
59 end 60 end
60 end 61 end
spec/views/problems/show.html.haml_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 describe "problems/show.html.haml" do 3 describe "problems/show.html.haml" do
  4 + class PivotalLabsTracker
  5 + def initialize(app, params); end
  6 + def label; 'pivotal'; end
  7 + def configured?; true; end
  8 + def comments_allowed?; false; end
  9 + end
  10 +
  11 + class GithubIssuesTracker
  12 + def initialize(app, params); end
  13 + def label; 'github'; end
  14 + def configured?; true; end
  15 + def comments_allowed?; false; end
  16 + end
  17 +
4 let(:problem) { Fabricate(:problem) } 18 let(:problem) { Fabricate(:problem) }
5 let(:comment) { Fabricate(:comment) } 19 let(:comment) { Fabricate(:comment) }
6 20
@@ -16,7 +30,8 @@ describe &quot;problems/show.html.haml&quot; do @@ -16,7 +30,8 @@ describe &quot;problems/show.html.haml&quot; do
16 end 30 end
17 31
18 def with_issue_tracker(tracker, problem) 32 def with_issue_tracker(tracker, problem)
19 - problem.app.issue_tracker = tracker.new :api_token => "token token token", :project_id => "1234" 33 + problem.app.issue_tracker = IssueTracker.new :type_tracker => tracker, :options => {:api_token => "token token token", :project_id => "1234"}
  34 + ErrbitPlugin::Register.stub(:issue_tracker).with(tracker).and_return(tracker.constantize)
20 view.stub(:problem).and_return(problem) 35 view.stub(:problem).and_return(problem)
21 view.stub(:app).and_return(problem.app) 36 view.stub(:app).and_return(problem.app)
22 end 37 end
@@ -75,7 +90,7 @@ describe &quot;problems/show.html.haml&quot; do @@ -75,7 +90,7 @@ describe &quot;problems/show.html.haml&quot; do
75 90
76 it 'should allow creating issue for github if application has a github tracker' do 91 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")) 92 problem = Fabricate(:problem_with_comments, :app => Fabricate(:app, :github_repo => "test_user/test_repo"))
78 - with_issue_tracker(GithubIssuesTracker, problem) 93 + with_issue_tracker("GithubIssuesTracker", problem)
79 view.stub(:problem).and_return(problem) 94 view.stub(:problem).and_return(problem)
80 view.stub(:app).and_return(problem.app) 95 view.stub(:app).and_return(problem.app)
81 render 96 render
@@ -96,16 +111,15 @@ describe &quot;problems/show.html.haml&quot; do @@ -96,16 +111,15 @@ describe &quot;problems/show.html.haml&quot; do
96 111
97 end 112 end
98 113
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 - } 114 + context "with tracker associate on app" do
  115 + before do
  116 + with_issue_tracker("PivotalLabsTracker", problem)
  117 + end
104 context "with problem without issue link" do 118 context "with problem without issue link" do
105 - let(:problem){ Problem.new(:new_record => false, :app => app) } 119 + before do
  120 + problem.issue_link = nil
  121 + end
106 it 'not see link if no issue tracker' do 122 it 'not see link if no issue tracker' do
107 - view.stub(:problem).and_return(problem)  
108 - view.stub(:app).and_return(problem.app)  
109 render 123 render
110 expect(view.content_for(:action_bar)).to match(/create issue/) 124 expect(view.content_for(:action_bar)).to match(/create issue/)
111 end 125 end
@@ -113,11 +127,11 @@ describe &quot;problems/show.html.haml&quot; do @@ -113,11 +127,11 @@ describe &quot;problems/show.html.haml&quot; do
113 end 127 end
114 128
115 context "with problem with issue link" do 129 context "with problem with issue link" do
116 - let(:problem){ Problem.new(:new_record => false, :app => app, :issue_link => 'http://foo') } 130 + before do
  131 + problem.issue_link = 'http://foo'
  132 + end
117 133
118 it 'not see link if no issue tracker' do 134 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 135 render
122 expect(view.content_for(:action_bar)).to_not match(/create issue/) 136 expect(view.content_for(:action_bar)).to_not match(/create issue/)
123 end 137 end
@@ -147,7 +161,7 @@ describe &quot;problems/show.html.haml&quot; do @@ -147,7 +161,7 @@ describe &quot;problems/show.html.haml&quot; do
147 context "with issue tracker" do 161 context "with issue tracker" do
148 it 'should not display the comments section' do 162 it 'should not display the comments section' do
149 problem = Fabricate(:problem) 163 problem = Fabricate(:problem)
150 - with_issue_tracker(PivotalLabsTracker, problem) 164 + with_issue_tracker("PivotalLabsTracker", problem)
151 render 165 render
152 expect(view.view_flow.get(:comments)).to be_blank 166 expect(view.view_flow.get(:comments)).to be_blank
153 end 167 end
@@ -155,7 +169,7 @@ describe &quot;problems/show.html.haml&quot; do @@ -155,7 +169,7 @@ describe &quot;problems/show.html.haml&quot; do
155 it 'should display existing comments' do 169 it 'should display existing comments' do
156 problem = Fabricate(:problem_with_comments) 170 problem = Fabricate(:problem_with_comments)
157 problem.reload 171 problem.reload
158 - with_issue_tracker(PivotalLabsTracker, problem) 172 + with_issue_tracker("PivotalLabsTracker", problem)
159 render 173 render
160 174
161 expect(view.content_for(:comments)).to include('Test comment') 175 expect(view.content_for(:comments)).to include('Test comment')