party_foul 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
[](https://gemnasium.com/dockyard/party_foul)
|
5
5
|
[](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
|