Commit 90033270a10b952161d1e545993761516d5e8566

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

Extract all IssueTracker system and migrate to errbit_plugin

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

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