redmine_airbrake_backend 0.6.2 → 1.0.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/CHANGELOG.md +11 -1
- data/README.md +17 -16
- data/app/controllers/airbrake_controller.rb +113 -70
- data/app/controllers/airbrake_notice_controller.rb +22 -17
- data/app/controllers/airbrake_report_controller.rb +22 -8
- data/app/helpers/airbrake_helper.rb +18 -23
- data/app/views/airbrake/issue_description/default.erb +21 -27
- data/config/locales/de.yml +1 -1
- data/config/locales/en.yml +1 -1
- data/config/routes.rb +3 -4
- data/lib/redmine_airbrake_backend.rb +4 -0
- data/lib/redmine_airbrake_backend/backtrace_element.rb +37 -0
- data/lib/redmine_airbrake_backend/engine.rb +2 -1
- data/lib/redmine_airbrake_backend/error.rb +21 -45
- data/lib/redmine_airbrake_backend/ios_report.rb +87 -0
- data/lib/redmine_airbrake_backend/version.rb +1 -1
- data/redmine_airbrake_backend.gemspec +0 -1
- metadata +5 -21
- data/lib/redmine_airbrake_backend/report/ios.rb +0 -82
- data/lib/redmine_airbrake_backend/request.rb +0 -133
- data/lib/redmine_airbrake_backend/request/json.rb +0 -85
- data/lib/redmine_airbrake_backend/request/xml.rb +0 -122
@@ -1,133 +0,0 @@
|
|
1
|
-
require 'json'
|
2
|
-
require 'base64'
|
3
|
-
require 'redmine_airbrake_backend/error'
|
4
|
-
|
5
|
-
module RedmineAirbrakeBackend
|
6
|
-
# Represents a request received by airbrake
|
7
|
-
class Request
|
8
|
-
class Error < StandardError; end
|
9
|
-
class Invalid < Error; end
|
10
|
-
|
11
|
-
attr_reader :api_key, :project, :tracker, :category, :priority, :assignee, :repository, :type
|
12
|
-
attr_reader :params, :notifier, :errors, :context, :request, :env
|
13
|
-
attr_reader :environment_name
|
14
|
-
|
15
|
-
def initialize(config, data = {})
|
16
|
-
# config
|
17
|
-
@config = self.class.parse_config(config)
|
18
|
-
raise Invalid.new('Encoded configuration in api-key is missing') if @config.blank?
|
19
|
-
|
20
|
-
# API key
|
21
|
-
@api_key = @config[:api_key]
|
22
|
-
raise Invalid.new('No or invalid api-key') if @api_key.blank?
|
23
|
-
|
24
|
-
# Project
|
25
|
-
@project = Project.where(identifier: @config[:project]).first
|
26
|
-
raise Invalid.new('No or invalid project') if @project.blank?
|
27
|
-
raise Invalid.new('Airbrake not enabled for project') if @project.enabled_modules.where(name: :airbrake).empty?
|
28
|
-
|
29
|
-
# Check configuration
|
30
|
-
raise Invalid.new('Custom field for notice hash is not configured!') if notice_hash_field.blank?
|
31
|
-
|
32
|
-
# Tracker
|
33
|
-
@tracker = record_for(@project.trackers, :tracker)
|
34
|
-
raise Invalid.new('No or invalid tracker') if @tracker.blank?
|
35
|
-
raise Invalid.new('Custom field for notice hash not available on selected tracker') if @tracker.custom_fields.where(id: notice_hash_field.id).first.blank?
|
36
|
-
|
37
|
-
# Category
|
38
|
-
@category = record_for(@project.issue_categories, :category)
|
39
|
-
|
40
|
-
# Priority
|
41
|
-
@priority = record_for(IssuePriority, :priority) || IssuePriority.default
|
42
|
-
|
43
|
-
# Assignee
|
44
|
-
@assignee = record_for(@project.users, :assignee, [:id, :login])
|
45
|
-
|
46
|
-
# Repository
|
47
|
-
@repository = @project.repositories.where(identifier: (@config[:repository] || '')).first
|
48
|
-
|
49
|
-
# Type
|
50
|
-
@type = @config[:type]
|
51
|
-
|
52
|
-
# Errors
|
53
|
-
@errors = (data[:errors] || [])
|
54
|
-
@errors.each { |e| e.request = self }
|
55
|
-
|
56
|
-
# Environment
|
57
|
-
@env = data[:env]
|
58
|
-
|
59
|
-
# Request
|
60
|
-
@request = data[:request]
|
61
|
-
|
62
|
-
# Context
|
63
|
-
@context = data[:context]
|
64
|
-
|
65
|
-
# Notifier
|
66
|
-
@notifier = data[:notifier]
|
67
|
-
|
68
|
-
# Params
|
69
|
-
@params = data[:params]
|
70
|
-
|
71
|
-
# Environment name
|
72
|
-
@environment_name = (@env[:environment_name].presence || @env[:name].presence) if @env.present?
|
73
|
-
@environment_name ||= @context[:environment].presence if @context.present?
|
74
|
-
end
|
75
|
-
|
76
|
-
def notice_hash_field
|
77
|
-
custom_field(:hash_field)
|
78
|
-
end
|
79
|
-
|
80
|
-
def occurrences_field
|
81
|
-
custom_field(:occurrences_field)
|
82
|
-
end
|
83
|
-
|
84
|
-
def reopen?
|
85
|
-
reopen_regexp = project_setting(:reopen_regexp)
|
86
|
-
|
87
|
-
return false if environment_name.blank? || reopen_regexp.blank?
|
88
|
-
|
89
|
-
!!(environment_name =~ /#{reopen_regexp}/i)
|
90
|
-
end
|
91
|
-
|
92
|
-
def reopen_repeat_description?
|
93
|
-
!!project_setting(:reopen_repeat_description)
|
94
|
-
end
|
95
|
-
|
96
|
-
private
|
97
|
-
|
98
|
-
def record_for(on, config_key, fields = [:id, :name])
|
99
|
-
fields.each do |field|
|
100
|
-
val = on.where(field => @config[config_key]).first
|
101
|
-
return val if val.present?
|
102
|
-
end
|
103
|
-
|
104
|
-
project_setting(config_key)
|
105
|
-
end
|
106
|
-
|
107
|
-
def project_setting(key)
|
108
|
-
return nil if @project.airbrake_settings.blank?
|
109
|
-
|
110
|
-
@project.airbrake_settings.send(key) if @project.airbrake_settings.respond_to?(key)
|
111
|
-
end
|
112
|
-
|
113
|
-
def custom_field(key)
|
114
|
-
@project.issue_custom_fields.where(id: global_setting(key)).first || CustomField.where(id: global_setting(key), is_for_all: true).first
|
115
|
-
end
|
116
|
-
|
117
|
-
def global_setting(key)
|
118
|
-
Setting.plugin_redmine_airbrake_backend[key]
|
119
|
-
end
|
120
|
-
|
121
|
-
def self.parse_config(api_key_data)
|
122
|
-
config = ::JSON.parse(api_key_data).symbolize_keys rescue nil
|
123
|
-
|
124
|
-
return config.symbolize_keys if config.is_a?(Hash)
|
125
|
-
|
126
|
-
config = ::JSON.parse(Base64.decode64(api_key_data)) rescue nil
|
127
|
-
|
128
|
-
return config.symbolize_keys if config.is_a?(Hash)
|
129
|
-
|
130
|
-
nil
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
@@ -1,85 +0,0 @@
|
|
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
|
-
errors += parse_errors(parsed_json_data[:errors])
|
20
|
-
errors << parse_report(parsed_json_data[:type], parsed_json_data[:report])
|
21
|
-
errors.compact!
|
22
|
-
|
23
|
-
raise Invalid.new('No error or report found') if errors.blank?
|
24
|
-
|
25
|
-
new(config, context: context, params: params, notifier: notifier, errors: errors, request: request, env: env)
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def self.parse_plain_section(section_data)
|
31
|
-
return {} if section_data.blank?
|
32
|
-
|
33
|
-
section_data.symbolize_keys
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.parse_errors(errors)
|
37
|
-
errors = []
|
38
|
-
|
39
|
-
(errors || []).each do |error_data|
|
40
|
-
error = parse_error(error_data)
|
41
|
-
errors << ::RedmineAirbrakeBackend::Error.new(error) if error.present?
|
42
|
-
end
|
43
|
-
|
44
|
-
errors
|
45
|
-
end
|
46
|
-
|
47
|
-
def self.parse_error(error_data)
|
48
|
-
return nil if error_data.blank?
|
49
|
-
|
50
|
-
error = {}
|
51
|
-
error[:type] = secure_type(error_data[:type])
|
52
|
-
error[:message] = error_data[:message]
|
53
|
-
error[:backtrace] = error_data[:backtrace].map do |backtrace_element|
|
54
|
-
{
|
55
|
-
line: backtrace_element[:line].to_i,
|
56
|
-
file: backtrace_element[:file],
|
57
|
-
method: (backtrace_element[:method] || backtrace_element[:function])
|
58
|
-
}
|
59
|
-
end
|
60
|
-
|
61
|
-
error
|
62
|
-
end
|
63
|
-
|
64
|
-
def self.parse_report(type, report_data)
|
65
|
-
return nil if report_data.blank?
|
66
|
-
|
67
|
-
sec_type = secure_type(type)
|
68
|
-
|
69
|
-
require "redmine_airbrake_backend/report/#{sec_type}" rescue nil
|
70
|
-
|
71
|
-
clazz = "RedmineAirbrakeBackend::Report::#{sec_type.camelize}".constantize rescue nil
|
72
|
-
|
73
|
-
return nil if clazz.blank?
|
74
|
-
|
75
|
-
report = clazz.parse(report_data)
|
76
|
-
|
77
|
-
report.present? ? ::RedmineAirbrakeBackend::Error.new(report) : nil
|
78
|
-
end
|
79
|
-
|
80
|
-
def self.secure_type(type)
|
81
|
-
type.to_s.gsub(/[^a-zA-Z0-9_-]/, '')
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
@@ -1,122 +0,0 @@
|
|
1
|
-
require 'json'
|
2
|
-
require 'nokogiri'
|
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 = Nokogiri::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? || version.value.blank?
|
25
|
-
raise UnsupportedVersion.new(version) unless SUPPORTED_API_VERSIONS.include?(version.value)
|
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
|
-
convert_element(notice_doc.at('//request'))
|
55
|
-
end
|
56
|
-
|
57
|
-
def self.convert_element(elem)
|
58
|
-
return nil if elem.nil?
|
59
|
-
|
60
|
-
return elem.children.first.inner_text if elem.children.count == 1 && elem.children.first.is_a?(Nokogiri::XML::Text)
|
61
|
-
return convert_attributes(elem.attributes) if elem.children.blank?
|
62
|
-
return convert_var_elements(elem.children) if elem.children.count == elem.children.select { |c| c.name == 'var' }.count
|
63
|
-
|
64
|
-
h = convert_children(elem)
|
65
|
-
h.delete_if { |k, v| k.strip.blank? }
|
66
|
-
h.symbolize_keys
|
67
|
-
end
|
68
|
-
|
69
|
-
def self.convert_children(elem)
|
70
|
-
h = {}
|
71
|
-
|
72
|
-
elem.children.each do |e|
|
73
|
-
key = format_hash_key(e.name)
|
74
|
-
|
75
|
-
if h.key?(key)
|
76
|
-
h[key] = [h[key]] unless h[key].is_a?(Array)
|
77
|
-
h[key] << convert_element(e)
|
78
|
-
else
|
79
|
-
h[key] = convert_element(e)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
h
|
84
|
-
end
|
85
|
-
|
86
|
-
def self.convert_var_elements(elements)
|
87
|
-
vars = {}
|
88
|
-
elements.each do |elem|
|
89
|
-
vars[format_hash_key(elem.attributes['key'])] = elem.inner_text
|
90
|
-
end
|
91
|
-
vars.delete_if { |k, v| k.strip.blank? }
|
92
|
-
vars.symbolize_keys
|
93
|
-
end
|
94
|
-
|
95
|
-
def self.format_hash_key(key)
|
96
|
-
key.to_s.gsub(/-/, '_')
|
97
|
-
end
|
98
|
-
|
99
|
-
def self.convert_attributes(attributes)
|
100
|
-
attrs = {}
|
101
|
-
|
102
|
-
attributes.each do |k, v|
|
103
|
-
attrs[k] = v.value
|
104
|
-
end
|
105
|
-
|
106
|
-
attrs.symbolize_keys
|
107
|
-
end
|
108
|
-
|
109
|
-
def self.ensure_hash_array(data)
|
110
|
-
return nil if data.blank?
|
111
|
-
|
112
|
-
d = (data.is_a?(Array) ? data : [data]).compact
|
113
|
-
d.reject! { |e| !e.is_a?(Hash) }
|
114
|
-
d.blank? ? nil : d
|
115
|
-
end
|
116
|
-
|
117
|
-
def self.format_backtrace(backtrace)
|
118
|
-
ensure_hash_array(backtrace).first[:line] rescue nil
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|