redmine_airbrake_backend 1.2.1 → 1.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Gem Version](https://img.shields.io/gem/v/redmine_airbrake_backend.svg)](https://rubygems.org/gems/redmine_airbrake_backend)
|
2
|
-
[![Dependencies](https://img.shields.io/gemnasium/ydkn/redmine_airbrake_backend.svg)](https://gemnasium.com/ydkn/redmine_airbrake_backend)
|
3
|
-
[![Code Climate](https://img.shields.io/codeclimate/github/ydkn/redmine_airbrake_backend.svg)](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
|