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
Gemfile.lock
| ... | ... | @@ -60,10 +60,17 @@ GEM |
| 60 | 60 | factory_girl_rails (1.0.1) |
| 61 | 61 | factory_girl (~> 1.3) |
| 62 | 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 | 69 | haml (3.0.25) |
| 64 | 70 | happymapper (0.3.2) |
| 65 | 71 | libxml-ruby (~> 1.1.3) |
| 66 | 72 | has_scope (0.5.1) |
| 73 | + hashie (1.0.0) | |
| 67 | 74 | hoptoad_notifier (2.4.11) |
| 68 | 75 | activesupport |
| 69 | 76 | builder |
| ... | ... | @@ -96,7 +103,16 @@ GEM |
| 96 | 103 | bundler (>= 0.9.19) |
| 97 | 104 | rails (~> 3.0.0) |
| 98 | 105 | railties (~> 3.0.0) |
| 106 | + multi_json (1.0.3) | |
| 107 | + multipart-post (1.1.3) | |
| 99 | 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 | 116 | orm_adapter (0.0.5) |
| 101 | 117 | pivotal-tracker (0.2.0) |
| 102 | 118 | builder |
| ... | ... | @@ -123,6 +139,8 @@ GEM |
| 123 | 139 | rake (>= 0.8.7) |
| 124 | 140 | thor (~> 0.14.4) |
| 125 | 141 | rake (0.8.7) |
| 142 | + rash (0.3.0) | |
| 143 | + hashie (~> 1.0.0) | |
| 126 | 144 | rbx-require-relative (0.0.5) |
| 127 | 145 | responders (0.6.4) |
| 128 | 146 | rest-client (1.5.1) |
| ... | ... | @@ -193,6 +211,7 @@ DEPENDENCIES |
| 193 | 211 | mongoid (= 2.0.2) |
| 194 | 212 | mongoid_rails_migrations |
| 195 | 213 | nokogiri |
| 214 | + octokit | |
| 196 | 215 | pivotal-tracker |
| 197 | 216 | rails (= 3.0.5) |
| 198 | 217 | redmine_client! | ... | ... |
| ... | ... | @@ -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 | = label_tag :type_none, :for => label_for_attr(w, 'type_issuetracker'), :class => "label_radio none" do |
| 7 | 7 | = w.radio_button :type, "IssueTracker", 'data-section' => 'none' |
| 8 | 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 | 12 | = label_tag :type_lighthouseapp, :for => label_for_attr(w, 'type_lighthousetracker'), :class => "label_radio lighthouseapp" do |
| 10 | 13 | = w.radio_button :type, "LighthouseTracker", 'data-section' => 'lighthouse' |
| 11 | 14 | Lighthouse |
| ... | ... | @@ -15,7 +18,6 @@ |
| 15 | 18 | = label_tag :type_pivotal, :for => label_for_attr(w, 'type_pivotallabstracker'), :class => "label_radio pivotal" do |
| 16 | 19 | = w.radio_button :type, "PivotalLabsTracker", 'data-section' => 'pivotal' |
| 17 | 20 | Pivotal Tracker |
| 18 | - %br | |
| 19 | 21 | = label_tag :type_fogbugz, :for => label_for_attr(w, 'type_fogbugztracker'), :class => "label_radio fogbugz" do |
| 20 | 22 | = w.radio_button :type, "FogbugzTracker", 'data-section' => 'fogbugz' |
| 21 | 23 | FogBugz |
| ... | ... | @@ -25,6 +27,13 @@ |
| 25 | 27 | |
| 26 | 28 | %div.tracker_params.none{:class => (w.object && !(w.object.class < IssueTracker)) ? 'chosen' : nil} |
| 27 | 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 | 37 | %div.tracker_params.lighthouse{:class => w.object.is_a?(LighthouseTracker) ? 'chosen' : nil} |
| 29 | 38 | = w.label :account, "Account" |
| 30 | 39 | = w.text_field :account, :placeholder => "abc from abc.lighthouseapp.com" | ... | ... |
| ... | ... | @@ -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 | 540 | |
| 541 | 541 | /* Icons for Issue Tracker Radio Buttons */ |
| 542 | 542 | div.issue_tracker.nested label.label_radio { |
| 543 | - color: #777777; | |
| 543 | + color: #929292; | |
| 544 | 544 | padding-left: 33px; |
| 545 | 545 | margin-bottom: 6px; |
| 546 | 546 | margin-right: 8px; |
| ... | ... | @@ -551,7 +551,7 @@ div.issue_tracker.nested .label_radio input { |
| 551 | 551 | } |
| 552 | 552 | |
| 553 | 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 | 562 | div.issue_tracker.nested label.mingle { background: url(/images/mingle_inactive.png) no-repeat; } |
| 563 | 563 | div.issue_tracker.nested label.fogbugz { background: url(/images/fogbugz_inactive.png) no-repeat; } |
| 564 | 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 | 566 | /* Active icons */ |
| 566 | 567 | div.issue_tracker.nested label.r_on.none { background: url(/images/none_create.png) no-repeat; } |
| 567 | 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 | 570 | div.issue_tracker.nested label.r_on.mingle { background: url(/images/mingle_create.png) no-repeat; } |
| 570 | 571 | div.issue_tracker.nested label.r_on.fogbugz { background: url(/images/fogbugz_create.png) no-repeat; } |
| 571 | 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 | 575 | /* Apps Table */ |
| 575 | 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 | 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 | 685 | #action-bar a.lighthouseapp_goto { |
| 681 | 686 | background: transparent url(/images/lighthouseapp_goto.png) 6px 5px no-repeat; |
| 682 | 687 | } |
| ... | ... | @@ -697,6 +702,10 @@ table.tally th.value { |
| 697 | 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 | 709 | /* Notices Pagination */ |
| 701 | 710 | .notice-pagination { |
| 702 | 711 | float: left; | ... | ... |
spec/controllers/apps_controller_spec.rb
| ... | ... | @@ -402,6 +402,33 @@ describe AppsController do |
| 402 | 402 | end |
| 403 | 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 | 433 | end |
| 407 | 434 | end |
| ... | ... | @@ -435,3 +462,4 @@ describe AppsController do |
| 435 | 462 | end |
| 436 | 463 | |
| 437 | 464 | end |
| 465 | + | ... | ... |
spec/controllers/errs_controller_spec.rb
| ... | ... | @@ -395,6 +395,53 @@ describe ErrsController do |
| 395 | 395 | err.issue_link.should == @issue_link.sub(/\.xml$/, '') |
| 396 | 396 | end |
| 397 | 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 | 445 | end |
| 399 | 446 | |
| 400 | 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 | 20 | e.ticket_properties 'card_type = Defect, defect_status = open, priority = essential' |
| 21 | 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 | + | ... | ... |