redmine_airbrake_backend 0.6.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|