Commit e8cd05155a74a59076aa859281cc5bd16b81f0ef

Authored by Nathan Broadbent
1 parent 32964b43
Exists in master and in 1 other branch production

Added Github Issues Tracker.

@@ -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
@@ -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!
app/models/issue_trackers/github_tracker.rb 0 → 100644
@@ -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"
app/views/issue_trackers/github_body.txt.erb 0 → 100644
@@ -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 +
public/images/github_create.png 0 → 100644

2.3 KB

public/images/github_goto.png 0 → 100644

2.3 KB

public/images/github_inactive.png 0 → 100644

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 =&gt; :issue_tracker, :class =&gt; :mingle_tra @@ -20,3 +20,9 @@ Factory.define :mingle_tracker, :parent =&gt; :issue_tracker, :class =&gt; :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 +