redmine_airbrake_backend 0.4.3 → 0.5.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: 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