Commit e8cd05155a74a59076aa859281cc5bd16b81f0ef
1 parent
32964b43
Exists in
master
and in
1 other branch
Added Github Issues Tracker.
Showing
12 changed files
with
188 additions
and
4 deletions
Show diff stats
Gemfile
@@ -12,6 +12,7 @@ gem 'mongoid_rails_migrations' | @@ -12,6 +12,7 @@ gem 'mongoid_rails_migrations' | ||
12 | gem 'useragent', '~> 0.3.1' | 12 | gem 'useragent', '~> 0.3.1' |
13 | gem 'pivotal-tracker' | 13 | gem 'pivotal-tracker' |
14 | gem 'ruby-fogbugz', :require => 'fogbugz' | 14 | gem 'ruby-fogbugz', :require => 'fogbugz' |
15 | +gem 'octokit' | ||
15 | gem 'inherited_resources' | 16 | gem 'inherited_resources' |
16 | 17 | ||
17 | group :production do | 18 | group :production do |
Gemfile.lock
@@ -60,10 +60,17 @@ GEM | @@ -60,10 +60,17 @@ GEM | ||
60 | factory_girl_rails (1.0.1) | 60 | factory_girl_rails (1.0.1) |
61 | factory_girl (~> 1.3) | 61 | factory_girl (~> 1.3) |
62 | railties (>= 3.0.0) | 62 | railties (>= 3.0.0) |
63 | + faraday (0.6.1) | ||
64 | + addressable (~> 2.2.4) | ||
65 | + multipart-post (~> 1.1.0) | ||
66 | + rack (>= 1.1.0, < 2) | ||
67 | + faraday_middleware (0.6.5) | ||
68 | + faraday (~> 0.6.0) | ||
63 | haml (3.0.25) | 69 | haml (3.0.25) |
64 | happymapper (0.3.2) | 70 | happymapper (0.3.2) |
65 | libxml-ruby (~> 1.1.3) | 71 | libxml-ruby (~> 1.1.3) |
66 | has_scope (0.5.1) | 72 | has_scope (0.5.1) |
73 | + hashie (1.0.0) | ||
67 | hoptoad_notifier (2.4.11) | 74 | hoptoad_notifier (2.4.11) |
68 | activesupport | 75 | activesupport |
69 | builder | 76 | builder |
@@ -96,7 +103,16 @@ GEM | @@ -96,7 +103,16 @@ GEM | ||
96 | bundler (>= 0.9.19) | 103 | bundler (>= 0.9.19) |
97 | rails (~> 3.0.0) | 104 | rails (~> 3.0.0) |
98 | railties (~> 3.0.0) | 105 | railties (~> 3.0.0) |
106 | + multi_json (1.0.3) | ||
107 | + multipart-post (1.1.3) | ||
99 | nokogiri (1.4.4) | 108 | nokogiri (1.4.4) |
109 | + octokit (0.6.3) | ||
110 | + addressable (~> 2.2.4) | ||
111 | + faraday (~> 0.6.0) | ||
112 | + faraday_middleware (~> 0.6.0) | ||
113 | + hashie (~> 1.0.0) | ||
114 | + multi_json (~> 1.0.0) | ||
115 | + rash (~> 0.3.0) | ||
100 | orm_adapter (0.0.5) | 116 | orm_adapter (0.0.5) |
101 | pivotal-tracker (0.2.0) | 117 | pivotal-tracker (0.2.0) |
102 | builder | 118 | builder |
@@ -123,6 +139,8 @@ GEM | @@ -123,6 +139,8 @@ GEM | ||
123 | rake (>= 0.8.7) | 139 | rake (>= 0.8.7) |
124 | thor (~> 0.14.4) | 140 | thor (~> 0.14.4) |
125 | rake (0.8.7) | 141 | rake (0.8.7) |
142 | + rash (0.3.0) | ||
143 | + hashie (~> 1.0.0) | ||
126 | rbx-require-relative (0.0.5) | 144 | rbx-require-relative (0.0.5) |
127 | responders (0.6.4) | 145 | responders (0.6.4) |
128 | rest-client (1.5.1) | 146 | rest-client (1.5.1) |
@@ -193,6 +211,7 @@ DEPENDENCIES | @@ -193,6 +211,7 @@ DEPENDENCIES | ||
193 | mongoid (= 2.0.2) | 211 | mongoid (= 2.0.2) |
194 | mongoid_rails_migrations | 212 | mongoid_rails_migrations |
195 | nokogiri | 213 | nokogiri |
214 | + octokit | ||
196 | pivotal-tracker | 215 | pivotal-tracker |
197 | rails (= 3.0.5) | 216 | rails (= 3.0.5) |
198 | redmine_client! | 217 | redmine_client! |
@@ -0,0 +1,20 @@ | @@ -0,0 +1,20 @@ | ||
1 | +class GithubTracker < IssueTracker | ||
2 | + def self.label; "github"; end | ||
3 | + | ||
4 | + def check_params | ||
5 | + if %w(project_id username api_token ).detect {|f| self[f].blank? } | ||
6 | + errors.add :base, 'You must specify your Github repository, username and API token' | ||
7 | + end | ||
8 | + end | ||
9 | + | ||
10 | + def create_issue(err) | ||
11 | + client = Octokit::Client.new(:login => username, :token => api_token) | ||
12 | + issue = client.create_issue(project_id, issue_title(err), body_template.result(binding), options = {}) | ||
13 | + err.update_attribute :issue_link, issue.html_url | ||
14 | + end | ||
15 | + | ||
16 | + def body_template | ||
17 | + @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/github_body.txt.erb").gsub(/^\s*/, '')) | ||
18 | + end | ||
19 | +end | ||
20 | + |
app/views/apps/_issue_tracker_fields.html.haml
@@ -6,6 +6,9 @@ | @@ -6,6 +6,9 @@ | ||
6 | = label_tag :type_none, :for => label_for_attr(w, 'type_issuetracker'), :class => "label_radio none" do | 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' | 7 | = w.radio_button :type, "IssueTracker", 'data-section' => 'none' |
8 | (None) | 8 | (None) |
9 | + = label_tag :type_github, :for => label_for_attr(w, 'type_githubtracker'), :class => "label_radio github" do | ||
10 | + = w.radio_button :type, "GithubTracker", 'data-section' => 'github' | ||
11 | + Github Issues | ||
9 | = label_tag :type_lighthouseapp, :for => label_for_attr(w, 'type_lighthousetracker'), :class => "label_radio lighthouseapp" do | 12 | = label_tag :type_lighthouseapp, :for => label_for_attr(w, 'type_lighthousetracker'), :class => "label_radio lighthouseapp" do |
10 | = w.radio_button :type, "LighthouseTracker", 'data-section' => 'lighthouse' | 13 | = w.radio_button :type, "LighthouseTracker", 'data-section' => 'lighthouse' |
11 | Lighthouse | 14 | Lighthouse |
@@ -15,7 +18,6 @@ | @@ -15,7 +18,6 @@ | ||
15 | = label_tag :type_pivotal, :for => label_for_attr(w, 'type_pivotallabstracker'), :class => "label_radio pivotal" do | 18 | = label_tag :type_pivotal, :for => label_for_attr(w, 'type_pivotallabstracker'), :class => "label_radio pivotal" do |
16 | = w.radio_button :type, "PivotalLabsTracker", 'data-section' => 'pivotal' | 19 | = w.radio_button :type, "PivotalLabsTracker", 'data-section' => 'pivotal' |
17 | Pivotal Tracker | 20 | Pivotal Tracker |
18 | - %br | ||
19 | = label_tag :type_fogbugz, :for => label_for_attr(w, 'type_fogbugztracker'), :class => "label_radio fogbugz" do | 21 | = label_tag :type_fogbugz, :for => label_for_attr(w, 'type_fogbugztracker'), :class => "label_radio fogbugz" do |
20 | = w.radio_button :type, "FogbugzTracker", 'data-section' => 'fogbugz' | 22 | = w.radio_button :type, "FogbugzTracker", 'data-section' => 'fogbugz' |
21 | FogBugz | 23 | FogBugz |
@@ -25,6 +27,13 @@ | @@ -25,6 +27,13 @@ | ||
25 | 27 | ||
26 | %div.tracker_params.none{:class => (w.object && !(w.object.class < IssueTracker)) ? 'chosen' : nil} | 28 | %div.tracker_params.none{:class => (w.object && !(w.object.class < IssueTracker)) ? 'chosen' : nil} |
27 | %p When no issue tracker has been configured, you will be able to leave comments on errors. | 29 | %p When no issue tracker has been configured, you will be able to leave comments on errors. |
30 | + %div.tracker_params.github{:class => w.object.is_a?(GithubTracker) ? 'chosen' : nil} | ||
31 | + = w.label :project_id, "Repository" | ||
32 | + = w.text_field :project_id, :placeholder => "errbit/errbit from https://github.com/errbit/errbit" | ||
33 | + = w.label :username, "Username" | ||
34 | + = w.text_field :username, :placeholder => "Your username on Github" | ||
35 | + = w.label :api_token, "Token" | ||
36 | + = w.text_field :api_token, :placeholder => "Your API Token" | ||
28 | %div.tracker_params.lighthouse{:class => w.object.is_a?(LighthouseTracker) ? 'chosen' : nil} | 37 | %div.tracker_params.lighthouse{:class => w.object.is_a?(LighthouseTracker) ? 'chosen' : nil} |
29 | = w.label :account, "Account" | 38 | = w.label :account, "Account" |
30 | = w.text_field :account, :placeholder => "abc from abc.lighthouseapp.com" | 39 | = w.text_field :account, :placeholder => "abc from abc.lighthouseapp.com" |
@@ -0,0 +1,45 @@ | @@ -0,0 +1,45 @@ | ||
1 | +[See this exception on Errbit](<%= app_err_url err.app, err %> "See this exception on Errbit") | ||
2 | +<% if notice = err.notices.first %> | ||
3 | +# <%= notice.message %> # | ||
4 | +## Summary ## | ||
5 | +<% if notice.request['url'].present? %> | ||
6 | + ### URL ### | ||
7 | + [<%= notice.request['url'] %>](<%= notice.request['url'] %>)" | ||
8 | +<% end %> | ||
9 | +### Where ### | ||
10 | +<%= notice.err.where %> | ||
11 | + | ||
12 | +### Occured ### | ||
13 | +<%= notice.created_at.to_s(:micro) %> | ||
14 | + | ||
15 | +### Similar ### | ||
16 | +<%= (notice.err.notices_count - 1).to_s %> | ||
17 | + | ||
18 | +## Params ## | ||
19 | +``` | ||
20 | +<%= pretty_hash(notice.params) %> | ||
21 | +``` | ||
22 | + | ||
23 | +## Session ## | ||
24 | +``` | ||
25 | +<%= pretty_hash(notice.session) %> | ||
26 | +``` | ||
27 | + | ||
28 | +## Backtrace ## | ||
29 | +``` | ||
30 | +<% for line in notice.backtrace %><%= line['number'] %>: <%= line['file'].sub(/^\[PROJECT_ROOT\]/, '') %> -> **<%= line['method'] %>** | ||
31 | +<% end %> | ||
32 | +``` | ||
33 | + | ||
34 | +## Environment ## | ||
35 | + | ||
36 | +<table> | ||
37 | +<% for key, val in notice.env_vars %> | ||
38 | + <tr> | ||
39 | + <td><%= key %>:</td> | ||
40 | + <td><%= val %></td> | ||
41 | + </tr> | ||
42 | +<% end %> | ||
43 | +</table> | ||
44 | +<% end %> | ||
45 | + |
2.3 KB
2.3 KB
2.03 KB
public/stylesheets/application.css
@@ -540,7 +540,7 @@ div.issue_tracker.nested img { | @@ -540,7 +540,7 @@ div.issue_tracker.nested img { | ||
540 | 540 | ||
541 | /* Icons for Issue Tracker Radio Buttons */ | 541 | /* Icons for Issue Tracker Radio Buttons */ |
542 | div.issue_tracker.nested label.label_radio { | 542 | div.issue_tracker.nested label.label_radio { |
543 | - color: #777777; | 543 | + color: #929292; |
544 | padding-left: 33px; | 544 | padding-left: 33px; |
545 | margin-bottom: 6px; | 545 | margin-bottom: 6px; |
546 | margin-right: 8px; | 546 | margin-right: 8px; |
@@ -551,7 +551,7 @@ div.issue_tracker.nested .label_radio input { | @@ -551,7 +551,7 @@ div.issue_tracker.nested .label_radio input { | ||
551 | } | 551 | } |
552 | 552 | ||
553 | div.issue_tracker.nested label.r_on { | 553 | div.issue_tracker.nested label.r_on { |
554 | - color: #262626; | 554 | + color: #191919; |
555 | } | 555 | } |
556 | 556 | ||
557 | 557 | ||
@@ -562,6 +562,7 @@ div.issue_tracker.nested label.lighthouseapp { background: url(/images/lighthous | @@ -562,6 +562,7 @@ div.issue_tracker.nested label.lighthouseapp { background: url(/images/lighthous | ||
562 | div.issue_tracker.nested label.mingle { background: url(/images/mingle_inactive.png) no-repeat; } | 562 | div.issue_tracker.nested label.mingle { background: url(/images/mingle_inactive.png) no-repeat; } |
563 | div.issue_tracker.nested label.fogbugz { background: url(/images/fogbugz_inactive.png) no-repeat; } | 563 | div.issue_tracker.nested label.fogbugz { background: url(/images/fogbugz_inactive.png) no-repeat; } |
564 | div.issue_tracker.nested label.pivotal { background: url(/images/pivotal_inactive.png) no-repeat; } | 564 | div.issue_tracker.nested label.pivotal { background: url(/images/pivotal_inactive.png) no-repeat; } |
565 | +div.issue_tracker.nested label.github { background: url(/images/github_inactive.png) no-repeat; } | ||
565 | /* Active icons */ | 566 | /* Active icons */ |
566 | div.issue_tracker.nested label.r_on.none { background: url(/images/none_create.png) no-repeat; } | 567 | div.issue_tracker.nested label.r_on.none { background: url(/images/none_create.png) no-repeat; } |
567 | div.issue_tracker.nested label.r_on.redmine { background: url(/images/redmine_create.png) no-repeat; } | 568 | div.issue_tracker.nested label.r_on.redmine { background: url(/images/redmine_create.png) no-repeat; } |
@@ -569,7 +570,7 @@ div.issue_tracker.nested label.r_on.lighthouseapp { background: url(/images/ligh | @@ -569,7 +570,7 @@ div.issue_tracker.nested label.r_on.lighthouseapp { background: url(/images/ligh | ||
569 | div.issue_tracker.nested label.r_on.mingle { background: url(/images/mingle_create.png) no-repeat; } | 570 | div.issue_tracker.nested label.r_on.mingle { background: url(/images/mingle_create.png) no-repeat; } |
570 | div.issue_tracker.nested label.r_on.fogbugz { background: url(/images/fogbugz_create.png) no-repeat; } | 571 | div.issue_tracker.nested label.r_on.fogbugz { background: url(/images/fogbugz_create.png) no-repeat; } |
571 | div.issue_tracker.nested label.r_on.pivotal { background: url(/images/pivotal_create.png) no-repeat; } | 572 | div.issue_tracker.nested label.r_on.pivotal { background: url(/images/pivotal_create.png) no-repeat; } |
572 | - | 573 | +div.issue_tracker.nested label.r_on.github { background: url(/images/github_create.png) no-repeat; } |
573 | 574 | ||
574 | /* Apps Table */ | 575 | /* Apps Table */ |
575 | table.apps tbody tr:hover td ,table.errs tbody tr:hover td { background-color: #F2F2F2;} | 576 | table.apps tbody tr:hover td ,table.errs tbody tr:hover td { background-color: #F2F2F2;} |
@@ -677,6 +678,10 @@ table.tally th.value { | @@ -677,6 +678,10 @@ table.tally th.value { | ||
677 | background: transparent url(/images/mingle_create.png) 6px 5px no-repeat; | 678 | background: transparent url(/images/mingle_create.png) 6px 5px no-repeat; |
678 | } | 679 | } |
679 | 680 | ||
681 | +#action-bar a.github_create { | ||
682 | + background: transparent url(/images/github_create.png) 6px 5px no-repeat; | ||
683 | +} | ||
684 | + | ||
680 | #action-bar a.lighthouseapp_goto { | 685 | #action-bar a.lighthouseapp_goto { |
681 | background: transparent url(/images/lighthouseapp_goto.png) 6px 5px no-repeat; | 686 | background: transparent url(/images/lighthouseapp_goto.png) 6px 5px no-repeat; |
682 | } | 687 | } |
@@ -697,6 +702,10 @@ table.tally th.value { | @@ -697,6 +702,10 @@ table.tally th.value { | ||
697 | background: transparent url(/images/mingle_goto.png) 6px 5px no-repeat; | 702 | background: transparent url(/images/mingle_goto.png) 6px 5px no-repeat; |
698 | } | 703 | } |
699 | 704 | ||
705 | +#action-bar a.github_goto { | ||
706 | + background: transparent url(/images/github_goto.png) 6px 5px no-repeat; | ||
707 | +} | ||
708 | + | ||
700 | /* Notices Pagination */ | 709 | /* Notices Pagination */ |
701 | .notice-pagination { | 710 | .notice-pagination { |
702 | float: left; | 711 | float: left; |
spec/controllers/apps_controller_spec.rb
@@ -402,6 +402,33 @@ describe AppsController do | @@ -402,6 +402,33 @@ describe AppsController do | ||
402 | end | 402 | end |
403 | end | 403 | end |
404 | 404 | ||
405 | + context "github issues" do | ||
406 | + context 'with correct params' do | ||
407 | + before do | ||
408 | + put :update, :id => @app.id, :app => { :issue_tracker_attributes => { | ||
409 | + :type => 'GithubTracker', :project_id => 'test', :username => 'user', | ||
410 | + :api_token => '123123' | ||
411 | + } } | ||
412 | + @app.reload | ||
413 | + end | ||
414 | + | ||
415 | + subject {@app.issue_tracker} | ||
416 | + its(:type) {should == "GithubTracker"} | ||
417 | + its(:project_id) {should == 'test'} | ||
418 | + its(:username) {should == 'user'} | ||
419 | + its(:api_token) {should == '123123'} | ||
420 | + end | ||
421 | + | ||
422 | + it "should show validation notice when sufficient params are not present" do | ||
423 | + put :update, :id => @app.id, :app => { :issue_tracker_attributes => { | ||
424 | + :type => 'GithubTracker', :project_id => 'test', :username => 'user' | ||
425 | + } } | ||
426 | + @app.reload | ||
427 | + | ||
428 | + @app.issue_tracker_configured?.should == false | ||
429 | + response.body.should match(/You must specify your Github repository, username and API token/) | ||
430 | + end | ||
431 | + end | ||
405 | 432 | ||
406 | end | 433 | end |
407 | end | 434 | end |
@@ -435,3 +462,4 @@ describe AppsController do | @@ -435,3 +462,4 @@ describe AppsController do | ||
435 | end | 462 | end |
436 | 463 | ||
437 | end | 464 | end |
465 | + |
spec/controllers/errs_controller_spec.rb
@@ -395,6 +395,53 @@ describe ErrsController do | @@ -395,6 +395,53 @@ describe ErrsController do | ||
395 | err.issue_link.should == @issue_link.sub(/\.xml$/, '') | 395 | err.issue_link.should == @issue_link.sub(/\.xml$/, '') |
396 | end | 396 | end |
397 | end | 397 | end |
398 | + | ||
399 | + context "github issues tracker" do | ||
400 | + let(:notice) { Factory :notice } | ||
401 | + let(:tracker) { Factory :github_tracker, :app => notice.err.app } | ||
402 | + let(:err) { notice.err } | ||
403 | + | ||
404 | + before(:each) do | ||
405 | + number = 5 | ||
406 | + @issue_link = "https://github.com/#{tracker.project_id}/issues/#{number}" | ||
407 | + body = <<EOF | ||
408 | +{ | ||
409 | + "issue": { | ||
410 | + "position": 1.0, | ||
411 | + "number": #{number}, | ||
412 | + "votes": 0, | ||
413 | + "created_at": "2010/01/21 13:45:59 -0800", | ||
414 | + "comments": 0, | ||
415 | + "body": "Test Body", | ||
416 | + "title": "Test Issue", | ||
417 | + "user": "test_user", | ||
418 | + "state": "open", | ||
419 | + "html_url": "#{@issue_link}" | ||
420 | + } | ||
421 | +} | ||
422 | +EOF | ||
423 | + stub_request(:post, "https://#{tracker.username}%2Ftoken:#{tracker.api_token}@github.com/api/v2/json/issues/open/#{tracker.project_id}"). | ||
424 | + to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body ) | ||
425 | + | ||
426 | + post :create_issue, :app_id => err.app.id, :id => err.id | ||
427 | + err.reload | ||
428 | + end | ||
429 | + | ||
430 | + it "should make request to Github with err params" do | ||
431 | + requested = have_requested(:post, "https://#{tracker.username}%2Ftoken:#{tracker.api_token}@github.com/api/v2/json/issues/open/#{tracker.project_id}") | ||
432 | + WebMock.should requested.with(:headers => {'Content-Type' => 'application/x-www-form-urlencoded'}) | ||
433 | + WebMock.should requested.with(:body => /title=%5Bproduction%5D%5Bfoo%23bar%5D%20FooError%3A%20Too%20Much%20Bar/) | ||
434 | + WebMock.should requested.with(:body => /See%20this%20exception%20on%20Errbit/) | ||
435 | + end | ||
436 | + | ||
437 | + it "should redirect to err page" do | ||
438 | + response.should redirect_to( app_err_path(err.app, err) ) | ||
439 | + end | ||
440 | + | ||
441 | + it "should create issue link for err" do | ||
442 | + err.issue_link.should == @issue_link | ||
443 | + end | ||
444 | + end | ||
398 | end | 445 | end |
399 | 446 | ||
400 | context "absent issue tracker" do | 447 | context "absent issue tracker" do |
spec/factories/issue_tracker_factories.rb
@@ -20,3 +20,9 @@ Factory.define :mingle_tracker, :parent => :issue_tracker, :class => :mingle_tra | @@ -20,3 +20,9 @@ Factory.define :mingle_tracker, :parent => :issue_tracker, :class => :mingle_tra | ||
20 | e.ticket_properties 'card_type = Defect, defect_status = open, priority = essential' | 20 | e.ticket_properties 'card_type = Defect, defect_status = open, priority = essential' |
21 | end | 21 | end |
22 | 22 | ||
23 | +Factory.define :github_tracker, :parent => :issue_tracker, :class => :github_tracker do |e| | ||
24 | + e.project_id 'test_account/test_project' | ||
25 | + e.username 'test_username' | ||
26 | + e.api_token '12497asfa987' | ||
27 | +end | ||
28 | + |