redmine_airbrake_backend 0.4.3 → 0.5.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 02aaf16472df5dc61443a9bc2ee67c615f8ad9d3
4
- data.tar.gz: a90521ed091002dced77aa89e314dc087341d652
3
+ metadata.gz: fc1393573d1439588f21f8995af1bc85b4d8661e
4
+ data.tar.gz: eafd09a81fccf63b1b7af678fb59acf66d86401d
5
5
  SHA512:
6
- metadata.gz: 677eb39c75a8af444887c31f2fa35ae566fd1ff4d14b4116f841ccb017e294905d309fa28731cb7728847071d467b71be7d502d0a32dddd8dcbe6ac8a1ef324f
7
- data.tar.gz: b905d8b8722ebc99be7a0539ace444c4d6ce6be46fc84659664fa45d43d6b4c39c22117147f0310d52b3081ca7848135daf16f3fd13946ccc081436c31df5f70
6
+ metadata.gz: f1e451b7341129c911d65f16fe679980d5d5efd4a3c9d2b70b771ca4e13b3ff0f69b6a873ed8e69bd34d28758e5cb75ec501e292b6ae0405dfcec3b4d9e6da74
7
+ data.tar.gz: 97012b7e3cc150080116ea79121de9cc84602ab82ea125c345f3530ab69f0a9ba869298defabe076c0b31acd048347359c07bb25c6043c52eb0d1676dbd34917
data/Gemfile.lock ADDED
@@ -0,0 +1,91 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ redmine_airbrake_backend (0.5.0)
5
+ hpricot
6
+ htmlentities
7
+ rails
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ actionmailer (4.1.6)
13
+ actionpack (= 4.1.6)
14
+ actionview (= 4.1.6)
15
+ mail (~> 2.5, >= 2.5.4)
16
+ actionpack (4.1.6)
17
+ actionview (= 4.1.6)
18
+ activesupport (= 4.1.6)
19
+ rack (~> 1.5.2)
20
+ rack-test (~> 0.6.2)
21
+ actionview (4.1.6)
22
+ activesupport (= 4.1.6)
23
+ builder (~> 3.1)
24
+ erubis (~> 2.7.0)
25
+ activemodel (4.1.6)
26
+ activesupport (= 4.1.6)
27
+ builder (~> 3.1)
28
+ activerecord (4.1.6)
29
+ activemodel (= 4.1.6)
30
+ activesupport (= 4.1.6)
31
+ arel (~> 5.0.0)
32
+ activesupport (4.1.6)
33
+ i18n (~> 0.6, >= 0.6.9)
34
+ json (~> 1.7, >= 1.7.7)
35
+ minitest (~> 5.1)
36
+ thread_safe (~> 0.1)
37
+ tzinfo (~> 1.1)
38
+ arel (5.0.1.20140414130214)
39
+ builder (3.2.2)
40
+ erubis (2.7.0)
41
+ hike (1.2.3)
42
+ hpricot (0.8.6)
43
+ htmlentities (4.3.2)
44
+ i18n (0.6.11)
45
+ json (1.8.1)
46
+ mail (2.6.1)
47
+ mime-types (>= 1.16, < 3)
48
+ mime-types (2.4.2)
49
+ minitest (5.4.2)
50
+ multi_json (1.10.1)
51
+ rack (1.5.2)
52
+ rack-test (0.6.2)
53
+ rack (>= 1.0)
54
+ rails (4.1.6)
55
+ actionmailer (= 4.1.6)
56
+ actionpack (= 4.1.6)
57
+ actionview (= 4.1.6)
58
+ activemodel (= 4.1.6)
59
+ activerecord (= 4.1.6)
60
+ activesupport (= 4.1.6)
61
+ bundler (>= 1.3.0, < 2.0)
62
+ railties (= 4.1.6)
63
+ sprockets-rails (~> 2.0)
64
+ railties (4.1.6)
65
+ actionpack (= 4.1.6)
66
+ activesupport (= 4.1.6)
67
+ rake (>= 0.8.7)
68
+ thor (>= 0.18.1, < 2.0)
69
+ rake (10.3.2)
70
+ sprockets (2.12.2)
71
+ hike (~> 1.2)
72
+ multi_json (~> 1.0)
73
+ rack (~> 1.0)
74
+ tilt (~> 1.1, != 1.3.0)
75
+ sprockets-rails (2.2.0)
76
+ actionpack (>= 3.0)
77
+ activesupport (>= 3.0)
78
+ sprockets (>= 2.8, < 4.0)
79
+ thor (0.19.1)
80
+ thread_safe (0.3.4)
81
+ tilt (1.4.1)
82
+ tzinfo (1.2.2)
83
+ thread_safe (~> 0.1)
84
+
85
+ PLATFORMS
86
+ ruby
87
+
88
+ DEPENDENCIES
89
+ bundler (~> 1.3)
90
+ rake
91
+ redmine_airbrake_backend!
data/Rakefile CHANGED
@@ -4,7 +4,10 @@ rescue LoadError
4
4
  puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
5
  end
6
6
 
7
+ Bundler::GemHelper.install_tasks
8
+
7
9
  require 'rdoc/task'
10
+ require 'rake/testtask'
8
11
 
9
12
  RDoc::Task.new(:rdoc) do |rdoc|
10
13
  rdoc.rdoc_dir = 'rdoc'
@@ -14,14 +17,6 @@ RDoc::Task.new(:rdoc) do |rdoc|
14
17
  rdoc.rdoc_files.include('lib/**/*.rb')
15
18
  end
16
19
 
17
- #APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
- #load 'rails/tasks/engine.rake'
19
-
20
-
21
- Bundler::GemHelper.install_tasks
22
-
23
- require 'rake/testtask'
24
-
25
20
  Rake::TestTask.new(:test) do |t|
26
21
  t.libs << 'lib'
27
22
  t.libs << 'test'
@@ -1,171 +1,151 @@
1
- require 'redmine_airbrake_backend/notice'
1
+ require 'tempfile'
2
+ require 'redmine_airbrake_backend/request/xml'
3
+ require 'redmine_airbrake_backend/request/json'
2
4
 
5
+ # Controller with airbrake related stuff
3
6
  class AirbrakeController < ::ApplicationController
4
- protect_from_forgery except: :notice
5
- prepend_before_filter :parse_notice_and_api_auth
6
- before_filter :load_records
7
-
8
- accept_api_auth :notice
9
-
10
- def notice
11
- return unless authorize(:issues, :create)
12
-
13
- # Issue by project, tracker and hash
14
- issue_ids = CustomValue.where(customized_type: Issue.name, custom_field_id: notice_hash_field.id, value: notice_hash).select([:customized_id]).collect{|cv| cv.customized_id}
15
- @issue = Issue.where(id: issue_ids, project_id: @project.id, tracker_id: @tracker.id).first
16
- @issue = Issue.new(
17
- subject: subject,
18
- project: @project,
19
- tracker: @tracker,
20
- author: User.current,
21
- category: @category,
22
- priority: @priority,
23
- description: render_description,
24
- assigned_to: @assignee
25
- ) unless @issue
7
+ skip_before_filter :verify_authenticity_token
26
8
 
27
- custom_field_values = {}
9
+ before_filter :authorize_airbrake
10
+ before_filter :handle_request
28
11
 
29
- # Error hash
30
- custom_field_values[notice_hash_field.id] = notice_hash if @issue.new_record?
12
+ after_filter :cleanup_tempfiles
31
13
 
32
- # Update occurrences
33
- if occurrences_field.present?
34
- occurrences_value = @issue.custom_value_for(occurrences_field.id)
35
- custom_field_values[occurrences_field.id] = ((occurrences_value ? occurrences_value.value.to_i : 0) + 1).to_s
36
- end
14
+ rescue_from RedmineAirbrakeBackend::Request::Error, with: :render_bad_request
37
15
 
38
- @issue.custom_field_values = custom_field_values
16
+ private
39
17
 
40
- # Reopen if closed
41
- if reopen? && @issue.status.is_closed?
42
- desc = "*Issue reopened after occurring again in _#{@notice.env[:environment_name]}_ environment.*"
43
- desc << "\n\n#{render_description}" if project_setting(:reopen_repeat_description)
18
+ def authorize_airbrake
19
+ @project = @request.project
44
20
 
45
- @issue.status = IssueStatus.where(is_default: true).order(:position).first
46
- @issue.init_journal(User.current, desc)
47
- end
21
+ authorize(:issues, :create)
22
+ end
48
23
 
49
- if @issue.save
50
- render xml: {
51
- notice: {
52
- id: notice_hash,
53
- url: issue_url(@issue)
54
- }
55
- }
56
- else
57
- render nothing: true, status: :internal_server_error
58
- end
24
+ def render_bad_request(error)
25
+ render text: error.message, status: :bad_request
59
26
  end
60
27
 
61
- private
28
+ def parse_xml_request
29
+ @request = RedmineAirbrakeBackend::Request::XML.parse(request.body)
62
30
 
63
- def parse_notice_and_api_auth
64
- @notice = RedmineAirbrakeBackend::Notice.parse(request.body)
65
- params[:key] = @notice.params[:api_key]
66
- rescue RedmineAirbrakeBackend::Notice::NoticeInvalid, RedmineAirbrakeBackend::Notice::UnsupportedVersion
67
- render nothing: true, status: :bad_request
31
+ params[:key] = @request.api_key
68
32
  end
69
33
 
70
- def load_records
71
- # Project
72
- unless @project = Project.where(identifier: @notice.params[:project]).first
73
- render text: 'Project not found!', status: :bad_request
74
- return
75
- end
34
+ def parse_json_request
35
+ @request = RedmineAirbrakeBackend::Request::JSON.parse(params)
76
36
 
77
- # Check configuration
78
- if notice_hash_field.blank?
79
- render text: 'Custom field for notice hash is not configured!', status: :internal_server_error
80
- return
81
- end
37
+ params[:key] = @request.api_key
38
+ end
82
39
 
83
- # Tracker
84
- unless (@tracker = record_for(@project.trackers, :tracker)) && @tracker.custom_fields.where(id: notice_hash_field.id).first
85
- render text: 'Tracker not found!', status: :bad_request
86
- return
87
- end
40
+ def handle_request
41
+ @tempfiles = []
88
42
 
89
- # Category
90
- @category = record_for(@project.issue_categories, :category)
43
+ @results = []
91
44
 
92
- # Priority
93
- @priority = record_for(IssuePriority, :priority) || IssuePriority.default
45
+ @request.errors.each do |error|
46
+ issue = find_or_initialize_issue(error)
94
47
 
95
- # Assignee
96
- @assignee = record_for(@project.users, :assignee, [:id, :login])
97
- end
48
+ set_custom_field_values(issue, @request, error.airbrake_hash)
98
49
 
99
- def record_for(on, param_key, fields=[:id, :name])
100
- fields.each do |field|
101
- val = on.where(field => @notice.params[param_key]).first
102
- return val if val.present?
103
- end
50
+ reopen_issue(issue, error) if @request.reopen? && issue.status.is_closed?
104
51
 
105
- project_setting(param_key)
52
+ if issue.save
53
+ @results << {
54
+ issue: issue,
55
+ hash: error.airbrake_hash
56
+ }
57
+ end
58
+ end
106
59
  end
107
60
 
108
- def project_setting(key)
109
- return nil if @project.airbrake_settings.blank?
110
- @project.airbrake_settings.send(key) if @project.airbrake_settings.respond_to?(key)
61
+ # Load or initialize issue by project, tracker and airbrake hash
62
+ def find_or_initialize_issue(error)
63
+ issue_ids = CustomValue.where(customized_type: Issue.name, custom_field_id: @request.notice_hash_field.id, value: error.airbrake_hash).pluck(:customized_id)
64
+
65
+ issue = Issue.where(id: issue_ids, project_id: @request.project.id, tracker_id: @request.tracker.id).first
66
+
67
+ return issue if issue.present?
68
+
69
+ issue = Issue.new(
70
+ subject: error.subject,
71
+ project: @request.project,
72
+ tracker: @request.tracker,
73
+ author: User.current,
74
+ category: @request.category,
75
+ priority: @request.priority,
76
+ description: render_description(error),
77
+ assigned_to: @request.assignee
78
+ )
79
+
80
+ add_error_attachments_to_issue(issue, error)
81
+
82
+ issue
111
83
  end
112
84
 
113
- def subject
114
- s = ''
115
- if @notice.error[:class].blank? || @notice.error[:message].starts_with?("#{@notice.error[:class]}:")
116
- s = "[#{notice_hash[0..7]}] #{@notice.error[:message]}"
117
- else
118
- s = "[#{notice_hash[0..7]}] #{@notice.error[:class]} #{@notice.error[:message]}"
85
+ def set_custom_field_values(issue, request, airbrake_hash)
86
+ custom_field_values = {}
87
+
88
+ # Error hash
89
+ custom_field_values[request.notice_hash_field.id] = airbrake_hash if issue.new_record?
90
+
91
+ # Update occurrences
92
+ if request.occurrences_field.present?
93
+ occurrences_value = issue.custom_value_for(request.occurrences_field.id)
94
+ custom_field_values[request.occurrences_field.id] = ((occurrences_value ? occurrences_value.value.to_i : 0) + 1).to_s
119
95
  end
120
- s[0..254].strip
96
+
97
+ issue.custom_field_values = custom_field_values
121
98
  end
122
99
 
123
- def notice_hash
124
- h = []
125
- h << @notice.error[:class]
126
- h << @notice.error[:message]
127
- h += normalized_backtrace
100
+ def add_error_attachments_to_issue(issue, error)
101
+ return if error.attachments.blank?
128
102
 
129
- Digest::MD5.hexdigest(h.compact.join("\n"))
130
- end
103
+ error.attachments.each do |attachment_data|
104
+ filename = attachment_data[:filename].presence || Redmine::Utils.random_hex(16)
131
105
 
132
- def normalized_backtrace
133
- if @notice.error.present? && @notice.error[:backtrace].present?
134
- @notice.error[:backtrace].collect do |e|
135
- "#{e[:file]}|#{e[:method].gsub(/_\d+_/, '')}|#{e[:number]}" rescue nil
136
- end.compact
137
- else
138
- []
106
+ file = Tempfile.new(filename)
107
+ @tempfiles << file
108
+
109
+ file.write(attachment_data[:data])
110
+ file.rewind
111
+
112
+ attachment = Attachment.new(file: file)
113
+ attachment.author = User.current
114
+ attachment.filename = filename
115
+
116
+ issue.attachments << attachment
139
117
  end
140
118
  end
141
119
 
142
- def notice_hash_field
143
- custom_field(:hash_field)
144
- end
120
+ def reopen_issue(issue, error)
121
+ return if @request.environment_name.blank?
145
122
 
146
- def occurrences_field
147
- custom_field(:occurrences_field)
148
- end
123
+ desc = "*Issue reopened after occurring again in _#{@request.environment_name}_ environment.*"
124
+ desc << "\n\n#{render_description(error)}" if @request.reopen_repeat_description?
149
125
 
150
- def custom_field(key)
151
- @project.issue_custom_fields.where(id: setting(key)).first || CustomField.where(id: setting(key), is_for_all: true).first
152
- end
126
+ issue.status = IssueStatus.where(is_default: true).order(:position).first
153
127
 
154
- def reopen?
155
- return false if @notice.env.blank? || @notice.env[:environment_name].blank? || project_setting(:reopen_regexp).blank?
156
- !!(@notice.env[:environment_name] =~ /#{project_setting(:reopen_regexp)}/i)
157
- end
128
+ issue.init_journal(User.current, desc)
158
129
 
159
- def setting(key)
160
- Setting.plugin_redmine_airbrake_backend[key]
130
+ add_error_attachments_to_issue(issue, error)
161
131
  end
162
132
 
163
- def render_description
164
- if template_exists?("issue_description_#{@notice.params[:type]}", 'airbrake', true)
165
- render_to_string(partial: "issue_description_#{@notice.params[:type]}")
133
+ def render_description(error)
134
+ locals = { request: @request, error: error }
135
+
136
+ if template_exists?("airbrake/issue_description/#{@request.type}")
137
+ render_to_string("airbrake/issue_description/#{@request.type}", layout: false, locals: locals)
166
138
  else
167
- render_to_string(partial: 'issue_description')
139
+ render_to_string('airbrake/issue_description/default', layout: false, locals: locals)
168
140
  end
169
141
  end
170
142
 
143
+ def cleanup_tempfiles
144
+ return if @tempfiles.blank?
145
+
146
+ @tempfiles.each do |tempfile|
147
+ tempfile.close rescue nil
148
+ tempfile.unlink rescue nil
149
+ end
150
+ end
171
151
  end
@@ -0,0 +1,25 @@
1
+ # Controller for airbrake notices
2
+ class AirbrakeNoticeController < ::AirbrakeController
3
+ prepend_before_filter :parse_xml_request, only: [:notice_xml]
4
+ prepend_before_filter :parse_json_request, only: [:notice_json]
5
+
6
+ accept_api_auth :notice_xml, :notice_json
7
+
8
+ # Handle airbrake XML notices
9
+ def notice_xml
10
+ render xml: {
11
+ notice: {
12
+ id: (@results.first[:hash] rescue nil)
13
+ }
14
+ }
15
+ end
16
+
17
+ # Handle airbrake JSON notices
18
+ def notice_json
19
+ render json: {
20
+ notice: {
21
+ id: (@results.first[:hash] rescue nil)
22
+ }
23
+ }
24
+ end
25
+ end
@@ -1,3 +1,4 @@
1
+ # Controller for project-specific airbrake settings
1
2
  class AirbrakeProjectSettingsController < ::ApplicationController
2
3
  before_filter :find_project
3
4
  before_filter :find_airbrake_setting
@@ -7,10 +8,11 @@ class AirbrakeProjectSettingsController < ::ApplicationController
7
8
  def update
8
9
  @airbrake_project_setting.safe_attributes = params[:airbrake_project_setting]
9
10
 
10
- @airbrake_project_setting.save
11
+ if @airbrake_project_setting.save
12
+ flash[:notice] = l(:notice_successful_update)
13
+ end
11
14
 
12
- flash[:notice] = l(:notice_successful_update)
13
- redirect_to settings_project_path(@project, :tab => 'airbrake')
15
+ redirect_to settings_project_path(@project, tab: 'airbrake')
14
16
  end
15
17
 
16
18
  private
@@ -22,5 +24,4 @@ class AirbrakeProjectSettingsController < ::ApplicationController
22
24
  def find_airbrake_setting
23
25
  @airbrake_project_setting = @project.airbrake_settings || AirbrakeProjectSetting.new(project: @project)
24
26
  end
25
-
26
27
  end