redmine_airbrake_backend 0.4.3 → 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.
- checksums.yaml +4 -4
- data/Gemfile.lock +91 -0
- data/Rakefile +3 -8
- data/app/controllers/airbrake_controller.rb +107 -127
- data/app/controllers/airbrake_notice_controller.rb +25 -0
- data/app/controllers/airbrake_project_settings_controller.rb +5 -4
- data/app/controllers/airbrake_report_controller.rb +15 -0
- data/app/helpers/airbrake_helper.rb +40 -15
- data/app/models/airbrake_project_setting.rb +1 -0
- data/app/views/airbrake/issue_description/default.erb +52 -0
- data/config/routes.rb +5 -1
- data/db/migrate/20130708102357_create_airbrake_project_settings.rb +0 -2
- data/db/migrate/20131003151228_add_reopen_repeat_description.rb +0 -2
- data/lib/redmine_airbrake_backend.rb +0 -2
- data/lib/redmine_airbrake_backend/engine.rb +10 -10
- data/lib/redmine_airbrake_backend/error.rb +78 -0
- data/lib/redmine_airbrake_backend/patches/projects_helper.rb +8 -7
- data/lib/redmine_airbrake_backend/report/ios.rb +71 -0
- data/lib/redmine_airbrake_backend/request.rb +132 -0
- data/lib/redmine_airbrake_backend/request/json.rb +73 -0
- data/lib/redmine_airbrake_backend/request/xml.rb +121 -0
- data/lib/redmine_airbrake_backend/version.rb +2 -1
- data/redmine_airbrake_backend.gemspec +3 -3
- data/test/unit/notice_test.rb +1 -3
- metadata +11 -4
- data/app/views/airbrake/_issue_description.erb +0 -50
- data/lib/redmine_airbrake_backend/notice.rb +0 -109
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'redmine_airbrake_backend/request'
|
2
|
+
|
3
|
+
module RedmineAirbrakeBackend
|
4
|
+
class Request
|
5
|
+
# Represents a JSON request received by airbrake
|
6
|
+
class JSON < ::RedmineAirbrakeBackend::Request
|
7
|
+
# Creates a request from a parsed json request
|
8
|
+
def self.parse(parsed_json_data)
|
9
|
+
raise Invalid if parsed_json_data.blank?
|
10
|
+
|
11
|
+
context = parse_plain_section(parsed_json_data[:context])
|
12
|
+
params = parse_plain_section(parsed_json_data[:params])
|
13
|
+
notifier = parse_plain_section(parsed_json_data[:notifier])
|
14
|
+
request = parse_plain_section(parsed_json_data[:request])
|
15
|
+
env = parse_plain_section(parsed_json_data[:environment])
|
16
|
+
config = parsed_json_data[:key]
|
17
|
+
|
18
|
+
errors = []
|
19
|
+
|
20
|
+
(parsed_json_data[:errors] || []).each do |error_data|
|
21
|
+
error = parse_error(error_data)
|
22
|
+
errors << ::RedmineAirbrakeBackend::Error.new(error) if error.present?
|
23
|
+
end
|
24
|
+
|
25
|
+
report = parse_report(parsed_json_data[:type], parsed_json_data[:report])
|
26
|
+
errors << ::RedmineAirbrakeBackend::Error.new(report) if report.present?
|
27
|
+
|
28
|
+
errors.compact!
|
29
|
+
|
30
|
+
raise Invalid.new('No error or report found') if errors.blank?
|
31
|
+
|
32
|
+
new(config, context: context, params: params, notifier: notifier, errors: errors, request: request, env: env)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def self.parse_plain_section(section_data)
|
38
|
+
return {} if section_data.blank?
|
39
|
+
|
40
|
+
section_data.symbolize_keys
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.parse_error(error_data)
|
44
|
+
return nil if error_data.blank?
|
45
|
+
|
46
|
+
error = {}
|
47
|
+
error[:type] = error_data[:type]
|
48
|
+
error[:message] = error_data[:message]
|
49
|
+
error[:backtrace] = error_data[:backtrace].map do |backtrace_element|
|
50
|
+
{
|
51
|
+
line: backtrace_element[:line].to_i,
|
52
|
+
file: backtrace_element[:file],
|
53
|
+
method: (backtrace_element[:method] || backtrace_element[:function])
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
error
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.parse_report(type, report_data)
|
61
|
+
return nil if report_data.blank?
|
62
|
+
|
63
|
+
require "redmine_airbrake_backend/report/#{type}"
|
64
|
+
|
65
|
+
clazz = "RedmineAirbrakeBackend::Report::#{type.to_s.camelize}".constantize rescue nil
|
66
|
+
|
67
|
+
return nil if clazz.blank?
|
68
|
+
|
69
|
+
clazz.parse(report_data)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'hpricot'
|
3
|
+
require 'redmine_airbrake_backend/request'
|
4
|
+
|
5
|
+
module RedmineAirbrakeBackend
|
6
|
+
class Request
|
7
|
+
# Represents a XML request received by airbrake
|
8
|
+
class XML < ::RedmineAirbrakeBackend::Request
|
9
|
+
# Supported airbrake api versions
|
10
|
+
SUPPORTED_API_VERSIONS = %w(2.4)
|
11
|
+
|
12
|
+
class UnsupportedVersion < Error; end
|
13
|
+
|
14
|
+
# Creates a notice from an airbrake xml request
|
15
|
+
def self.parse(xml_data)
|
16
|
+
doc = Hpricot::XML(xml_data) rescue nil
|
17
|
+
raise Invalid if doc.blank?
|
18
|
+
|
19
|
+
notice = doc.at('notice')
|
20
|
+
raise Invalid if notice.blank?
|
21
|
+
|
22
|
+
# Version
|
23
|
+
version = notice.attributes['version']
|
24
|
+
raise Invalid.new('No version') if version.blank?
|
25
|
+
raise UnsupportedVersion.new(version) unless SUPPORTED_API_VERSIONS.include?(version)
|
26
|
+
|
27
|
+
error_data = parse_error(notice)
|
28
|
+
raise Invalid.new('No error found') if error_data.blank?
|
29
|
+
error = RedmineAirbrakeBackend::Error.new(error_data)
|
30
|
+
|
31
|
+
notifier = convert_element(notice.at('notifier'))
|
32
|
+
request = parse_request(notice)
|
33
|
+
env = convert_element(notice.at('server-environment'))
|
34
|
+
config = notice.at('api-key').inner_text
|
35
|
+
|
36
|
+
new(config, notifier: notifier, errors: [error], request: request, env: env)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def self.parse_error(notice_doc)
|
42
|
+
error = convert_element(notice_doc.at('error'))
|
43
|
+
|
44
|
+
# map class to type
|
45
|
+
error[:type] ||= error[:class]
|
46
|
+
error.delete(:class)
|
47
|
+
|
48
|
+
error[:backtrace] = format_backtrace(error[:backtrace])
|
49
|
+
|
50
|
+
error
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.parse_request(notice_doc)
|
54
|
+
request = convert_element(notice_doc.at('request'))
|
55
|
+
|
56
|
+
if request.present? && request[:session].present?
|
57
|
+
request[:session][:log] = request[:session][:log].present? ? format_session_log(request[:session][:log]) : nil
|
58
|
+
end
|
59
|
+
|
60
|
+
request
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.convert_element(elem)
|
64
|
+
return nil if elem.nil?
|
65
|
+
return elem.children.first.inner_text if !elem.children.nil? && elem.children.count == 1 && elem.children.first.is_a?(Hpricot::Text)
|
66
|
+
return elem.attributes.to_hash.symbolize_keys if elem.children.nil?
|
67
|
+
return convert_var_elements(elem.children) if elem.children.count == elem.children.select { |c| c.name == 'var' }.count
|
68
|
+
|
69
|
+
h = {}
|
70
|
+
elem.children.each do |e|
|
71
|
+
key = format_hash_key(e.name)
|
72
|
+
if h.key?(key)
|
73
|
+
h[key] = [h[key]] unless h[key].is_a?(Array)
|
74
|
+
h[key] << convert_element(e)
|
75
|
+
else
|
76
|
+
h[key] = convert_element(e)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
h.delete_if { |k, v| k.strip.blank? }
|
80
|
+
h.symbolize_keys
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.convert_var_elements(elements)
|
84
|
+
vars = {}
|
85
|
+
elements.each do |elem|
|
86
|
+
vars[format_hash_key(elem.attributes['key'])] = elem.inner_text
|
87
|
+
end
|
88
|
+
vars.delete_if { |k, v| k.strip.blank? }
|
89
|
+
vars.symbolize_keys
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.format_hash_key(key)
|
93
|
+
key.to_s.gsub(/-/, '_')
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.ensure_hash_array(data)
|
97
|
+
return nil if data.blank?
|
98
|
+
|
99
|
+
d = (data.is_a?(Array) ? data : [data]).compact
|
100
|
+
d.reject! { |e| !e.is_a?(Hash) }
|
101
|
+
d.blank? ? nil : d
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.format_backtrace(backtrace)
|
105
|
+
ensure_hash_array(backtrace).first[:line] rescue nil
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.format_session_log(log)
|
109
|
+
log = ::JSON.parse(log) rescue nil
|
110
|
+
|
111
|
+
log = ensure_hash_array(log)
|
112
|
+
return nil if log.blank?
|
113
|
+
|
114
|
+
log.map! { |l| l.symbolize_keys!; l[:time] = (Time.parse(l[:time]) rescue nil); l }
|
115
|
+
log.reject! { |l| l[:time].blank? }
|
116
|
+
|
117
|
+
log.blank? ? nil : log
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
|
2
|
+
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
3
3
|
|
4
4
|
require 'redmine_airbrake_backend/version'
|
5
5
|
|
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = RedmineAirbrakeBackend::VERSION
|
9
9
|
spec.authors = ['Florian Schwab']
|
10
10
|
spec.email = ['me@ydkn.de']
|
11
|
-
spec.description = %q
|
12
|
-
spec.summary = %q
|
11
|
+
spec.description = %q(Plugin which adds Airbrake support to Redmine)
|
12
|
+
spec.summary = %q(This plugin provides the necessary API to use Redmine as a Airbrake backend)
|
13
13
|
spec.homepage = 'https://github.com/ydkn/redmine_airbrake_backend'
|
14
14
|
spec.license = 'MIT'
|
15
15
|
|
data/test/unit/notice_test.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require File.expand_path('../../test_helper', __FILE__)
|
2
2
|
|
3
3
|
class NoticeTest < ActiveSupport::TestCase
|
4
|
-
|
5
4
|
test 'parse' do
|
6
5
|
api_key = {
|
7
6
|
api_key: 'foobar',
|
@@ -48,7 +47,6 @@ class NoticeTest < ActiveSupport::TestCase
|
|
48
47
|
private
|
49
48
|
|
50
49
|
def prepare_xml(xml_data)
|
51
|
-
xml_data.split("\n").map{|l| l.strip}.join('')
|
50
|
+
xml_data.split("\n").map { |l| l.strip }.join('')
|
52
51
|
end
|
53
|
-
|
54
52
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redmine_airbrake_backend
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Florian Schwab
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-10-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -89,14 +89,17 @@ extra_rdoc_files: []
|
|
89
89
|
files:
|
90
90
|
- ".gitignore"
|
91
91
|
- Gemfile
|
92
|
+
- Gemfile.lock
|
92
93
|
- MIT-LICENSE
|
93
94
|
- README.md
|
94
95
|
- Rakefile
|
95
96
|
- app/controllers/airbrake_controller.rb
|
97
|
+
- app/controllers/airbrake_notice_controller.rb
|
96
98
|
- app/controllers/airbrake_project_settings_controller.rb
|
99
|
+
- app/controllers/airbrake_report_controller.rb
|
97
100
|
- app/helpers/airbrake_helper.rb
|
98
101
|
- app/models/airbrake_project_setting.rb
|
99
|
-
- app/views/airbrake/
|
102
|
+
- app/views/airbrake/issue_description/default.erb
|
100
103
|
- app/views/projects/settings/_airbrake.html.erb
|
101
104
|
- app/views/settings/_airbrake.html.erb
|
102
105
|
- bin/rails
|
@@ -107,12 +110,16 @@ files:
|
|
107
110
|
- db/migrate/20131003151228_add_reopen_repeat_description.rb
|
108
111
|
- lib/redmine_airbrake_backend.rb
|
109
112
|
- lib/redmine_airbrake_backend/engine.rb
|
110
|
-
- lib/redmine_airbrake_backend/
|
113
|
+
- lib/redmine_airbrake_backend/error.rb
|
111
114
|
- lib/redmine_airbrake_backend/patches/issue_category.rb
|
112
115
|
- lib/redmine_airbrake_backend/patches/issue_priority.rb
|
113
116
|
- lib/redmine_airbrake_backend/patches/project.rb
|
114
117
|
- lib/redmine_airbrake_backend/patches/projects_helper.rb
|
115
118
|
- lib/redmine_airbrake_backend/patches/tracker.rb
|
119
|
+
- lib/redmine_airbrake_backend/report/ios.rb
|
120
|
+
- lib/redmine_airbrake_backend/request.rb
|
121
|
+
- lib/redmine_airbrake_backend/request/json.rb
|
122
|
+
- lib/redmine_airbrake_backend/request/xml.rb
|
116
123
|
- lib/redmine_airbrake_backend/version.rb
|
117
124
|
- lib/tasks/test.rake
|
118
125
|
- redmine_airbrake_backend.gemspec
|
@@ -1,50 +0,0 @@
|
|
1
|
-
h1. <%= @notice.error[:message] %>
|
2
|
-
|
3
|
-
<% if @notice.error.present? && @notice.error[:backtrace].present? %>
|
4
|
-
h2. Stacktrace:
|
5
|
-
|
6
|
-
p((. <%=raw @notice.error[:backtrace].collect{|e| format_backtrace_element(e)}.join("\n") %>
|
7
|
-
<% end %>
|
8
|
-
|
9
|
-
<% if @notice.request.present? %>
|
10
|
-
h2. Request:
|
11
|
-
|
12
|
-
<%=format_list_item('URL', @notice.request[:url]) %>
|
13
|
-
<%=format_list_item('Component', @notice.request[:component]) %>
|
14
|
-
<%=format_list_item('Action', @notice.request[:action]) %>
|
15
|
-
|
16
|
-
<% if @notice.request[:params].present? %>
|
17
|
-
h2. Parameters:
|
18
|
-
|
19
|
-
<%=format_table(@notice.request[:params]) %>
|
20
|
-
<% end %>
|
21
|
-
|
22
|
-
<% if @notice.request[:cgi_data].present? %>
|
23
|
-
h2. Headers:
|
24
|
-
|
25
|
-
<%=format_table(@notice.request[:cgi_data]) %>
|
26
|
-
<% end %>
|
27
|
-
|
28
|
-
<% log = @notice.request[:session].delete(:log) if @notice.request[:session].present? %>
|
29
|
-
<% if @notice.request[:session].present? %>
|
30
|
-
h2. Session:
|
31
|
-
|
32
|
-
<%=format_table(@notice.request[:session]) %>
|
33
|
-
<% end %>
|
34
|
-
|
35
|
-
<% if log.present? %>
|
36
|
-
h2. Log:
|
37
|
-
|
38
|
-
<pre>
|
39
|
-
<%=format_log(log) %>
|
40
|
-
</pre>
|
41
|
-
<% end %>
|
42
|
-
<% end %>
|
43
|
-
|
44
|
-
<% if @notice.env.present? %>
|
45
|
-
h2. Environment
|
46
|
-
|
47
|
-
<%=format_list_item('Name', @notice.env[:environment_name]) %>
|
48
|
-
<%=format_list_item('Directory', @notice.env[:project_root]) %>
|
49
|
-
<%=format_list_item('Hostname', @notice.env[:hostname]) %>
|
50
|
-
<% end %>
|
@@ -1,109 +0,0 @@
|
|
1
|
-
require 'json'
|
2
|
-
require 'hpricot'
|
3
|
-
require 'htmlentities'
|
4
|
-
|
5
|
-
module RedmineAirbrakeBackend
|
6
|
-
class Notice
|
7
|
-
SUPPORTED_API_VERSIONS = %w(2.4)
|
8
|
-
|
9
|
-
class NoticeInvalid < StandardError; end
|
10
|
-
class UnsupportedVersion < StandardError; end
|
11
|
-
|
12
|
-
attr_reader :version, :params, :notifier, :error, :request, :env
|
13
|
-
|
14
|
-
def initialize(version, params, notifier, options={})
|
15
|
-
@version = version
|
16
|
-
@params = params
|
17
|
-
@notifier = notifier
|
18
|
-
|
19
|
-
@error = options.delete(:error)
|
20
|
-
@request = options.delete(:request)
|
21
|
-
@env = options.delete(:env)
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.parse(xml_data)
|
25
|
-
doc = Hpricot::XML(xml_data)
|
26
|
-
|
27
|
-
raise NoticeInvalid if (notice = doc.at('notice')).blank?
|
28
|
-
raise NoticeInvalid.new('no version') if (version = notice.attributes['version']).blank?
|
29
|
-
raise UnsupportedVersion.new(version) unless SUPPORTED_API_VERSIONS.include?(version)
|
30
|
-
|
31
|
-
params = JSON.parse(notice.at('api-key').inner_text).symbolize_keys rescue nil
|
32
|
-
raise NoticeInvalid.new('no or invalid api-key') if params.blank?
|
33
|
-
|
34
|
-
raise NoticeInvalid.new('no notifier') if (notifier = convert_element(notice.at('notifier'))).blank?
|
35
|
-
|
36
|
-
raise NoticeInvalid.new('no error') if (error = convert_element(notice.at('error'))).blank?
|
37
|
-
raise NoticeInvalid.new('no message') if error[:message].blank?
|
38
|
-
|
39
|
-
error[:backtrace] = format_backtrace(error[:backtrace])
|
40
|
-
|
41
|
-
request = convert_element(notice.at('request'))
|
42
|
-
if request.present? && request[:session].present?
|
43
|
-
request[:session][:log] = request[:session][:log].present? ? format_session_log(request[:session][:log]) : nil
|
44
|
-
end
|
45
|
-
|
46
|
-
env = convert_element(notice.at('server-environment'))
|
47
|
-
|
48
|
-
new(version, params, notifier, error: error, request: request, env: env)
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
def self.convert_element(elem)
|
54
|
-
return nil if elem.nil?
|
55
|
-
return elem.children.first.inner_text if !elem.children.nil? && elem.children.count == 1 && elem.children.first.is_a?(Hpricot::Text)
|
56
|
-
return elem.attributes.to_hash.symbolize_keys if elem.children.nil?
|
57
|
-
return convert_var_elements(elem.children) if elem.children.count == elem.children.select{|c| c.name == 'var'}.count
|
58
|
-
|
59
|
-
h = {}
|
60
|
-
elem.children.each do |e|
|
61
|
-
key = format_hash_key(e.name)
|
62
|
-
if h.key?(key)
|
63
|
-
h[key] = [h[key]] unless h[key].is_a?(Array)
|
64
|
-
h[key] << convert_element(e)
|
65
|
-
else
|
66
|
-
h[key] = convert_element(e)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
h.delete_if{|k,v| k.strip.blank?}
|
70
|
-
h.symbolize_keys
|
71
|
-
end
|
72
|
-
|
73
|
-
def self.convert_var_elements(elements)
|
74
|
-
vars = {}
|
75
|
-
elements.each do |elem|
|
76
|
-
vars[format_hash_key(elem.attributes['key'])] = elem.inner_text
|
77
|
-
end
|
78
|
-
vars.delete_if{|k,v| k.strip.blank?}
|
79
|
-
vars.symbolize_keys
|
80
|
-
end
|
81
|
-
|
82
|
-
def self.format_hash_key(key)
|
83
|
-
key.to_s.gsub(/-/, '_')
|
84
|
-
end
|
85
|
-
|
86
|
-
def self.ensure_hash_array(data)
|
87
|
-
return nil if data.blank?
|
88
|
-
|
89
|
-
d = (data.is_a?(Array) ? data : [data]).compact
|
90
|
-
d.reject!{|e| !e.is_a?(Hash)}
|
91
|
-
d.blank? ? nil : d
|
92
|
-
end
|
93
|
-
|
94
|
-
def self.format_backtrace(backtrace)
|
95
|
-
ensure_hash_array(backtrace).first[:line] rescue nil
|
96
|
-
end
|
97
|
-
|
98
|
-
def self.format_session_log(log)
|
99
|
-
log = JSON.parse(log) rescue nil
|
100
|
-
return nil unless log = ensure_hash_array(log)
|
101
|
-
|
102
|
-
log.map!{|l| l.symbolize_keys!; l[:time] = (Time.parse(l[:time]) rescue nil); l}
|
103
|
-
log.reject!{|l| l[:time].blank?}
|
104
|
-
|
105
|
-
log.blank? ? nil : log
|
106
|
-
end
|
107
|
-
|
108
|
-
end
|
109
|
-
end
|