Commit ac8d29a562cb5f44832106643398b9b4013f7da9

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

Merge pull request #612 from errbit/features/extract_issue_tracker

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

2.14 KB

app/assets/images/github_inactive.png

1.05 KB

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