redmine_airbrake_backend 1.2.1 → 1.2.2
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/.gitlab-ci.yml +42 -0
- data/.rubocop.yml +13 -0
- data/CHANGELOG.md +8 -0
- data/Gemfile +3 -1
- data/README.md +0 -4
- data/Rakefile +2 -1
- data/app/controllers/airbrake_controller.rb +32 -137
- data/app/controllers/airbrake_notice_controller.rb +8 -6
- data/app/controllers/airbrake_project_settings_controller.rb +6 -4
- data/app/controllers/airbrake_report_controller.rb +2 -0
- data/app/controllers/concerns/airbrake_attachments.rb +56 -0
- data/app/controllers/concerns/airbrake_issue_handling.rb +109 -0
- data/app/controllers/concerns/airbrake_rendering.rb +16 -0
- data/app/helpers/airbrake_helper.rb +35 -23
- data/app/models/airbrake_project_setting.rb +2 -0
- data/bin/rails +3 -3
- data/config/routes.rb +2 -0
- data/db/migrate/20130708102357_create_airbrake_project_settings.rb +3 -0
- data/db/migrate/20131003151228_add_reopen_repeat_description.rb +3 -0
- data/lib/redmine_airbrake_backend.rb +3 -0
- data/lib/redmine_airbrake_backend/backtrace_element.rb +6 -4
- data/lib/redmine_airbrake_backend/engine.rb +27 -25
- data/lib/redmine_airbrake_backend/error.rb +2 -0
- data/lib/redmine_airbrake_backend/ios_report.rb +33 -28
- data/lib/redmine_airbrake_backend/notice.rb +43 -27
- data/lib/redmine_airbrake_backend/patches/issue_category.rb +10 -5
- data/lib/redmine_airbrake_backend/patches/issue_priority.rb +10 -5
- data/lib/redmine_airbrake_backend/patches/project.rb +10 -5
- data/lib/redmine_airbrake_backend/patches/projects_helper.rb +16 -11
- data/lib/redmine_airbrake_backend/patches/tracker.rb +10 -5
- data/lib/redmine_airbrake_backend/version.rb +3 -1
- data/lib/tasks/test.rake +11 -9
- data/redmine_airbrake_backend.gemspec +10 -6
- data/test/unit/notice_test.rb +1 -1
- metadata +24 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 78d324b14146a054ed7f62573df24493843ebccc846a1a8936348fff51228098
|
4
|
+
data.tar.gz: 6225ac96f31412e899d0ab2f06e869f22761d4af19583dda20cdfefc1e00d416
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 366e346434ea2a82047ff0d1a7c6314ff2782e5e4056cdf3f2563e2ebfe0f958265d727a1e87dd47eddb001c61b3d832dd3fa20ee4e6f8eb9973e2aba3a59801
|
7
|
+
data.tar.gz: abb59d8c978611fa973c20a3de001904a1bfade5c30d910739ad2285cccc7a024a0e0951e57f4fb1da429d9fae0d2b61ebcb16c6fab24f94a97cd40fd763e18a
|
data/.gitlab-ci.yml
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
stages:
|
2
|
+
- build
|
3
|
+
- codequality
|
4
|
+
- security
|
5
|
+
|
6
|
+
build:
|
7
|
+
stage: build
|
8
|
+
image: ruby:2.5
|
9
|
+
script:
|
10
|
+
- gem install bundler --no-ri --no-rdoc
|
11
|
+
- bundle update
|
12
|
+
artifacts:
|
13
|
+
paths:
|
14
|
+
- Gemfile.lock
|
15
|
+
|
16
|
+
rubocop:
|
17
|
+
stage: codequality
|
18
|
+
image: ruby:2.5
|
19
|
+
script:
|
20
|
+
- gem install rubocop --no-ri --no-rdoc
|
21
|
+
- rubocop
|
22
|
+
|
23
|
+
dependency_scanning:
|
24
|
+
stage: security
|
25
|
+
dependencies:
|
26
|
+
- build
|
27
|
+
image: docker:stable
|
28
|
+
variables:
|
29
|
+
DOCKER_DRIVER: overlay2
|
30
|
+
allow_failure: true
|
31
|
+
services:
|
32
|
+
- docker:stable-dind
|
33
|
+
script:
|
34
|
+
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
|
35
|
+
- docker run
|
36
|
+
--env DEP_SCAN_DISABLE_REMOTE_CHECKS="${DEP_SCAN_DISABLE_REMOTE_CHECKS:-false}"
|
37
|
+
--volume "$PWD:/code"
|
38
|
+
--volume /var/run/docker.sock:/var/run/docker.sock
|
39
|
+
"registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$SP_VERSION" /code
|
40
|
+
artifacts:
|
41
|
+
paths:
|
42
|
+
- gl-dependency-scanning-report.json
|
data/.rubocop.yml
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 1.2.2 (2018-11-03)
|
4
|
+
### Fixes
|
5
|
+
- Fix finding repository if no filename is for backtrace element
|
6
|
+
### Changes
|
7
|
+
- Internal restructuring for better code style
|
8
|
+
- Requires Ruby >= 2.4.0
|
9
|
+
- Requires Redmine >= 3.4.0
|
10
|
+
|
3
11
|
## 1.2.1 (2018-01-29)
|
4
12
|
### Features
|
5
13
|
- All responses are formatted as JSON
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,3 @@
|
|
1
|
-
[](https://rubygems.org/gems/redmine_airbrake_backend)
|
2
|
-
[](https://gemnasium.com/ydkn/redmine_airbrake_backend)
|
3
|
-
[](https://codeclimate.com/github/ydkn/redmine_airbrake_backend)
|
4
|
-
|
5
1
|
# Redmine Airbrake Backend
|
6
2
|
|
7
3
|
This plugin provides the necessary API to use Redmine as an Airbrake backend.
|
data/Rakefile
CHANGED
@@ -1,13 +1,18 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'redmine_airbrake_backend/notice'
|
3
4
|
|
4
5
|
# Controller with airbrake related stuff
|
5
6
|
class AirbrakeController < ::ApplicationController
|
7
|
+
include AirbrakeIssueHandling
|
8
|
+
include AirbrakeRendering
|
9
|
+
|
6
10
|
skip_before_action :verify_authenticity_token
|
7
|
-
skip_before_action :session_expiration, :user_setup, :check_if_login_required, :set_localization,
|
11
|
+
skip_before_action :session_expiration, :user_setup, :check_if_login_required, :set_localization,
|
12
|
+
:check_password_change
|
8
13
|
|
9
14
|
prepend_before_action :find_project
|
10
|
-
prepend_before_action :parse_key
|
15
|
+
prepend_before_action :check_key, :parse_key
|
11
16
|
|
12
17
|
before_action :authorize
|
13
18
|
before_action :find_tracker
|
@@ -16,8 +21,6 @@ class AirbrakeController < ::ApplicationController
|
|
16
21
|
before_action :find_assignee
|
17
22
|
before_action :find_repository
|
18
23
|
|
19
|
-
after_action :cleanup_tempfiles
|
20
|
-
|
21
24
|
private
|
22
25
|
|
23
26
|
def find_project
|
@@ -25,16 +28,18 @@ class AirbrakeController < ::ApplicationController
|
|
25
28
|
end
|
26
29
|
|
27
30
|
def parse_key
|
31
|
+
raw_key = if (request.headers['HTTP_AUTHORIZATION'] || '') =~ /^Bearer\s+(.*)\s*$/i # New auth method
|
32
|
+
Regexp.last_match[1]
|
33
|
+
elsif params[:key].present? # Old auth method
|
34
|
+
params[:key]
|
35
|
+
end
|
36
|
+
|
37
|
+
@key = JSON.parse(raw_key).with_indifferent_access
|
38
|
+
rescue StandardError
|
28
39
|
@key = nil
|
40
|
+
end
|
29
41
|
|
30
|
-
|
31
|
-
# New auth method
|
32
|
-
@key = JSON.parse(Regexp.last_match[1]).with_indifferent_access rescue nil
|
33
|
-
elsif params[:key].present?
|
34
|
-
# Old auth method
|
35
|
-
@key = JSON.parse(params[:key]).with_indifferent_access rescue nil
|
36
|
-
end
|
37
|
-
|
42
|
+
def check_key
|
38
43
|
render_error('No or invalid API key format', :unauthorized) if @key.blank? || @key[:key].blank?
|
39
44
|
end
|
40
45
|
|
@@ -58,7 +63,9 @@ class AirbrakeController < ::ApplicationController
|
|
58
63
|
render_error('No or invalid tracker', :failed_dependency) if @tracker.blank?
|
59
64
|
|
60
65
|
# Check notice ID field
|
61
|
-
|
66
|
+
return unless @tracker.custom_fields.find_by(id: notice_hash_field.id).blank?
|
67
|
+
|
68
|
+
render_error('Custom field for notice hash not available on selected tracker', :failed_dependency)
|
62
69
|
end
|
63
70
|
|
64
71
|
def find_category
|
@@ -70,7 +77,7 @@ class AirbrakeController < ::ApplicationController
|
|
70
77
|
end
|
71
78
|
|
72
79
|
def find_assignee
|
73
|
-
@assignee = record_for(@project.users, :assignee, [
|
80
|
+
@assignee = record_for(@project.users, :assignee, %i[id login])
|
74
81
|
end
|
75
82
|
|
76
83
|
def find_repository
|
@@ -85,8 +92,14 @@ class AirbrakeController < ::ApplicationController
|
|
85
92
|
|
86
93
|
def render_airbrake_response
|
87
94
|
if @issue.present?
|
95
|
+
airbrake_id_value = CustomValue.find_by(
|
96
|
+
customized_type: Issue.name,
|
97
|
+
customized_id: @issue.id,
|
98
|
+
custom_field_id: notice_hash_field.id
|
99
|
+
)
|
100
|
+
|
88
101
|
render json: {
|
89
|
-
id:
|
102
|
+
id: airbrake_id_value&.value,
|
90
103
|
url: issue_url(@issue)
|
91
104
|
}, status: :created
|
92
105
|
else
|
@@ -94,7 +107,7 @@ class AirbrakeController < ::ApplicationController
|
|
94
107
|
end
|
95
108
|
end
|
96
109
|
|
97
|
-
def record_for(on, key, fields = [
|
110
|
+
def record_for(on, key, fields = %i[id name])
|
98
111
|
fields.each do |field|
|
99
112
|
val = on.find_by(field => @key[key])
|
100
113
|
return val if val.present?
|
@@ -114,7 +127,8 @@ class AirbrakeController < ::ApplicationController
|
|
114
127
|
end
|
115
128
|
|
116
129
|
def custom_field(key)
|
117
|
-
@project.issue_custom_fields.find_by(id: global_setting(key)) ||
|
130
|
+
@project.issue_custom_fields.find_by(id: global_setting(key)) ||
|
131
|
+
CustomField.find_by(id: global_setting(key), is_for_all: true)
|
118
132
|
end
|
119
133
|
|
120
134
|
def notice_hash_field
|
@@ -124,123 +138,4 @@ class AirbrakeController < ::ApplicationController
|
|
124
138
|
def occurrences_field
|
125
139
|
custom_field(:occurrences_field)
|
126
140
|
end
|
127
|
-
|
128
|
-
def create_issue!
|
129
|
-
return if @notice.blank?
|
130
|
-
return if @notice.errors.blank?
|
131
|
-
|
132
|
-
@issue = find_or_initialize_issue(@notice)
|
133
|
-
|
134
|
-
set_issue_custom_field_values(@issue, @notice)
|
135
|
-
|
136
|
-
reopen_issue(@issue, @notice) if @issue.persisted? && @issue.status.is_closed? && reopen_issue?(@notice)
|
137
|
-
|
138
|
-
@issue = nil unless @issue.save
|
139
|
-
end
|
140
|
-
|
141
|
-
# Load or initialize issue by project, tracker and airbrake hash
|
142
|
-
def find_or_initialize_issue(notice)
|
143
|
-
issue_ids = CustomValue.where(customized_type: Issue.name, custom_field_id: notice_hash_field.id, value: notice.id).pluck(:customized_id)
|
144
|
-
|
145
|
-
issue = Issue.find_by(id: issue_ids, project_id: @project.id, tracker_id: @tracker.id)
|
146
|
-
|
147
|
-
return issue if issue.present?
|
148
|
-
|
149
|
-
initialize_issue(notice)
|
150
|
-
end
|
151
|
-
|
152
|
-
def initialize_issue(notice)
|
153
|
-
issue = Issue.new(
|
154
|
-
subject: notice.subject,
|
155
|
-
project: @project,
|
156
|
-
tracker: @tracker,
|
157
|
-
author: User.current,
|
158
|
-
category: @category,
|
159
|
-
priority: @priority,
|
160
|
-
description: render_description(notice),
|
161
|
-
assigned_to: @assignee
|
162
|
-
)
|
163
|
-
|
164
|
-
add_attachments_to_issue(issue, notice)
|
165
|
-
|
166
|
-
issue
|
167
|
-
end
|
168
|
-
|
169
|
-
def set_issue_custom_field_values(issue, notice)
|
170
|
-
custom_field_values = {}
|
171
|
-
|
172
|
-
# Error ID
|
173
|
-
custom_field_values[notice_hash_field.id] = notice.id if issue.new_record?
|
174
|
-
|
175
|
-
# Update occurrences
|
176
|
-
if occurrences_field.present?
|
177
|
-
occurrences_value = issue.custom_value_for(occurrences_field.id)
|
178
|
-
custom_field_values[occurrences_field.id] = ((occurrences_value ? occurrences_value.value.to_i : 0) + 1).to_s
|
179
|
-
end
|
180
|
-
|
181
|
-
issue.custom_field_values = custom_field_values
|
182
|
-
end
|
183
|
-
|
184
|
-
def add_attachments_to_issue(issue, notice)
|
185
|
-
return if notice.attachments.blank?
|
186
|
-
|
187
|
-
@tempfiles ||= []
|
188
|
-
|
189
|
-
notice.attachments.each do |data|
|
190
|
-
filename = data[:filename].presence || Redmine::Utils.random_hex(16)
|
191
|
-
|
192
|
-
file = Tempfile.new(filename)
|
193
|
-
@tempfiles << file
|
194
|
-
|
195
|
-
file.write(data[:data])
|
196
|
-
file.rewind
|
197
|
-
|
198
|
-
issue.attachments << Attachment.new(file: file, author: User.current, filename: filename)
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
def reopen_issue?(notice)
|
203
|
-
reopen_regexp = project_setting(:reopen_regexp)
|
204
|
-
|
205
|
-
return false if reopen_regexp.blank?
|
206
|
-
return false if notice.environment_name.blank?
|
207
|
-
|
208
|
-
!!(notice.environment_name =~ /#{reopen_regexp}/i)
|
209
|
-
end
|
210
|
-
|
211
|
-
def issue_reopen_repeat_description?
|
212
|
-
!!project_setting(:reopen_repeat_description)
|
213
|
-
end
|
214
|
-
|
215
|
-
def reopen_issue(issue, notice)
|
216
|
-
return if notice.environment_name.blank?
|
217
|
-
|
218
|
-
desc = "*Issue reopened after occurring again in _#{notice.environment_name}_ environment.*"
|
219
|
-
desc << "\n\n#{render_description(notice)}" if issue_reopen_repeat_description?
|
220
|
-
|
221
|
-
issue.status = issue.tracker.default_status
|
222
|
-
|
223
|
-
issue.init_journal(User.current, desc)
|
224
|
-
|
225
|
-
add_attachments_to_issue(issue, notice)
|
226
|
-
end
|
227
|
-
|
228
|
-
def render_description(notice)
|
229
|
-
locals = { notice: notice }
|
230
|
-
|
231
|
-
if template_exists?("airbrake/issue_description/#{notice.type}")
|
232
|
-
render_to_string("airbrake/issue_description/#{notice.type}", layout: false, locals: locals)
|
233
|
-
else
|
234
|
-
render_to_string('airbrake/issue_description/default', layout: false, locals: locals)
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
def cleanup_tempfiles
|
239
|
-
return if @tempfiles.blank?
|
240
|
-
|
241
|
-
@tempfiles.each do |tempfile|
|
242
|
-
tempfile.close rescue nil
|
243
|
-
tempfile.unlink rescue nil
|
244
|
-
end
|
245
|
-
end
|
246
141
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Controller for airbrake notices
|
2
4
|
class AirbrakeNoticeController < ::AirbrakeController
|
3
5
|
accept_api_auth :notices
|
@@ -15,11 +17,11 @@ class AirbrakeNoticeController < ::AirbrakeController
|
|
15
17
|
|
16
18
|
def parse_notice
|
17
19
|
@notice = RedmineAirbrakeBackend::Notice.new(
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
errors: params[:errors].map { |e| RedmineAirbrakeBackend::Error.new(e) },
|
21
|
+
params: params[:params],
|
22
|
+
session: params[:session],
|
23
|
+
context: params[:context],
|
24
|
+
environment: params[:environment]
|
25
|
+
)
|
24
26
|
end
|
25
27
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Controller for project-specific airbrake settings
|
2
4
|
class AirbrakeProjectSettingsController < ::ApplicationController
|
3
5
|
before_filter :find_project
|
@@ -6,9 +8,7 @@ class AirbrakeProjectSettingsController < ::ApplicationController
|
|
6
8
|
menu_item :settings
|
7
9
|
|
8
10
|
def update
|
9
|
-
if @airbrake_project_setting.update(airbrake_project_setting_params)
|
10
|
-
flash[:notice] = l(:notice_successful_update)
|
11
|
-
end
|
11
|
+
flash[:notice] = l(:notice_successful_update) if @airbrake_project_setting.update(airbrake_project_setting_params)
|
12
12
|
|
13
13
|
redirect_to settings_project_path(@project, tab: 'airbrake')
|
14
14
|
end
|
@@ -25,6 +25,8 @@ class AirbrakeProjectSettingsController < ::ApplicationController
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def airbrake_project_setting_params
|
28
|
-
params
|
28
|
+
params
|
29
|
+
.require(:airbrake_project_setting)
|
30
|
+
.permit(:tracker_id, :category_id, :priority_id, :reopen_regexp, :reopen_repeat_description)
|
29
31
|
end
|
30
32
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
# Handling for creating / updating issues
|
6
|
+
module AirbrakeAttachments
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
after_action :cleanup_tempfiles
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def add_attachment_to_issue(issue, data)
|
16
|
+
filename = data[:filename].presence || Redmine::Utils.random_hex(16)
|
17
|
+
|
18
|
+
file = Tempfile.new(filename)
|
19
|
+
|
20
|
+
@tempfiles << file
|
21
|
+
|
22
|
+
file.write(data[:data])
|
23
|
+
file.rewind
|
24
|
+
|
25
|
+
issue.attachments << Attachment.new(file: file, author: User.current, filename: filename)
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_attachments_to_issue(issue, notice)
|
29
|
+
return if notice.attachments.blank?
|
30
|
+
|
31
|
+
@tempfiles ||= []
|
32
|
+
|
33
|
+
notice.attachments.each do |data|
|
34
|
+
add_attachment_to_issue(issue, data)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def close_tempfile(tempfile)
|
39
|
+
tempfile.close
|
40
|
+
rescue StandardError
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def unlink_tempfile(tempfile)
|
45
|
+
tempfile.unlink
|
46
|
+
rescue StandardError
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def cleanup_tempfiles
|
51
|
+
Array(@tempfiles).compact.each do |tempfile|
|
52
|
+
close_tempfile(tempfile)
|
53
|
+
unlink_tempfile(tempfile)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|