redmine_airbrake_backend 1.0.3 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3e17bf3121cc154d47b38ad482b7269a04b30f25
4
- data.tar.gz: 8a26cee201e9a7d6b5b8e9a7b3491e76ad4da4c5
3
+ metadata.gz: 5926fca2b10cfdf572494c9891adc633b583bbdb
4
+ data.tar.gz: 0dc91282b47937a48254ff1e3da0fbd4b555c0be
5
5
  SHA512:
6
- metadata.gz: cf46bb9b3d773987e39309ecc34d6dba06da4649f35459e11d1dae3ffdd6586d366b9f8fa7c4e979b23c2db3f5b966e995de3663b84058be5dbc7759c117b409
7
- data.tar.gz: b3cf1f9f4514a42fa90e1019df70ed9dbed0efac0a10f4c6f42632eeee2b821c53aa055e70d4b56d5409e8eb533dd2ac7b1ff7f735128c76d0b5cc455bf5de19
6
+ metadata.gz: 58cfc22082b4c0098a5c756d915401fe334a3346c55541038b21fcd500978f5646ab7627234b5eb882f00561df2bbc28569de2b3c2976d1708c31857a59036ad
7
+ data.tar.gz: 71de8ff6c61f7894afbafb4a763cef7402651beaac638f0756839a140fdc877c0ba7f6cc0466b5f39883b39460472ddd1531d968a37599b2aaaf772bd5c589ff
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.0 (2016-01-29)
4
+ ### Fixes
5
+ - Handle new ruby lambda backtrace syntax
6
+ - Don't treat nested errors separately
7
+
3
8
  ## 1.0.3 (2016-01-26)
4
9
  ### Fixes
5
10
  - Fix syntax error
@@ -1,5 +1,5 @@
1
1
  require 'tempfile'
2
- require 'redmine_airbrake_backend/error'
2
+ require 'redmine_airbrake_backend/notice'
3
3
 
4
4
 
5
5
  # Controller with airbrake related stuff
@@ -11,7 +11,6 @@ class AirbrakeController < ::ApplicationController
11
11
  prepend_before_action :load_records
12
12
  prepend_before_action :parse_key
13
13
  prepend_before_action :find_project
14
- before_action :set_environment
15
14
  before_action :authorize
16
15
 
17
16
  after_action :cleanup_tempfiles
@@ -25,14 +24,11 @@ class AirbrakeController < ::ApplicationController
25
24
  end
26
25
 
27
26
  def parse_key
28
- @key = JSON.parse(params[:key]).symbolize_keys #rescue nil
27
+ @key = JSON.parse(params[:key]).symbolize_keys rescue nil
29
28
 
30
29
  # API key
31
30
  invalid_request!('No or invalid API key') if @key.blank? || @key[:key].blank?
32
31
  params[:key] = @key[:key]
33
-
34
- # Type
35
- @type = @key[:type] || (params[:context][:language].split('/', 2).first.downcase rescue nil)
36
32
  end
37
33
 
38
34
  def load_records
@@ -56,10 +52,6 @@ class AirbrakeController < ::ApplicationController
56
52
  @repository = @project.repositories.find_by(identifier: (@key[:repository] || ''))
57
53
  end
58
54
 
59
- def set_environment
60
- @environment ||= params[:context][:environment].presence rescue nil
61
- end
62
-
63
55
  def invalid_request!(message)
64
56
  raise InvalidRequest.new(message)
65
57
  end
@@ -112,39 +104,52 @@ class AirbrakeController < ::ApplicationController
112
104
  custom_field(:occurrences_field)
113
105
  end
114
106
 
107
+ def create_issue!
108
+ return if @notice.blank?
109
+ return if @notice.errors.blank?
110
+
111
+ @issue = find_or_initialize_issue(@notice)
112
+
113
+ set_issue_custom_field_values(@issue, @notice)
114
+
115
+ reopen_issue(@issue, @notice) if @issue.persisted? && @issue.status.is_closed? && reopen_issue?(@notice)
116
+
117
+ @issue = nil unless @issue.save
118
+ end
119
+
115
120
  # Load or initialize issue by project, tracker and airbrake hash
116
- def find_or_initialize_issue(error)
117
- issue_ids = CustomValue.where(customized_type: Issue.name, custom_field_id: notice_hash_field.id, value: error.id).pluck(:customized_id)
121
+ def find_or_initialize_issue(notice)
122
+ issue_ids = CustomValue.where(customized_type: Issue.name, custom_field_id: notice_hash_field.id, value: notice.id).pluck(:customized_id)
118
123
 
119
124
  issue = Issue.find_by(id: issue_ids, project_id: @project.id, tracker_id: @tracker.id)
120
125
 
121
126
  return issue if issue.present?
122
127
 
123
- initialize_issue(error)
128
+ initialize_issue(notice)
124
129
  end
125
130
 
126
- def initialize_issue(error)
131
+ def initialize_issue(notice)
127
132
  issue = Issue.new(
128
- subject: error.subject,
133
+ subject: notice.subject,
129
134
  project: @project,
130
135
  tracker: @tracker,
131
136
  author: User.current,
132
137
  category: @category,
133
138
  priority: @priority,
134
- description: render_description(error),
139
+ description: render_description(notice),
135
140
  assigned_to: @assignee
136
141
  )
137
142
 
138
- add_attachments_to_issue(issue, error)
143
+ add_attachments_to_issue(issue, notice)
139
144
 
140
145
  issue
141
146
  end
142
147
 
143
- def set_issue_custom_field_values(issue, error)
148
+ def set_issue_custom_field_values(issue, notice)
144
149
  custom_field_values = {}
145
150
 
146
151
  # Error ID
147
- custom_field_values[notice_hash_field.id] = error.id if issue.new_record?
152
+ custom_field_values[notice_hash_field.id] = notice.id if issue.new_record?
148
153
 
149
154
  # Update occurrences
150
155
  if occurrences_field.present?
@@ -155,12 +160,12 @@ class AirbrakeController < ::ApplicationController
155
160
  issue.custom_field_values = custom_field_values
156
161
  end
157
162
 
158
- def add_attachments_to_issue(issue, error)
159
- return if error.attachments.blank?
163
+ def add_attachments_to_issue(issue, notice)
164
+ return if notice.attachments.blank?
160
165
 
161
166
  @tempfiles ||= []
162
167
 
163
- error.attachments.each do |data|
168
+ notice.attachments.each do |data|
164
169
  filename = data[:filename].presence || Redmine::Utils.random_hex(16)
165
170
 
166
171
  file = Tempfile.new(filename)
@@ -173,37 +178,37 @@ class AirbrakeController < ::ApplicationController
173
178
  end
174
179
  end
175
180
 
176
- def reopen_issue?
181
+ def reopen_issue?(notice)
177
182
  reopen_regexp = project_setting(:reopen_regexp)
178
183
 
179
184
  return false if reopen_regexp.blank?
180
- return false if @environment.blank?
185
+ return false if notice.environment_name.blank?
181
186
 
182
- !!(@environment =~ /#{reopen_regexp}/i)
187
+ !!(notice.environment_name =~ /#{reopen_regexp}/i)
183
188
  end
184
189
 
185
190
  def issue_reopen_repeat_description?
186
191
  !!project_setting(:reopen_repeat_description)
187
192
  end
188
193
 
189
- def reopen_issue(issue, error)
190
- return if @environment.blank?
194
+ def reopen_issue(issue, notice)
195
+ return if notice.environment_name.blank?
191
196
 
192
- desc = "*Issue reopened after occurring again in _#{@environment}_ environment.*"
193
- desc << "\n\n#{render_description(error)}" if issue_reopen_repeat_description?
197
+ desc = "*Issue reopened after occurring again in _#{notice.environment_name}_ environment.*"
198
+ desc << "\n\n#{render_description(notice)}" if issue_reopen_repeat_description?
194
199
 
195
200
  issue.status = issue.tracker.default_status
196
201
 
197
202
  issue.init_journal(User.current, desc)
198
203
 
199
- add_attachments_to_issue(issue, error)
204
+ add_attachments_to_issue(issue, notice)
200
205
  end
201
206
 
202
- def render_description(error)
203
- locals = { error: error }
207
+ def render_description(notice)
208
+ locals = { notice: notice }
204
209
 
205
- if template_exists?("airbrake/issue_description/#{@type}")
206
- render_to_string("airbrake/issue_description/#{@type}", layout: false, locals: locals)
210
+ if template_exists?("airbrake/issue_description/#{notice.type}")
211
+ render_to_string("airbrake/issue_description/#{notice.type}", layout: false, locals: locals)
207
212
  else
208
213
  render_to_string('airbrake/issue_description/default', layout: false, locals: locals)
209
214
  end
@@ -2,26 +2,24 @@
2
2
  class AirbrakeNoticeController < ::AirbrakeController
3
3
  accept_api_auth :notices
4
4
 
5
+ before_action :parse_notice
6
+
5
7
  # Handle airbrake notices
6
8
  def notices
7
- create_issues
9
+ create_issue!
8
10
 
9
11
  render_airbrake_response
10
12
  end
11
13
 
12
14
  private
13
15
 
14
- def create_issues
15
- params[:errors].each do |e|
16
- error = RedmineAirbrakeBackend::Error.new(e)
17
-
18
- issue = find_or_initialize_issue(error)
19
-
20
- set_issue_custom_field_values(issue, error)
21
-
22
- reopen_issue(issue, error) if issue.persisted? && issue.status.is_closed? && reopen_issue?
23
-
24
- @issue ||= issue if issue.save
25
- end
16
+ def parse_notice
17
+ @notice = RedmineAirbrakeBackend::Notice.new(
18
+ errors: params[:errors].map { |e| RedmineAirbrakeBackend::Error.new(e) },
19
+ params: params[:params],
20
+ session: params[:session],
21
+ context: params[:context],
22
+ environment: params[:environment]
23
+ )
26
24
  end
27
25
  end
@@ -5,24 +5,18 @@ require 'redmine_airbrake_backend/ios_report'
5
5
  class AirbrakeReportController < ::AirbrakeController
6
6
  accept_api_auth :ios_reports
7
7
 
8
+ before_action :parse_report
9
+
8
10
  # Handle airbrake iOS reports
9
11
  def ios_reports
10
- create_issue
12
+ create_issue!
11
13
 
12
14
  render_airbrake_response
13
15
  end
14
16
 
15
17
  private
16
18
 
17
- def create_issue
18
- error = RedmineAirbrakeBackend::IosReport.new(params[:report])
19
-
20
- @issue = find_or_initialize_issue(error)
21
-
22
- set_issue_custom_field_values(@issue, error)
23
-
24
- reopen_issue(@issue, error) if @issue.persisted? && @issue.status.is_closed? && reopen_issue?
25
-
26
- @issue = nil unless @issue.save
19
+ def parse_report
20
+ @notice = RedmineAirbrakeBackend::IosReport.new(params)
27
21
  end
28
22
  end
@@ -1,7 +1,17 @@
1
1
  module AirbrakeHelper
2
+ # Error title
3
+ def airbrake_error_title(error)
4
+ if error.type.blank?
5
+ error.message
6
+ else
7
+ "#{error.type}: #{error.message}"
8
+ end
9
+ end
10
+
2
11
  # Wiki markup for a table
3
- def format_table(data)
12
+ def airbrake_format_table(data)
4
13
  lines = []
14
+
5
15
  data.each do |key, value|
6
16
  next if value.blank?
7
17
 
@@ -11,19 +21,20 @@ module AirbrakeHelper
11
21
  lines << "|@#{key}@|@#{value.map { |k, v| "#{k}: #{v}"}.join(', ')}@|"
12
22
  end
13
23
  end
24
+
14
25
  lines.join("\n")
15
26
  end
16
27
 
17
28
  # Wiki markup for a list item
18
- def format_list_item(name, value)
29
+ def airbrake_format_list_item(name, value)
19
30
  return '' if value.blank?
20
31
 
21
32
  "* *#{name}:* #{value}"
22
33
  end
23
34
 
24
35
  # Wiki markup for backtrace element with link to repository if possible
25
- def format_backtrace_element(element)
26
- repository = repository_for_backtrace_element(element)
36
+ def airbrake_format_backtrace_element(element)
37
+ repository = airbrake_repository_for_backtrace_element(element)
27
38
 
28
39
  if repository.blank?
29
40
  if element.line.blank?
@@ -32,7 +43,7 @@ module AirbrakeHelper
32
43
  markup = "@#{element.file}:#{element.line}@"
33
44
  end
34
45
  else
35
- filename = filename_for_backtrace_element(element)
46
+ filename = airbrake_filename_for_backtrace_element(element)
36
47
 
37
48
  if repository.identifier.blank?
38
49
  markup = "source:\"#{filename}#L#{element.line}\""
@@ -46,15 +57,15 @@ module AirbrakeHelper
46
57
 
47
58
  private
48
59
 
49
- def repository_for_backtrace_element(element)
60
+ def airbrake_repository_for_backtrace_element(element)
50
61
  return nil unless element.file.start_with?('[PROJECT_ROOT]')
51
62
 
52
- filename = filename_for_backtrace_element(element)
63
+ filename = airbrake_filename_for_backtrace_element(element)
53
64
 
54
- repositories_for_backtrace.find { |r| r.entry(filename) }
65
+ airbrake_repositories_for_backtrace.find { |r| r.entry(filename) }
55
66
  end
56
67
 
57
- def repositories_for_backtrace
68
+ def airbrake_repositories_for_backtrace
58
69
  return @_bactrace_repositories unless @_bactrace_repositories.nil?
59
70
 
60
71
  if @repository.present?
@@ -66,7 +77,7 @@ module AirbrakeHelper
66
77
  @_bactrace_repositories
67
78
  end
68
79
 
69
- def filename_for_backtrace_element(element)
80
+ def airbrake_filename_for_backtrace_element(element)
70
81
  return nil if element.file.blank?
71
82
 
72
83
  element.file[14..-1]
@@ -1,44 +1,45 @@
1
- h1. <%= error.message %>
2
-
1
+ <% notice.errors.each do |error| %>
2
+ h1. <%= airbrake_error_title(error) %>
3
3
 
4
4
  <% if error.backtrace.present? %>
5
5
  h2. Stacktrace:
6
6
 
7
- p((. <%= raw error.backtrace.map { |e| format_backtrace_element(e) }.join("\n") %>
7
+ p((. <%= raw error.backtrace.map { |e| airbrake_format_backtrace_element(e) }.join("\n") %>
8
8
  <% end %>
9
9
 
10
+ <% end %>
10
11
 
11
- <% if error.application.present? %>
12
+ <% if notice.application.present? %>
12
13
  h2. Application:
13
14
 
14
- <%= format_list_item('Name', error.application[:name]) %>
15
- <%= format_list_item('Version', error.application[:version]) %>
15
+ <%= airbrake_format_list_item('Name', notice.application[:name]) %>
16
+ <%= airbrake_format_list_item('Version', notice.application[:version]) %>
16
17
  <% end %>
17
18
 
18
19
 
19
- <% if params[:params].present? %>
20
+ <% if notice.params.present? %>
20
21
  h2. Parameters:
21
22
 
22
- <%= format_table(params[:params]) %>
23
+ <%= airbrake_format_table(notice.params) %>
23
24
  <% end %>
24
25
 
25
26
 
26
- <% if params[:session].present? %>
27
+ <% if notice.session.present? %>
27
28
  h2. Session
28
29
 
29
- <%= format_table(params[:session]) %>
30
+ <%= airbrake_format_table(notice.session) %>
30
31
  <% end %>
31
32
 
32
33
 
33
- <% if params[:context].present? %>
34
+ <% if notice.context.present? %>
34
35
  h2. Context
35
36
 
36
- <%= format_table(params[:context].reject { |k, v| ['notifier'].include?(k) }) %>
37
+ <%= airbrake_format_table(notice.context) %>
37
38
  <% end %>
38
39
 
39
40
 
40
- <% if params[:environment].present? %>
41
+ <% if notice.environment.present? %>
41
42
  h2. Environment
42
43
 
43
- <%= format_table(params[:environment]) %>
44
+ <%= airbrake_format_table(notice.environment) %>
44
45
  <% end %>
@@ -29,7 +29,7 @@ module RedmineAirbrakeBackend
29
29
  def normalize_function_name(function_name)
30
30
  name = @function
31
31
  .downcase
32
- .gsub(/_\d+_/, '') # ruby blocks
32
+ .gsub(/([\d_]+$)?/, '') # ruby blocks
33
33
 
34
34
  RedmineAirbrakeBackend.filter_hex_values(name)
35
35
  end
@@ -5,30 +5,20 @@ require 'redmine_airbrake_backend/backtrace_element'
5
5
  module RedmineAirbrakeBackend
6
6
  # Error received by airbrake
7
7
  class Error
8
- attr_reader :type, :message, :backtrace
9
- attr_reader :id, :subject, :application, :attachments
8
+ attr_reader :id, :type, :message, :backtrace
10
9
 
11
- def initialize(data)
10
+ def initialize(options)
12
11
  # Type
13
- @type = data[:type]
12
+ @type = options[:type]
14
13
 
15
14
  # Message
16
- @message = data[:message]
15
+ @message = options[:message]
17
16
 
18
17
  # Backtrace
19
- @backtrace = data[:backtrace].map { |b| BacktraceElement.new(b) }
18
+ @backtrace = options[:backtrace].map { |b| BacktraceElement.new(b) }
20
19
 
21
20
  # Error ID
22
21
  @id = generate_id
23
-
24
- # Subject
25
- @subject = generate_subject
26
-
27
- # Attachments
28
- @attachments = (data[:attachments].presence || []).compact
29
-
30
- # Application
31
- @application = data[:application].presence
32
22
  end
33
23
 
34
24
  private
@@ -41,17 +31,5 @@ module RedmineAirbrakeBackend
41
31
 
42
32
  Digest::MD5.hexdigest(h.compact.join("\n"))
43
33
  end
44
-
45
- def generate_subject
46
- s = ''
47
-
48
- if @type.blank? || @message.starts_with?("#{@type}:")
49
- s = "[#{@id[0..7]}] #{@message}"
50
- else
51
- s = "[#{@id[0..7]}] #{@type}: #{@message}"
52
- end
53
-
54
- s[0..254].strip
55
- end
56
34
  end
57
35
  end
@@ -1,21 +1,26 @@
1
- require 'redmine_airbrake_backend/error'
1
+ require 'redmine_airbrake_backend/notice'
2
2
 
3
3
 
4
4
  module RedmineAirbrakeBackend
5
5
  # iOS Report received by airbrake
6
- class IosReport < Error
7
- def initialize(data)
8
- super(IosReport.parse(data))
6
+ class IosReport < Notice
7
+ def initialize(options)
8
+ error, application, attachments = self.class.parse(options[:report])
9
+
10
+ super({
11
+ errors: [Error.new(error)],
12
+ context: options[:context],
13
+ application: application,
14
+ attachments: attachments
15
+ })
9
16
  end
10
17
 
11
18
  private
12
19
 
13
20
  def self.parse(data)
14
- error = {
15
- application: {},
16
- backtrace: [],
17
- attachments: [],
18
- }
21
+ error = { backtrace: [] }
22
+ application = {}
23
+ attachments = []
19
24
 
20
25
  header_finished = false
21
26
  next_line_is_message = false
@@ -25,8 +30,8 @@ module RedmineAirbrakeBackend
25
30
  data.split("\n").each do |line|
26
31
  header_finished = true if line =~ /^(Application Specific Information|Last Exception Backtrace|Thread \d+( Crashed)?):$/
27
32
 
28
- unless header_finishedii
29
- ii = parse_header_line(line, error)
33
+ unless header_finished
34
+ ii = parse_header_line(line, error, application)
30
35
  indicent_identifier ||= ii if ii
31
36
  end
32
37
 
@@ -38,7 +43,10 @@ module RedmineAirbrakeBackend
38
43
 
39
44
  crashed_thread = false if line =~ /^Thread \d+:$/
40
45
 
41
- error[:backtrace] << parse_backtrace_element(line) if crashed_thread
46
+ if crashed_thread
47
+ backtrace = parse_backtrace_element(line)
48
+ error[:backtrace] << backtrace if backtrace
49
+ end
42
50
 
43
51
  crashed_thread = true if error[:backtrace].compact.blank? && line =~ /^(Last Exception Backtrace|Thread \d+ Crashed):$/
44
52
 
@@ -47,15 +55,15 @@ module RedmineAirbrakeBackend
47
55
 
48
56
  return nil if error.blank?
49
57
 
50
- error[:attachments] << {
58
+ attachments << {
51
59
  filename: "#{indicent_identifier}.crash",
52
60
  data: data
53
61
  } if indicent_identifier.present?
54
62
 
55
- error
63
+ [error, application, attachments]
56
64
  end
57
65
 
58
- def self.parse_header_line(line, error)
66
+ def self.parse_header_line(line, error, application)
59
67
  key, value = line.split(':', 2).map { |s| s.strip }
60
68
 
61
69
  return nil if key.blank? || value.blank?
@@ -68,9 +76,9 @@ module RedmineAirbrakeBackend
68
76
  when 'Incident Identifier'
69
77
  return value
70
78
  when 'Identifier'
71
- error[:application][:name] = value
79
+ application[:name] = value
72
80
  when 'Version'
73
- error[:application][:version] = value
81
+ application[:version] = value
74
82
  end
75
83
 
76
84
  nil
@@ -0,0 +1,64 @@
1
+ require 'redmine_airbrake_backend/error'
2
+
3
+
4
+ module RedmineAirbrakeBackend
5
+ # Notice received by airbrake
6
+ class Notice
7
+ attr_reader :id, :subject, :type, :environment_name
8
+ attr_reader :errors, :params, :session, :context, :environment, :application, :attachments
9
+
10
+ def initialize(options)
11
+ # Errors
12
+ @errors = options[:errors].compact
13
+
14
+ # Params
15
+ @params = options[:params]
16
+
17
+ # Session
18
+ @session = options[:session]
19
+
20
+ # Context
21
+ @context = options[:context].reject { |k, v| ['notifier'].include?(k) }
22
+
23
+ # Environment
24
+ @environment = options[:environment]
25
+
26
+ # Application
27
+ @application = options[:application]
28
+
29
+ # Attachments
30
+ @attachments = (options[:attachments].presence || []).compact
31
+
32
+ # Environment name
33
+ @environment_name = options[:context][:environment].presence rescue nil
34
+
35
+ # Type
36
+ @type = options[:type] || (options[:context][:language].split('/', 2).first.downcase rescue nil)
37
+
38
+ # Error ID
39
+ @id = generate_id
40
+
41
+ # Subject
42
+ @subject = generate_subject
43
+ end
44
+
45
+ private
46
+
47
+ def generate_id
48
+ Digest::MD5.hexdigest(@errors.map(&:id).join("\n"))
49
+ end
50
+
51
+ def generate_subject
52
+ error = @errors.first
53
+ s = ''
54
+
55
+ if error.type.blank? || error.message.starts_with?("#{error.type}:")
56
+ s = "[#{@id[0..7]}] #{error.message}"
57
+ else
58
+ s = "[#{@id[0..7]}] #{error.type}: #{error.message}"
59
+ end
60
+
61
+ s[0..254].strip
62
+ end
63
+ end
64
+ end
@@ -1,4 +1,4 @@
1
1
  module RedmineAirbrakeBackend
2
2
  # Version of this gem
3
- VERSION = '1.0.3'
3
+ VERSION = '1.1.0'
4
4
  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: 1.0.3
4
+ version: 1.1.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: 2016-01-26 00:00:00.000000000 Z
11
+ date: 2016-01-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -85,6 +85,7 @@ files:
85
85
  - lib/redmine_airbrake_backend/engine.rb
86
86
  - lib/redmine_airbrake_backend/error.rb
87
87
  - lib/redmine_airbrake_backend/ios_report.rb
88
+ - lib/redmine_airbrake_backend/notice.rb
88
89
  - lib/redmine_airbrake_backend/patches/issue_category.rb
89
90
  - lib/redmine_airbrake_backend/patches/issue_priority.rb
90
91
  - lib/redmine_airbrake_backend/patches/project.rb