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.
@@ -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