Commit ac8d29a562cb5f44832106643398b9b4013f7da9
Exists in
master
and in
1 other branch
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 < 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 < 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 < 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 < 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 < 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 < 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(', ') | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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/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 "action_mailer/railtie" |
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 < 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 | ... | ... |
... | ... | @@ -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
spec/acceptance/acceptance_helper.rb
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 "Regeneration api_Key" 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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 'spec_helper' |
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 'spec_helper' |
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 'spec_helper' |
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 "problems/show.html.haml" 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 "problems/show.html.haml" 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 "problems/show.html.haml" 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 "problems/show.html.haml" 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 "problems/show.html.haml" 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') | ... | ... |