party_foul 0.4.0 → 0.5.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 CHANGED
@@ -87,6 +87,9 @@ PartyFoul.configure do |config|
87
87
 
88
88
  # The repository for this application
89
89
  config.repo = 'repo_name'
90
+
91
+ # The branch for your deployed code
92
+ # config.branch = 'master'
90
93
  end
91
94
  ```
92
95
 
@@ -147,9 +150,8 @@ the change in one of the [different issue renderer adapters](https://github.com/
147
150
 
148
151
  ## Authors ##
149
152
 
150
- [Brian Cardarella](http://twitter.com/bcardarella)
151
-
152
- [Dan McClain](http://twitter.com/_danmcclain)
153
+ * [Brian Cardarella](http://twitter.com/bcardarella)
154
+ * [Dan McClain](http://twitter.com/_danmcclain)
153
155
 
154
156
  [We are very thankful for the many contributors](https://github.com/dockyard/party_foul/graphs/contributors)
155
157
 
@@ -22,4 +22,7 @@ PartyFoul.configure do |config|
22
22
 
23
23
  # The repository for this application
24
24
  config.repo = '<%= @repo %>'
25
+
26
+ # The branch for your deployed code
27
+ # config.branch = 'master'
25
28
  end
@@ -1,10 +1,19 @@
1
1
  class PartyFoul::ExceptionHandler
2
2
  attr_accessor :rendered_issue
3
3
 
4
+ # This handler will pass the exception and env from Rack off to a processor.
5
+ # The default PartyFoul processor will work synchronously. Processor adapters can be written
6
+ # to push this logic to a background job if desired.
7
+ #
8
+ # @param [Exception, Hash]
4
9
  def self.handle(exception, env)
5
10
  PartyFoul.processor.handle(exception, env)
6
11
  end
7
12
 
13
+ # Makes an attempt to determine what framework is being used and will use the proper
14
+ # IssueRenderer.
15
+ #
16
+ # @param [Exception, Hash]
8
17
  def initialize(exception, env)
9
18
  renderer_klass = if defined?(Rails)
10
19
  PartyFoul::RailsIssueRenderer
@@ -15,6 +24,8 @@ class PartyFoul::ExceptionHandler
15
24
  self.rendered_issue = renderer_klass.new(exception, env)
16
25
  end
17
26
 
27
+ # Begins to process the exception for Github Issues. Makes an attempt
28
+ # to find the issue. If found will update the issue. If not found will create a new issue.
18
29
  def run
19
30
  if issue = find_issue
20
31
  update_issue(issue)
@@ -23,6 +34,7 @@ class PartyFoul::ExceptionHandler
23
34
  end
24
35
  end
25
36
 
37
+ # Hits the Github API to find the matching issue using the fingerprint.
26
38
  def find_issue
27
39
  unless issue = PartyFoul.github.search.issues(owner: PartyFoul.owner, repo: PartyFoul.repo, state: 'open', keyword: fingerprint).issues.first
28
40
  issue = PartyFoul.github.search.issues(owner: PartyFoul.owner, repo: PartyFoul.repo, state: 'closed', keyword: fingerprint).issues.first
@@ -31,11 +43,16 @@ class PartyFoul::ExceptionHandler
31
43
  issue
32
44
  end
33
45
 
46
+ # Will create a new issue and a comment with the proper details. All issues are labeled as 'bug'.
34
47
  def create_issue
48
+ self.sha = PartyFoul.github.git_data.references.get(PartyFoul.owner, PartyFoul.repo, "heads/#{PartyFoul.branch}").object.sha
35
49
  issue = PartyFoul.github.issues.create(PartyFoul.owner, PartyFoul.repo, title: rendered_issue.title, body: rendered_issue.body, labels: ['bug'])
36
50
  PartyFoul.github.issues.comments.create(PartyFoul.owner, PartyFoul.repo, issue['number'], body: rendered_issue.comment)
37
51
  end
38
52
 
53
+ # Updates the given issue. If the issue is labeled as 'wontfix' nothing is done. If the issue is closed the issue is reopened and labeled as 'regression'.
54
+ #
55
+ # @param [Hashie::Mash]
39
56
  def update_issue(issue)
40
57
  unless issue.key?('labels') && issue['labels'].include?('wontfix')
41
58
  params = {body: rendered_issue.update_body(issue['body']), state: 'open'}
@@ -44,6 +61,7 @@ class PartyFoul::ExceptionHandler
44
61
  params[:labels] = ['bug', 'regression']
45
62
  end
46
63
 
64
+ self.sha = PartyFoul.github.git_data.references.get(PartyFoul.owner, PartyFoul.repo, "heads/#{PartyFoul.branch}").object.sha
47
65
  PartyFoul.github.issues.edit(PartyFoul.owner, PartyFoul.repo, issue['number'], params)
48
66
  PartyFoul.github.issues.comments.create(PartyFoul.owner, PartyFoul.repo, issue['number'], body: rendered_issue.comment)
49
67
  end
@@ -54,4 +72,8 @@ class PartyFoul::ExceptionHandler
54
72
  def fingerprint
55
73
  rendered_issue.fingerprint
56
74
  end
75
+
76
+ def sha=(sha)
77
+ rendered_issue.sha = sha
78
+ end
57
79
  end
@@ -1,37 +1,68 @@
1
1
  class PartyFoul::IssueRenderer
2
- attr_accessor :exception, :env
2
+ attr_accessor :exception, :env, :sha
3
3
 
4
+ # A new renderer instance for Githug issues
5
+ #
6
+ # @param [Exception, Hash]
4
7
  def initialize(exception, env)
5
8
  self.exception = exception
6
9
  self.env = env
7
10
  end
8
11
 
12
+ # Derived title of the issue. Must be implemented by the adapter class
13
+ #
14
+ # @return [NotImplementedError]
9
15
  def title
10
16
  raise NotImplementedError
11
17
  end
12
18
 
19
+ # Will compile the template for an issue body as defined in
20
+ # {PartyFoul.issue_template}
21
+ #
22
+ # @return [String]
13
23
  def body
14
24
  compile_template(PartyFoul.issue_template)
15
25
  end
16
26
 
27
+ # Will compile the template for a comment body as defined in
28
+ # {PartyFoul.comment_template}
17
29
  def comment
18
30
  compile_template(PartyFoul.comment_template)
19
31
  end
20
32
 
33
+ # Compiles the stack trace for use in the issue body. Lines in the
34
+ # stack trace that are part of the application will be rendered as
35
+ # links to the relative file and line on Github based upon
36
+ # {PartyFoul.web_url}, {PartyFoul.owner}, {PartyFoul.repo}, and
37
+ # {PartyFoul.branch}. The branch will be used at the time the
38
+ # exception happens to grab the SHA for that branch at that time for
39
+ # the purpose of linking.
40
+ #
41
+ # @return [String]
21
42
  def stack_trace
22
43
  exception.backtrace.map do |line|
23
44
  if matches = extract_file_name_and_line_number(line)
24
- "<a href='#{PartyFoul.repo_url}/tree/master/#{matches[2]}#L#{matches[3]}'>#{line}</a>"
45
+ "<a href='#{PartyFoul.repo_url}/blob/#{sha}/#{matches[2]}#L#{matches[3]}'>#{line}</a>"
25
46
  else
26
47
  line
27
48
  end
28
49
  end.join("\n")
29
50
  end
30
51
 
52
+ # A SHA1 hex digested representation of the title. The fingerprint is
53
+ # used to create a unique value in the issue body. This value is used
54
+ # for seraching when matching issues happen again in the future.
55
+ #
56
+ # @return [String]
31
57
  def fingerprint
32
58
  Digest::SHA1.hexdigest(title)
33
59
  end
34
60
 
61
+ # Will update the issue body. The count and the time stamp will both
62
+ # be updated. If the format of the issue body fails to match for
63
+ # whatever reason the issue body will be reset.
64
+ #
65
+ # @return [String]
35
66
  def update_body(old_body)
36
67
  begin
37
68
  current_count = old_body.match(/<th>Count<\/th><td>(\d+)<\/td>/)[1].to_i
@@ -43,22 +74,39 @@ class PartyFoul::IssueRenderer
43
74
  end
44
75
  end
45
76
 
77
+ # The params hash at the time the exception occurred. This method is
78
+ # overriden for each framework adapter. It should return a hash.
79
+ #
80
+ # @return [NotImplementedError]
46
81
  def params
47
82
  raise NotImplementedError
48
83
  end
49
84
 
85
+ # The timestamp when the exception occurred.
86
+ #
87
+ # @return [String]
50
88
  def occurred_at
51
89
  Time.now.strftime('%B %d, %Y %H:%M:%S %z')
52
90
  end
53
91
 
92
+ # IP address of the client who triggered the exception
93
+ #
94
+ # @return [String]
54
95
  def ip_address
55
96
  env['REMOTE_ADDR']
56
97
  end
57
98
 
99
+ # The session hash for the client at the time of the exception
100
+ #
101
+ # @return [Hash]
58
102
  def session
59
103
  env['rack.session']
60
104
  end
61
105
 
106
+ # HTTP Headers hash from the request. Headers can be filtered out by
107
+ # adding matching key names to {PartyFoul.filtered_http_headers}
108
+ #
109
+ # @return [Hash]
62
110
  def http_headers
63
111
  env.keys.select { |key| key =~ /^HTTP_(\w+)/ && !(PartyFoul.filtered_http_headers || []).include?($1.split('_').map(&:capitalize).join('-')) }.sort.inject({}) do |hash, key|
64
112
  hash[key.split('HTTP_').last.split('_').map(&:capitalize).join('-')] = env[key]
@@ -2,10 +2,16 @@ require 'party_foul/issue_renderer'
2
2
 
3
3
  module PartyFoul
4
4
  class RackIssueRenderer < IssueRenderer
5
+ # Rack params
6
+ #
7
+ # @return [Hash]
5
8
  def params
6
9
  env['QUERY_STRING']
7
10
  end
8
11
 
12
+ # Title for the issue comprised of (exception) "message"
13
+ #
14
+ # @return [String]
9
15
  def title
10
16
  %{(#{exception.class}) "#{exception.message}"}
11
17
  end
@@ -2,11 +2,17 @@ require 'party_foul/issue_renderer'
2
2
 
3
3
  module PartyFoul
4
4
  class RailsIssueRenderer < IssueRenderer
5
+ # Rails params hash. Filtered parms are respected.
6
+ #
7
+ # @return [Hash]
5
8
  def params
6
9
  parameter_filter = ActionDispatch::Http::ParameterFilter.new(env["action_dispatch.parameter_filter"])
7
10
  parameter_filter.filter(env['action_dispatch.request.path_parameters'])
8
11
  end
9
12
 
13
+ # Title for the issue comprised of Controller#action (exception) "message"
14
+ #
15
+ # @return [String]
10
16
  def title
11
17
  %{#{env['action_controller.instance'].class}##{env['action_dispatch.request.path_parameters']['action']} (#{exception.class}) "#{exception.message}"}
12
18
  end
@@ -1,4 +1,8 @@
1
1
  class PartyFoul::Processors::Sync
2
+ # Passes the exception and rack env data to the ExceptionHandler and
3
+ # runs everything synchronously.
4
+ #
5
+ # @param [Exception, Hash]
2
6
  def self.handle(exception, env)
3
7
  PartyFoul::ExceptionHandler.new(exception, env).run
4
8
  end
@@ -1,3 +1,3 @@
1
1
  module PartyFoul
2
- VERSION = '0.4.0'
2
+ VERSION = '0.5.0'
3
3
  end
data/lib/party_foul.rb CHANGED
@@ -4,13 +4,27 @@ module PartyFoul
4
4
  class << self
5
5
  attr_accessor :github, :oauth_token, :endpoint, :owner, :repo,
6
6
  :ignored_exceptions, :processor, :issue_template,
7
- :comment_template, :filtered_http_headers, :web_url
7
+ :comment_template, :filtered_http_headers, :web_url, :branch
8
8
  end
9
9
 
10
+ # The git branch that is used for linking in the stack trace
11
+ #
12
+ # @return [String] Defaults to 'master' if not set
13
+ def self.branch
14
+ @branch ||= 'master'
15
+ end
16
+
17
+ # The web url for Github. This is only interesting for Enterprise
18
+ # users
19
+ #
20
+ # @return [String] Defaults to 'https://github.com' if not set
10
21
  def self.web_url
11
22
  @web_url ||= 'https://github.com'
12
23
  end
13
24
 
25
+ # The template used for rendering the body of a new issue
26
+ #
27
+ # @return [String]
14
28
  def self.issue_template
15
29
  @issue_template ||=
16
30
  <<-TEMPLATE
@@ -26,6 +40,9 @@ Fingerprint: `:fingerprint`
26
40
  TEMPLATE
27
41
  end
28
42
 
43
+ # The template used for rendering the body of a new comment
44
+ #
45
+ # @return [String]
29
46
  def self.comment_template
30
47
  @comment_template ||=
31
48
  <<-TEMPLATE
@@ -39,14 +56,38 @@ Fingerprint: `:fingerprint`
39
56
  TEMPLATE
40
57
  end
41
58
 
59
+ # The collection of exceptions that should not be captured. Members of
60
+ # the collection must be string representations of the exception. For
61
+ # example:
62
+ #
63
+ # # This is good
64
+ # ['ActiveRecord::RecordNotFound']
65
+ #
66
+ # # This is not
67
+ # [ActiveRecord::RecordNotFound]
68
+ #
69
+ # @return [Array]
42
70
  def self.ignored_exceptions
43
71
  @ignored_exceptions || []
44
72
  end
45
73
 
74
+ # The url of the repository. Built using the {.web_url}, {.owner}, and {.repo}
75
+ # values
76
+ #
77
+ # @return [String]
46
78
  def self.repo_url
47
79
  "#{web_url}/#{owner}/#{repo}"
48
80
  end
49
81
 
82
+ # The configure block for PartyFoul. Use to initialize settings
83
+ #
84
+ # PartyFoul.configure do |config|
85
+ # config.owner 'dockyard'
86
+ # config.repo 'test_app'
87
+ # config.oauth_token = ENV['oauth_token']
88
+ # end
89
+ #
90
+ # @param [Block]
50
91
  def self.configure(&block)
51
92
  yield self
52
93
  self.processor ||= PartyFoul::Processors::Sync
@@ -14,6 +14,7 @@ describe 'Party Foul Confg' do
14
14
  config.endpoint = 'http://api.example.com'
15
15
  config.owner = 'test_owner'
16
16
  config.repo = 'test_repo'
17
+ config.branch = 'master'
17
18
  end
18
19
 
19
20
  PartyFoul.ignored_exceptions.must_equal ['StandardError']
@@ -23,5 +24,11 @@ describe 'Party Foul Confg' do
23
24
  PartyFoul.owner.must_equal 'test_owner'
24
25
  PartyFoul.repo.must_equal 'test_repo'
25
26
  PartyFoul.repo_url.must_equal 'http://example.com/test_owner/test_repo'
27
+ PartyFoul.branch.must_equal 'master'
28
+ end
29
+
30
+ it 'has default values' do
31
+ PartyFoul.web_url.must_equal 'https://github.com'
32
+ PartyFoul.branch.must_equal 'master'
26
33
  end
27
34
  end
@@ -8,8 +8,10 @@ describe 'Party Foul Exception Handler' do
8
8
  config.repo = 'test_repo'
9
9
  end
10
10
 
11
+ PartyFoul.stubs(:branch).returns('deploy')
11
12
  PartyFoul.github.stubs(:issues).returns(mock('Issues'))
12
13
  PartyFoul.github.stubs(:search).returns(mock('Search'))
14
+ PartyFoul.github.git_data.references.stubs(:get)
13
15
  PartyFoul.github.issues.stubs(:create)
14
16
  PartyFoul.github.issues.stubs(:edit)
15
17
  PartyFoul.github.issues.stubs(:comments).returns(mock('Comments'))
@@ -25,6 +27,7 @@ describe 'Party Foul Exception Handler' do
25
27
  PartyFoul.github.search.stubs(:issues).with(owner: 'test_owner', repo: 'test_repo', keyword: 'test_fingerprint', state: 'open').returns(Hashie::Mash.new(issues: []))
26
28
  PartyFoul.github.search.stubs(:issues).with(owner: 'test_owner', repo: 'test_repo', keyword: 'test_fingerprint', state: 'closed').returns(Hashie::Mash.new(issues: []))
27
29
  PartyFoul.github.issues.expects(:create).with('test_owner', 'test_repo', title: 'Test Title', body: 'Test Body', :labels => ['bug']).returns(Hashie::Mash.new('number' => 1))
30
+ PartyFoul.github.git_data.references.expects(:get).with('test_owner', 'test_repo', 'heads/deploy').returns(Hashie::Mash.new(object: Hashie::Mash.new(sha: 'abcdefg1234567890')))
28
31
  PartyFoul.github.issues.comments.expects(:create).with('test_owner', 'test_repo', 1, body: 'Test Comment')
29
32
  PartyFoul::ExceptionHandler.new(nil, {}).run
30
33
  end
@@ -41,6 +44,7 @@ describe 'Party Foul Exception Handler' do
41
44
  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}]))
42
45
  PartyFoul.github.issues.expects(:edit).with('test_owner', 'test_repo', 1, body: 'New Body', state: 'open')
43
46
  PartyFoul.github.issues.comments.expects(:create).with('test_owner', 'test_repo', 1, body: 'Test Comment')
47
+ PartyFoul.github.git_data.references.expects(:get).with('test_owner', 'test_repo', 'heads/deploy').returns(Hashie::Mash.new(object: Hashie::Mash.new(sha: 'abcdefg1234567890')))
44
48
  PartyFoul::ExceptionHandler.new(nil, {}).run
45
49
  end
46
50
  end
@@ -51,6 +55,7 @@ describe 'Party Foul Exception Handler' do
51
55
  PartyFoul.github.search.stubs(:issues).with(owner: 'test_owner', repo: 'test_repo', keyword: 'test_fingerprint', state: 'closed').returns(Hashie::Mash.new(issues: [{title: 'Test Title', body: 'Test Body', state: 'closed', number: 1}]))
52
56
  PartyFoul.github.issues.expects(:edit).with('test_owner', 'test_repo', 1, body: 'New Body', state: 'open', labels: ['bug', 'regression'])
53
57
  PartyFoul.github.issues.comments.expects(:create).with('test_owner', 'test_repo', 1, body: 'Test Comment')
58
+ PartyFoul.github.git_data.references.expects(:get).with('test_owner', 'test_repo', 'heads/deploy').returns(Hashie::Mash.new(object: Hashie::Mash.new(sha: 'abcdefg1234567890')))
54
59
  PartyFoul::ExceptionHandler.new(nil, {}).run
55
60
  end
56
61
  end
@@ -64,6 +69,7 @@ describe 'Party Foul Exception Handler' do
64
69
  PartyFoul.github.issues.expects(:create).never
65
70
  PartyFoul.github.issues.expects(:edit).never
66
71
  PartyFoul.github.issues.comments.expects(:create).never
72
+ PartyFoul.github.git_data.references.expects(:get).never
67
73
  PartyFoul::ExceptionHandler.new(nil, {}).run
68
74
  end
69
75
  end
data/test/test_helper.rb CHANGED
@@ -21,7 +21,7 @@ module MiniTest::Expectations
21
21
  end
22
22
 
23
23
  def clean_up_party
24
- %w{github oauth_token endpoint owner repo ignored_exceptions processor issue_template comment_template filtered_http_headers web_url}.each do |attr|
24
+ %w{github oauth_token endpoint owner repo ignored_exceptions processor issue_template comment_template filtered_http_headers web_url branch}.each do |attr|
25
25
  PartyFoul.send("#{attr}=", nil)
26
26
  end
27
27
  end
@@ -22,4 +22,7 @@ PartyFoul.configure do |config|
22
22
 
23
23
  # The repository for this application
24
24
  config.repo = 'test_repo'
25
+
26
+ # The branch for your deployed code
27
+ # config.branch = 'master'
25
28
  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: 0.4.0
4
+ version: 0.5.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-01-27 00:00:00.000000000 Z
13
+ date: 2013-01-28 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: github_api
@@ -200,7 +200,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
200
200
  version: '0'
201
201
  segments:
202
202
  - 0
203
- hash: -3123915090860232817
203
+ hash: -549281873562555121
204
204
  required_rubygems_version: !ruby/object:Gem::Requirement
205
205
  none: false
206
206
  requirements:
@@ -209,7 +209,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
209
209
  version: '0'
210
210
  segments:
211
211
  - 0
212
- hash: -3123915090860232817
212
+ hash: -549281873562555121
213
213
  requirements: []
214
214
  rubyforge_project:
215
215
  rubygems_version: 1.8.23