party_foul 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +21 -10
- data/lib/generators/party_foul/install_generator.rb +3 -3
- data/lib/generators/party_foul/templates/party_foul.rb +8 -5
- data/lib/party_foul.rb +4 -4
- data/lib/party_foul/exception_handler.rb +14 -3
- data/lib/party_foul/issue_renderers/base.rb +17 -8
- data/lib/party_foul/issue_renderers/rack.rb +6 -7
- data/lib/party_foul/issue_renderers/rackless.rb +6 -7
- data/lib/party_foul/issue_renderers/rails.rb +4 -7
- data/lib/party_foul/version.rb +1 -1
- data/test/party_foul/configure_test.rb +2 -0
- data/test/party_foul/exception_handler_test.rb +66 -6
- data/test/party_foul/issue_renderers/base_test.rb +28 -4
- data/test/party_foul/issue_renderers/rack_test.rb +2 -2
- data/test/party_foul/issue_renderers/rackless_test.rb +2 -2
- data/test/party_foul/issue_renderers/rails_test.rb +2 -2
- data/test/test_helper.rb +1 -1
- data/test/tmp/config/initializers/party_foul.rb +8 -5
- metadata +6 -6
data/README.md
CHANGED
@@ -4,12 +4,12 @@
|
|
4
4
|
[![Dependency Status](https://gemnasium.com/dockyard/party_foul.png?travis)](https://gemnasium.com/dockyard/party_foul)
|
5
5
|
[![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/dockyard/party_foul)
|
6
6
|
|
7
|
-
Rails exceptions automatically opened as issues on
|
7
|
+
Rails exceptions automatically opened as issues on GitHub
|
8
8
|
|
9
9
|
## Looking for help? ##
|
10
10
|
|
11
11
|
If it is a bug [please open an issue on
|
12
|
-
|
12
|
+
GitHub](https://github.com/dockyard/party_foul/issues). If you need help using
|
13
13
|
the gem please ask the question on
|
14
14
|
[Stack Overflow](http://stackoverflow.com). Be sure to tag the
|
15
15
|
question with `DockYard` so we can find it.
|
@@ -19,7 +19,7 @@ question with `DockYard` so we can find it.
|
|
19
19
|
`PartyFoul` captures exceptions in your application and does the
|
20
20
|
following:
|
21
21
|
|
22
|
-
1. Attempt to find a matching issue in your
|
22
|
+
1. Attempt to find a matching issue in your GitHub repo
|
23
23
|
2. If no matching issue is found an new issue is created with a
|
24
24
|
unique title, session information, and stack trace. The issue is
|
25
25
|
tagged as a `bug`. A new comment is added with relevant data on the
|
@@ -36,7 +36,7 @@ application state.
|
|
36
36
|
|
37
37
|
## Installation ##
|
38
38
|
|
39
|
-
**Note** We highly recommend that you create a new
|
39
|
+
**Note** We highly recommend that you create a new GitHub account that is
|
40
40
|
a collaborator on your repository. Use this new account's credentials
|
41
41
|
for the installation below. If you use your own account you will
|
42
42
|
not receive emails when issues are created, updated, reopened, etc...
|
@@ -55,7 +55,7 @@ If you are using Rails you can run the install generator.
|
|
55
55
|
rails g party_foul:install
|
56
56
|
```
|
57
57
|
|
58
|
-
This prompts you for the
|
58
|
+
This prompts you for the GitHub credentials of the account that is
|
59
59
|
opening the issues. The OAuth token for that account is stored
|
60
60
|
in `config/initializers/party_foul.rb`. You may want to remove the token
|
61
61
|
string and store in an environment variable. It is best not to store the
|
@@ -76,15 +76,15 @@ PartyFoul.configure do |config|
|
|
76
76
|
# The constants here *must* be represented as strings
|
77
77
|
config.blacklisted_exceptions = ['ActiveRecord::RecordNotFound', 'ActionController::RoutingError']
|
78
78
|
|
79
|
-
# The OAuth token for the account that is opening the issues on
|
79
|
+
# The OAuth token for the account that is opening the issues on GitHub
|
80
80
|
config.oauth_token = 'abcdefgh1234567890'
|
81
81
|
|
82
|
-
# The API endpoint for
|
83
|
-
# instance of Enterprise
|
82
|
+
# The API endpoint for GitHub. Unless you are hosting a private
|
83
|
+
# instance of Enterprise GitHub you do not need to include this
|
84
84
|
config.endpoint = 'https://api.github.com'
|
85
85
|
|
86
|
-
# The Web URL for
|
87
|
-
# instance of Enterprise
|
86
|
+
# The Web URL for GitHub. Unless you are hosting a private
|
87
|
+
# instance of Enterprise GitHub you do not need to include this
|
88
88
|
config.web_url = 'https://github.com'
|
89
89
|
|
90
90
|
# The organization or user that owns the target repository
|
@@ -176,6 +176,17 @@ PartyFoul.configure do |config|
|
|
176
176
|
end
|
177
177
|
```
|
178
178
|
|
179
|
+
### Limiting Comments
|
180
|
+
|
181
|
+
You can specify a limit on the number of comments added to each issue. The main issue will still be updated
|
182
|
+
with a count and time for each occurrence, regardless of the limit.
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
PartyFoul.configure do |config|
|
186
|
+
config.comment_limit = 10
|
187
|
+
end
|
188
|
+
```
|
189
|
+
|
179
190
|
## Tracking errors outside of an HTTP request
|
180
191
|
|
181
192
|
You may want to track errors outside of a reqular HTTP stack. In that
|
@@ -7,8 +7,8 @@ module PartyFoul
|
|
7
7
|
source_root File.expand_path('../templates', __FILE__)
|
8
8
|
|
9
9
|
def create_initializer_file
|
10
|
-
login = ask '
|
11
|
-
password = STDIN.noecho { ask '
|
10
|
+
login = ask 'GitHub login:'
|
11
|
+
password = STDIN.noecho { ask 'GitHub password:' }
|
12
12
|
@owner = ask_with_default "\nRepository owner:", login
|
13
13
|
@repo = ask 'Repository name:'
|
14
14
|
@endpoint = ask_with_default 'Api Endpoint:', 'https://api.github.com'
|
@@ -19,7 +19,7 @@ module PartyFoul
|
|
19
19
|
@oauth_token = github.oauth.create(scopes: ['repo'], note: "PartyFoul #{@owner}/#{@repo}", note_url: "#{@web_url}/#{@owner}/#{@repo}").token
|
20
20
|
template 'party_foul.rb', 'config/initializers/party_foul.rb'
|
21
21
|
rescue Github::Error::Unauthorized
|
22
|
-
say 'There was an error retrieving your
|
22
|
+
say 'There was an error retrieving your GitHub OAuth token'
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
@@ -3,15 +3,15 @@ PartyFoul.configure do |config|
|
|
3
3
|
# The constants here *must* be represented as strings
|
4
4
|
config.blacklisted_exceptions = ['ActiveRecord::RecordNotFound', 'ActionController::RoutingError']
|
5
5
|
|
6
|
-
# The OAuth token for the account that is opening the issues on
|
6
|
+
# The OAuth token for the account that is opening the issues on GitHub
|
7
7
|
config.oauth_token = '<%= @oauth_token %>'
|
8
8
|
|
9
|
-
# The API endpoint for
|
10
|
-
# instance of Enterprise
|
9
|
+
# The API endpoint for GitHub. Unless you are hosting a private
|
10
|
+
# instance of Enterprise GitHub you do not need to include this
|
11
11
|
config.endpoint = '<%= @endpoint %>'
|
12
12
|
|
13
|
-
# The Web URL for
|
14
|
-
# instance of Enterprise
|
13
|
+
# The Web URL for GitHub. Unless you are hosting a private
|
14
|
+
# instance of Enterprise GitHub you do not need to include this
|
15
15
|
config.web_url = '<%= @web_url %>'
|
16
16
|
|
17
17
|
# The organization or user that owns the target repository
|
@@ -29,4 +29,7 @@ PartyFoul.configure do |config|
|
|
29
29
|
# config.additional_labels = Proc.new do |exception, env|
|
30
30
|
# []
|
31
31
|
# end
|
32
|
+
|
33
|
+
# Limit the number of comments per issue
|
34
|
+
# config.comment_limit = 10
|
32
35
|
end
|
data/lib/party_foul.rb
CHANGED
@@ -2,7 +2,7 @@ require 'github_api'
|
|
2
2
|
|
3
3
|
module PartyFoul
|
4
4
|
class << self
|
5
|
-
attr_accessor :github, :oauth_token, :endpoint, :owner, :repo, :blacklisted_exceptions, :processor, :web_url, :branch, :whitelisted_rack_variables, :additional_labels
|
5
|
+
attr_accessor :github, :oauth_token, :endpoint, :owner, :repo, :blacklisted_exceptions, :processor, :web_url, :branch, :whitelisted_rack_variables, :additional_labels, :comment_limit
|
6
6
|
end
|
7
7
|
|
8
8
|
def self.whitelisted_rack_variables
|
@@ -16,7 +16,7 @@ module PartyFoul
|
|
16
16
|
@branch ||= 'master'
|
17
17
|
end
|
18
18
|
|
19
|
-
# The web url for
|
19
|
+
# The web url for GitHub. This is only interesting for Enterprise
|
20
20
|
# users
|
21
21
|
#
|
22
22
|
# @return [String] Defaults to 'https://github.com' if not set
|
@@ -24,7 +24,7 @@ module PartyFoul
|
|
24
24
|
@web_url ||= 'https://github.com'
|
25
25
|
end
|
26
26
|
|
27
|
-
# The api endpoint for
|
27
|
+
# The api endpoint for GitHub. This is only interesting for Enterprise
|
28
28
|
# users
|
29
29
|
#
|
30
30
|
# @return [String] Defaults to 'https://api.github.com' if not set
|
@@ -71,7 +71,7 @@ module PartyFoul
|
|
71
71
|
# config.oauth_token = ENV['oauth_token']
|
72
72
|
# end
|
73
73
|
#
|
74
|
-
# Will also setup for
|
74
|
+
# Will also setup for GitHub api connections
|
75
75
|
#
|
76
76
|
# @param [Block]
|
77
77
|
def self.configure(&block)
|
@@ -24,7 +24,7 @@ class PartyFoul::ExceptionHandler
|
|
24
24
|
self.rendered_issue = renderer_klass.new(exception, env)
|
25
25
|
end
|
26
26
|
|
27
|
-
# Begins to process the exception for
|
27
|
+
# Begins to process the exception for GitHub Issues. Makes an attempt
|
28
28
|
# to find the issue. If found will update the issue. If not found will create a new issue.
|
29
29
|
def run
|
30
30
|
if issue = find_issue
|
@@ -34,7 +34,7 @@ class PartyFoul::ExceptionHandler
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
# Hits the
|
37
|
+
# Hits the GitHub API to find the matching issue using the fingerprint.
|
38
38
|
def find_issue
|
39
39
|
unless issue = PartyFoul.github.search.issues(owner: PartyFoul.owner, repo: PartyFoul.repo, state: 'open', keyword: fingerprint).issues.first
|
40
40
|
issue = PartyFoul.github.search.issues(owner: PartyFoul.owner, repo: PartyFoul.repo, state: 'closed', keyword: fingerprint).issues.first
|
@@ -63,7 +63,9 @@ class PartyFoul::ExceptionHandler
|
|
63
63
|
|
64
64
|
self.sha = PartyFoul.github.git_data.references.get(PartyFoul.owner, PartyFoul.repo, "heads/#{PartyFoul.branch}").object.sha
|
65
65
|
PartyFoul.github.issues.edit(PartyFoul.owner, PartyFoul.repo, issue['number'], params)
|
66
|
-
|
66
|
+
unless comment_limit_met?(issue['body'])
|
67
|
+
PartyFoul.github.issues.comments.create(PartyFoul.owner, PartyFoul.repo, issue['number'], body: rendered_issue.comment)
|
68
|
+
end
|
67
69
|
end
|
68
70
|
end
|
69
71
|
|
@@ -80,4 +82,13 @@ class PartyFoul::ExceptionHandler
|
|
80
82
|
def sha=(sha)
|
81
83
|
rendered_issue.sha = sha
|
82
84
|
end
|
85
|
+
|
86
|
+
def occurrence_count(body)
|
87
|
+
result = body.match(/<th>Count<\/th><td>(\d+)<\/td>/)
|
88
|
+
result.nil? ? 0 : result[1].to_i
|
89
|
+
end
|
90
|
+
|
91
|
+
def comment_limit_met?(body)
|
92
|
+
!!PartyFoul.comment_limit && PartyFoul.comment_limit <= occurrence_count(body)
|
93
|
+
end
|
83
94
|
end
|
@@ -1,7 +1,10 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
1
3
|
class PartyFoul::IssueRenderers::Base
|
2
4
|
attr_accessor :exception, :env, :sha
|
5
|
+
attr_reader :body
|
3
6
|
|
4
|
-
# A new renderer instance for
|
7
|
+
# A new renderer instance for GitHub issues
|
5
8
|
#
|
6
9
|
# @param [Exception, Hash]
|
7
10
|
def initialize(exception, env)
|
@@ -9,11 +12,11 @@ class PartyFoul::IssueRenderers::Base
|
|
9
12
|
self.env = env
|
10
13
|
end
|
11
14
|
|
12
|
-
#
|
15
|
+
# Title of the issue with any object ids masked
|
13
16
|
#
|
14
|
-
# @return [
|
17
|
+
# @return [String]
|
15
18
|
def title
|
16
|
-
|
19
|
+
raw_title.gsub(/#<(\w+):0x\w+?>/, "#<\\1:0xXXXXXX>")
|
17
20
|
end
|
18
21
|
|
19
22
|
# Renders the issue body
|
@@ -22,7 +25,7 @@ class PartyFoul::IssueRenderers::Base
|
|
22
25
|
#
|
23
26
|
# @return [String]
|
24
27
|
def body
|
25
|
-
<<-BODY
|
28
|
+
@body ||= <<-BODY
|
26
29
|
#{build_table_from_hash(body_options)}
|
27
30
|
|
28
31
|
## Stack Trace
|
@@ -41,7 +44,7 @@ BODY
|
|
41
44
|
|
42
45
|
# Compiles the stack trace for use in the issue body. Lines in the
|
43
46
|
# stack trace that are part of the application will be rendered as
|
44
|
-
# links to the relative file and line on
|
47
|
+
# links to the relative file and line on GitHub based upon
|
45
48
|
# {PartyFoul.web_url}, {PartyFoul.owner}, {PartyFoul.repo}, and
|
46
49
|
# {PartyFoul.branch}. The branch will be used at the time the
|
47
50
|
# exception happens to grab the SHA for that branch at that time for
|
@@ -76,7 +79,7 @@ BODY
|
|
76
79
|
begin
|
77
80
|
current_count = old_body.match(/<th>Count<\/th><td>(\d+)<\/td>/)[1].to_i
|
78
81
|
old_body.sub!("<th>Count</th><td>#{current_count}</td>", "<th>Count</th><td>#{current_count + 1}</td>")
|
79
|
-
old_body.sub!(/<th>Last Occurrence<\/th><td
|
82
|
+
old_body.sub!(/<th>Last Occurrence<\/th><td>.+?<\/td>/, "<th>Last Occurrence</th><td>#{occurred_at}</td>")
|
80
83
|
old_body
|
81
84
|
rescue
|
82
85
|
self.body
|
@@ -87,7 +90,7 @@ BODY
|
|
87
90
|
#
|
88
91
|
# @return [String]
|
89
92
|
def occurred_at
|
90
|
-
Time.now.strftime('%B %d, %Y %H:%M:%S %z')
|
93
|
+
@occurred_at ||= Time.now.strftime('%B %d, %Y %H:%M:%S %z')
|
91
94
|
end
|
92
95
|
|
93
96
|
# The hash used for building the table in issue body
|
@@ -122,6 +125,8 @@ BODY
|
|
122
125
|
key, value = row
|
123
126
|
if row[1].kind_of?(Hash)
|
124
127
|
value = build_table_from_hash(row[1])
|
128
|
+
else
|
129
|
+
value = CGI.escapeHTML(value.to_s)
|
125
130
|
end
|
126
131
|
rows << "<tr><th>#{key}</th><td>#{value}</td></tr>"
|
127
132
|
end
|
@@ -147,4 +152,8 @@ BODY
|
|
147
152
|
def extract_file_name_and_line_number(backtrace_line)
|
148
153
|
backtrace_line.match(/#{app_root}\/((.+?):(\d+))/)
|
149
154
|
end
|
155
|
+
|
156
|
+
def raw_title
|
157
|
+
raise NotImplementedError
|
158
|
+
end
|
150
159
|
end
|
@@ -1,11 +1,4 @@
|
|
1
1
|
class PartyFoul::IssueRenderers::Rack < PartyFoul::IssueRenderers::Base
|
2
|
-
# Title for the issue comprised of (exception) "message"
|
3
|
-
#
|
4
|
-
# @return [String]
|
5
|
-
def title
|
6
|
-
%{(#{exception.class}) "#{exception.message}"}
|
7
|
-
end
|
8
|
-
|
9
2
|
def comment_options
|
10
3
|
super.merge(URL: url, Params: params, Session: session, 'IP Address' => ip_address, 'HTTP Headers' => http_headers)
|
11
4
|
end
|
@@ -42,4 +35,10 @@ class PartyFoul::IssueRenderers::Rack < PartyFoul::IssueRenderers::Base
|
|
42
35
|
def http_headers
|
43
36
|
{ Version: env['HTTP_VERSION'], 'User Agent' => env['HTTP_USER_AGENT'], 'Accept Encoding' => env['HTTP_ACCEPT_ENCODING'], Accept: env['HTTP_ACCEPT'] }
|
44
37
|
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def raw_title
|
42
|
+
%{(#{exception.class}) "#{exception.message}"}
|
43
|
+
end
|
45
44
|
end
|
@@ -13,14 +13,13 @@ class PartyFoul::IssueRenderers::Rackless < PartyFoul::IssueRenderers::Base
|
|
13
13
|
env[:params]
|
14
14
|
end
|
15
15
|
|
16
|
-
# Title for the issue comprised of Controller#action (exception) "message"
|
17
|
-
#
|
18
|
-
# @return [String]
|
19
|
-
def title
|
20
|
-
%{#{env[:class]}##{env[:method]} (#{exception.class}) "#{exception.message}"}
|
21
|
-
end
|
22
|
-
|
23
16
|
def comment_options
|
24
17
|
super.merge(Params: params)
|
25
18
|
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def raw_title
|
23
|
+
%{#{env[:class]}##{env[:method]} (#{exception.class}) "#{exception.message}"}
|
24
|
+
end
|
26
25
|
end
|
@@ -1,11 +1,4 @@
|
|
1
1
|
class PartyFoul::IssueRenderers::Rails < PartyFoul::IssueRenderers::Rack
|
2
|
-
# Title for the issue comprised of Controller#action (exception) "message"
|
3
|
-
#
|
4
|
-
# @return [String]
|
5
|
-
def title
|
6
|
-
%{#{env['action_controller.instance'].class}##{env['action_dispatch.request.path_parameters']['action']} (#{exception.class}) "#{exception.message}"}
|
7
|
-
end
|
8
|
-
|
9
2
|
# Rails params hash. Filtered parms are respected.
|
10
3
|
#
|
11
4
|
# @return [Hash]
|
@@ -27,4 +20,8 @@ class PartyFoul::IssueRenderers::Rails < PartyFoul::IssueRenderers::Rack
|
|
27
20
|
def app_root
|
28
21
|
Rails.root
|
29
22
|
end
|
23
|
+
|
24
|
+
def raw_title
|
25
|
+
%{#{env['action_controller.instance'].class}##{env['action_dispatch.request.path_parameters']['action']} (#{exception.class}) "#{exception.message}"}
|
26
|
+
end
|
30
27
|
end
|
data/lib/party_foul/version.rb
CHANGED
@@ -15,6 +15,7 @@ describe 'Party Foul Confg' do
|
|
15
15
|
config.owner = 'test_owner'
|
16
16
|
config.repo = 'test_repo'
|
17
17
|
config.branch = 'master'
|
18
|
+
config.comment_limit = 10
|
18
19
|
end
|
19
20
|
|
20
21
|
PartyFoul.blacklisted_exceptions.must_equal ['StandardError']
|
@@ -25,6 +26,7 @@ describe 'Party Foul Confg' do
|
|
25
26
|
PartyFoul.repo.must_equal 'test_repo'
|
26
27
|
PartyFoul.repo_url.must_equal 'http://example.com/test_owner/test_repo'
|
27
28
|
PartyFoul.branch.must_equal 'master'
|
29
|
+
PartyFoul.comment_limit.must_equal 10
|
28
30
|
end
|
29
31
|
|
30
32
|
it 'has default values' do
|
@@ -21,7 +21,7 @@ describe 'Party Foul Exception Handler' do
|
|
21
21
|
end
|
22
22
|
|
23
23
|
context 'when error is new' do
|
24
|
-
it 'will open a new error on
|
24
|
+
it 'will open a new error on GitHub' do
|
25
25
|
PartyFoul::IssueRenderers::Rails.any_instance.stubs(:body).returns('Test Body')
|
26
26
|
PartyFoul::IssueRenderers::Rails.any_instance.stubs(:comment).returns('Test Comment')
|
27
27
|
PartyFoul.github.search.stubs(:issues).with(owner: 'test_owner', repo: 'test_repo', keyword: 'test_fingerprint', state: 'open').returns(Hashie::Mash.new(issues: []))
|
@@ -41,7 +41,7 @@ describe 'Party Foul Exception Handler' do
|
|
41
41
|
after do
|
42
42
|
clean_up_party
|
43
43
|
end
|
44
|
-
it 'will open a new error on
|
44
|
+
it 'will open a new error on GitHub with the additional labels' do
|
45
45
|
PartyFoul::IssueRenderers::Rails.any_instance.stubs(:body).returns('Test Body')
|
46
46
|
PartyFoul::IssueRenderers::Rails.any_instance.stubs(:comment).returns('Test Comment')
|
47
47
|
PartyFoul.github.search.stubs(:issues).with(owner: 'test_owner', repo: 'test_repo', keyword: 'test_fingerprint', state: 'open').returns(Hashie::Mash.new(issues: []))
|
@@ -75,17 +75,17 @@ describe 'Party Foul Exception Handler' do
|
|
75
75
|
clean_up_party
|
76
76
|
end
|
77
77
|
|
78
|
-
it 'will open a new error on
|
78
|
+
it 'will open a new error on GitHub with the default labels if no additional labels are returned from the proc' do
|
79
79
|
PartyFoul.github.issues.expects(:create).with('test_owner', 'test_repo', title: 'Test Title', body: 'Test Body', :labels => ['bug']).returns(Hashie::Mash.new('number' => 1))
|
80
80
|
PartyFoul::ExceptionHandler.new(stub(:message => ''), {}).run
|
81
81
|
end
|
82
82
|
|
83
|
-
it 'will open a new error on
|
83
|
+
it 'will open a new error on GitHub with the additional labels based on the exception message' do
|
84
84
|
PartyFoul.github.issues.expects(:create).with('test_owner', 'test_repo', title: 'Test Title', body: 'Test Body', :labels => ['bug', 'database_error']).returns(Hashie::Mash.new('number' => 1))
|
85
85
|
PartyFoul::ExceptionHandler.new(stub(:message => 'Database'), {}).run
|
86
86
|
end
|
87
87
|
|
88
|
-
it 'will open a new error on
|
88
|
+
it 'will open a new error on GitHub with the additional labels based on the env' do
|
89
89
|
PartyFoul.github.issues.expects(:create).with('test_owner', 'test_repo', title: 'Test Title', body: 'Test Body', :labels => ['bug', 'beta']).returns(Hashie::Mash.new('number' => 1))
|
90
90
|
PartyFoul::ExceptionHandler.new(stub(:message => ''), {:http_host => 'beta.example.com'}).run
|
91
91
|
end
|
@@ -99,11 +99,24 @@ describe 'Party Foul Exception Handler' do
|
|
99
99
|
end
|
100
100
|
|
101
101
|
context 'and open' do
|
102
|
-
|
102
|
+
before do
|
103
103
|
PartyFoul.github.search.stubs(:issues).with(owner: 'test_owner', repo: 'test_repo', keyword: 'test_fingerprint', state: 'open').returns(Hashie::Mash.new(issues: [{title: 'Test Title', body: 'Test Body', state: 'open', number: 1}]))
|
104
104
|
PartyFoul.github.issues.expects(:edit).with('test_owner', 'test_repo', 1, body: 'New Body', state: 'open')
|
105
105
|
PartyFoul.github.issues.comments.expects(:create).with('test_owner', 'test_repo', 1, body: 'Test Comment')
|
106
106
|
PartyFoul.github.git_data.references.expects(:get).with('test_owner', 'test_repo', 'heads/deploy').returns(Hashie::Mash.new(object: Hashie::Mash.new(sha: 'abcdefg1234567890')))
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'will update the issue' do
|
110
|
+
PartyFoul::ExceptionHandler.new(nil, {}).run
|
111
|
+
end
|
112
|
+
|
113
|
+
it "doesn't post a comment if the limit has been met" do
|
114
|
+
PartyFoul.configure do |config|
|
115
|
+
config.comment_limit = 10
|
116
|
+
end
|
117
|
+
PartyFoul::ExceptionHandler.any_instance.expects(:occurrence_count).returns(10)
|
118
|
+
PartyFoul.github.issues.comments.unstub(:create) # Necessary for the `never` expectation to work.
|
119
|
+
PartyFoul.github.issues.comments.expects(:create).never
|
107
120
|
PartyFoul::ExceptionHandler.new(nil, {}).run
|
108
121
|
end
|
109
122
|
end
|
@@ -132,4 +145,51 @@ describe 'Party Foul Exception Handler' do
|
|
132
145
|
PartyFoul::ExceptionHandler.new(nil, {}).run
|
133
146
|
end
|
134
147
|
end
|
148
|
+
|
149
|
+
describe '#occurrence_count' do
|
150
|
+
before do
|
151
|
+
@handler = PartyFoul::ExceptionHandler.new(nil, {})
|
152
|
+
end
|
153
|
+
|
154
|
+
it "returns the count" do
|
155
|
+
@handler.send(:occurrence_count, "<th>Count</th><td>1</td>").must_equal 1
|
156
|
+
end
|
157
|
+
|
158
|
+
it "returns 0 if no count is found" do
|
159
|
+
@handler.send(:occurrence_count, "Unexpected Body").must_equal 0
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
describe '#comment_limit_met?' do
|
164
|
+
before do
|
165
|
+
@handler = PartyFoul::ExceptionHandler.new(nil, {})
|
166
|
+
end
|
167
|
+
|
168
|
+
context "with no limit" do
|
169
|
+
it "returns false when there is no limit" do
|
170
|
+
PartyFoul.configure do |config|
|
171
|
+
config.comment_limit = nil
|
172
|
+
end
|
173
|
+
@handler.send(:comment_limit_met?, "").must_equal false
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context "with a limit" do
|
178
|
+
before do
|
179
|
+
PartyFoul.configure do |config|
|
180
|
+
config.comment_limit = 10
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
it "returns false when there is a limit that has not been hit" do
|
185
|
+
@handler.stubs(:occurrence_count).returns(1)
|
186
|
+
@handler.send(:comment_limit_met?, "").must_equal false
|
187
|
+
end
|
188
|
+
|
189
|
+
it "returns true if the limit has been hit" do
|
190
|
+
@handler.stubs(:occurrence_count).returns(10)
|
191
|
+
@handler.send(:comment_limit_met?, "").must_equal true
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
135
195
|
end
|
@@ -21,11 +21,11 @@ describe 'Party Foul Issue Renderer Base' do
|
|
21
21
|
end
|
22
22
|
|
23
23
|
it 'updates count and timestamp' do
|
24
|
-
body = <<-BODY
|
24
|
+
body = <<-BODY.gsub(/\n/, '')
|
25
25
|
<table>
|
26
26
|
<tr><th>Exception</th><td>Test Exception</td></tr>
|
27
|
-
<tr><th>Count</th><td>1</td></tr>
|
28
27
|
<tr><th>Last Occurrence</th><td>January 01, 1970 00:00:00 -0500</td></tr>
|
28
|
+
<tr><th>Count</th><td>1</td></tr>
|
29
29
|
</table>
|
30
30
|
|
31
31
|
## Stack Trace
|
@@ -35,11 +35,11 @@ Fingerprint: `abcdefg1234567890`
|
|
35
35
|
|
36
36
|
Time.stubs(:now).returns(Time.new(1985, 10, 25, 1, 22, 0, '-05:00'))
|
37
37
|
|
38
|
-
expected_body = <<-BODY
|
38
|
+
expected_body = <<-BODY.gsub(/\n/, '')
|
39
39
|
<table>
|
40
40
|
<tr><th>Exception</th><td>Test Exception</td></tr>
|
41
|
-
<tr><th>Count</th><td>2</td></tr>
|
42
41
|
<tr><th>Last Occurrence</th><td>October 25, 1985 01:22:00 -0500</td></tr>
|
42
|
+
<tr><th>Count</th><td>2</td></tr>
|
43
43
|
</table>
|
44
44
|
|
45
45
|
## Stack Trace
|
@@ -79,6 +79,13 @@ Fingerprint: `abcdefg1234567890`
|
|
79
79
|
expected = '<table><tr><th>Value 1</th><td>abc</td></tr><tr><th>Value 2</th><td><table><tr><th>Value A</th><td>123</td></tr><tr><th>Value B</th><td>456</td></tr></table></td></tr></table>'
|
80
80
|
rendered_issue.build_table_from_hash(hash).must_equal expected
|
81
81
|
end
|
82
|
+
|
83
|
+
it 'escapes HTML entities' do
|
84
|
+
rendered_issue = PartyFoul::IssueRenderers::Base.new(nil, nil)
|
85
|
+
hash = { 'Value 1' => 'Error in #<Foo>' }
|
86
|
+
expected = '<table><tr><th>Value 1</th><td>Error in #<Foo></td></tr></table>'
|
87
|
+
rendered_issue.build_table_from_hash(hash).must_equal expected
|
88
|
+
end
|
82
89
|
end
|
83
90
|
|
84
91
|
describe '#fingerprint' do
|
@@ -88,4 +95,21 @@ Fingerprint: `abcdefg1234567890`
|
|
88
95
|
rendered_issue.fingerprint.must_equal Digest::SHA1.hexdigest(rendered_issue.title)
|
89
96
|
end
|
90
97
|
end
|
98
|
+
|
99
|
+
describe '#occurred_at' do
|
100
|
+
it 'memoizes the time' do
|
101
|
+
rendered_issue = PartyFoul::IssueRenderers::Base.new(nil, nil)
|
102
|
+
expected = rendered_issue.occurred_at
|
103
|
+
Time.stubs(:now).returns(Time.new(1970, 1, 1, 0, 0, 1, '-05:00'))
|
104
|
+
rendered_issue.occurred_at.must_equal expected
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe '#title' do
|
109
|
+
it 'masks the object ids in the raw_title' do
|
110
|
+
rendered_issue = PartyFoul::IssueRenderers::Base.new(nil, nil)
|
111
|
+
rendered_issue.stubs(:raw_title).returns('Error for #<ClassName:0xabcdefg1234567>')
|
112
|
+
rendered_issue.title.must_equal 'Error for #<ClassName:0xXXXXXX>'
|
113
|
+
end
|
114
|
+
end
|
91
115
|
end
|
@@ -11,14 +11,14 @@ describe 'Rack Issue Renderer' do
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
describe '#
|
14
|
+
describe '#raw_title' do
|
15
15
|
before do
|
16
16
|
@exception = Exception.new('message')
|
17
17
|
end
|
18
18
|
|
19
19
|
it 'constructs the title with the class and instance method' do
|
20
20
|
@rendered_issue = PartyFoul::IssueRenderers::Rack.new(@exception, {})
|
21
|
-
@rendered_issue.
|
21
|
+
@rendered_issue.send(:raw_title).must_equal %{(Exception) "message"}
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
@@ -16,14 +16,14 @@ describe 'Rackless Issue Renderer' do
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
describe '#
|
19
|
+
describe '#raw_title' do
|
20
20
|
before do
|
21
21
|
@exception = Exception.new('message')
|
22
22
|
@rendered_issue = PartyFoul::IssueRenderers::Rackless.new(@exception, @env)
|
23
23
|
end
|
24
24
|
|
25
25
|
it 'constructs the title with the controller and action' do
|
26
|
-
@rendered_issue.
|
26
|
+
@rendered_issue.send(:raw_title).must_equal %{Worker#perform (Exception) "message"}
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
@@ -23,7 +23,7 @@ describe 'Rails Issue Renderer' do
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
describe '#
|
26
|
+
describe '#raw_title' do
|
27
27
|
before do
|
28
28
|
@exception = Exception.new('message')
|
29
29
|
controller_instance = mock('Controller')
|
@@ -36,7 +36,7 @@ describe 'Rails Issue Renderer' do
|
|
36
36
|
end
|
37
37
|
|
38
38
|
it 'constructs the title with the controller and action' do
|
39
|
-
@rendered_issue.
|
39
|
+
@rendered_issue.send(:raw_title).must_equal %{LandingController#index (Exception) "message"}
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
data/test/test_helper.rb
CHANGED
@@ -24,7 +24,7 @@ module MiniTest::Expectations
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def clean_up_party
|
27
|
-
%w{github oauth_token endpoint owner repo blacklisted_exceptions processor web_url branch additional_labels}.each do |attr|
|
27
|
+
%w{github oauth_token endpoint owner repo blacklisted_exceptions processor web_url branch additional_labels comment_limit}.each do |attr|
|
28
28
|
PartyFoul.send("#{attr}=", nil)
|
29
29
|
end
|
30
30
|
end
|
@@ -3,15 +3,15 @@ PartyFoul.configure do |config|
|
|
3
3
|
# The constants here *must* be represented as strings
|
4
4
|
config.blacklisted_exceptions = ['ActiveRecord::RecordNotFound', 'ActionController::RoutingError']
|
5
5
|
|
6
|
-
# The OAuth token for the account that is opening the issues on
|
6
|
+
# The OAuth token for the account that is opening the issues on GitHub
|
7
7
|
config.oauth_token = 'test_token'
|
8
8
|
|
9
|
-
# The API endpoint for
|
10
|
-
# instance of Enterprise
|
9
|
+
# The API endpoint for GitHub. Unless you are hosting a private
|
10
|
+
# instance of Enterprise GitHub you do not need to include this
|
11
11
|
config.endpoint = 'http://api.example.com'
|
12
12
|
|
13
|
-
# The Web URL for
|
14
|
-
# instance of Enterprise
|
13
|
+
# The Web URL for GitHub. Unless you are hosting a private
|
14
|
+
# instance of Enterprise GitHub you do not need to include this
|
15
15
|
config.web_url = 'http://example.com'
|
16
16
|
|
17
17
|
# The organization or user that owns the target repository
|
@@ -29,4 +29,7 @@ PartyFoul.configure do |config|
|
|
29
29
|
# config.additional_labels = Proc.new do |exception, env|
|
30
30
|
# []
|
31
31
|
# end
|
32
|
+
|
33
|
+
# Limit the number of comments per issue
|
34
|
+
# config.comment_limit = 10
|
32
35
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: party_foul
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-02-
|
13
|
+
date: 2013-02-16 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: github_api
|
@@ -172,7 +172,7 @@ dependencies:
|
|
172
172
|
- - ! '>='
|
173
173
|
- !ruby/object:Gem::Version
|
174
174
|
version: '0'
|
175
|
-
description: Auto-submit Rails exceptions as new isues on
|
175
|
+
description: Auto-submit Rails exceptions as new isues on GitHub
|
176
176
|
email:
|
177
177
|
- bcardarella@gmail.com
|
178
178
|
- rubygems@danmcclain.net
|
@@ -225,7 +225,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
225
225
|
version: '0'
|
226
226
|
segments:
|
227
227
|
- 0
|
228
|
-
hash:
|
228
|
+
hash: -627459678726509707
|
229
229
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
230
230
|
none: false
|
231
231
|
requirements:
|
@@ -234,13 +234,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
234
234
|
version: '0'
|
235
235
|
segments:
|
236
236
|
- 0
|
237
|
-
hash:
|
237
|
+
hash: -627459678726509707
|
238
238
|
requirements: []
|
239
239
|
rubyforge_project:
|
240
240
|
rubygems_version: 1.8.23
|
241
241
|
signing_key:
|
242
242
|
specification_version: 3
|
243
|
-
summary: Auto-submit Rails exceptions as new isues on
|
243
|
+
summary: Auto-submit Rails exceptions as new isues on GitHub
|
244
244
|
test_files:
|
245
245
|
- test/generator_test.rb
|
246
246
|
- test/party_foul/configure_test.rb
|