redmine_airbrake_backend 0.1.0 → 0.2.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: 7d8a9f1faf769ff932d95c7c834ee697dac4c686
4
- data.tar.gz: 1b6e0a26f4e5587afa7be9fa38bedf892e79c784
3
+ metadata.gz: 0c4df3337dec81bfdd744a3ca6a7b741b29ceb59
4
+ data.tar.gz: 2e43509ade9e2934870111dbd47e8fe2fa5baf58
5
5
  SHA512:
6
- metadata.gz: 1abf3729e9b38f7d0eb6d195d642632513ba8457cd84b0aa710d10d62725be77af8c25fd272341901033c62d5d5acbf5e8923da33d3ce6862f5c06de744ead16
7
- data.tar.gz: 0ced6e811c22c9e5ed5587dad4c359f120edb1df50745c94916da5495fe02cb6acba2353ecb86d536d2fca50024528bf723e5567676fd92aed659ae678f12627
6
+ metadata.gz: 79d9a308477cf2ee7b1df7c825a9ecde9e10d34278d9656e3c221f6598424f5ae855cbcb6a023c10a6d2fbbf6401b3ee97bb2684b4004c8cf969e7a6f2cdd4c5
7
+ data.tar.gz: ed5d5aa24babd1201e680fdb5c761ae7e6cc1050e6bb15e6cb189e8a1790b58df9aa447a9ba23e98cf759b45fe258521478711d1b8c870a6fe78cb0d4ccc77e0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- redmine_airbrake_backend (0.1.0)
4
+ redmine_airbrake_backend (0.2.0)
5
5
  hpricot
6
6
  htmlentities
7
7
  rails
data/README.md CHANGED
@@ -46,7 +46,7 @@ end
46
46
 
47
47
  ## Notes
48
48
 
49
- Based and inspired by https://github.com/milgner/redmine_airbrake_server
49
+ Based on https://github.com/milgner/redmine_airbrake_server
50
50
 
51
51
  ## Code Status
52
52
 
@@ -1,24 +1,14 @@
1
- require 'json'
2
- require 'hpricot'
3
- require 'htmlentities'
1
+ require 'redmine_airbrake_backend/notice'
4
2
 
5
3
  class AirbrakeController < ::ApplicationController
6
- SUPPORTED_API_VERSIONS = %w(2.4)
7
-
8
- prepend_before_filter :set_api_auth
9
- before_filter :parse_notice
10
- before_filter :init_vars
4
+ prepend_before_filter :parse_notice_and_api_auth
5
+ before_filter :load_records
11
6
 
12
7
  accept_api_auth :notice
13
8
 
14
9
  def notice
15
10
  return unless authorize(:issues, :create)
16
11
 
17
- raise ArgumentError.new("Airbrake version not supported") unless SUPPORTED_API_VERSIONS.include?(@notice[:version])
18
- raise ArgumentError.new("Project not found") unless @project
19
- raise ArgumentError.new("Tracker not found") unless @tracker
20
- raise ArgumentError.new("Tracker does not have a notice hash custom field") unless @tracker.custom_fields.where(id: notice_hash_field.id).first
21
-
22
12
  # Issue by project, tracker and hash
23
13
  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}
24
14
  @issue = Issue.where(id: issue_ids, project_id: @project.id, tracker_id: @tracker.id).first
@@ -29,7 +19,7 @@ class AirbrakeController < ::ApplicationController
29
19
  author: User.current,
30
20
  category: @category,
31
21
  priority: @priority,
32
- description: render_to_string(partial: 'issue_description'),
22
+ description: render_description,
33
23
  assigned_to: @assignee
34
24
  ) unless @issue
35
25
 
@@ -42,7 +32,7 @@ class AirbrakeController < ::ApplicationController
42
32
  # Reopen if closed
43
33
  if reopen? && @issue.status.is_closed?
44
34
  @issue.status = IssueStatus.where(is_default: true).order(:position).first
45
- @issue.init_journal(User.current, "Issue reopened after occurring again in environment #{@notice[:server_environment][:environment_name]}")
35
+ @issue.init_journal(User.current, "Issue reopened after occurring again in environment #{@notice.env[:environment_name]}")
46
36
  end
47
37
 
48
38
  # Hash
@@ -64,41 +54,73 @@ class AirbrakeController < ::ApplicationController
64
54
 
65
55
  private
66
56
 
67
- def set_api_auth
68
- params[:key] = redmine_params[:api_key] rescue nil
57
+ def parse_notice_and_api_auth
58
+ @notice = RedmineAirbrakeBackend::Notice.parse(request.body)
59
+ params[:key] = @notice.params[:api_key]
60
+ rescue RedmineAirbrakeBackend::Notice::NoticeInvalid, RedmineAirbrakeBackend::Notice::UnsupportedVersion
61
+ render nothing: true, status: :bad_request
69
62
  end
70
63
 
71
- def redmine_params
72
- JSON.parse(params[:notice][:api_key]).symbolize_keys
64
+ def load_records
65
+ # Project
66
+ unless @project = Project.where(identifier: @notice.params[:project]).first
67
+ render nothing: true, status: :bad_request
68
+ return
69
+ end
70
+
71
+ # Tracker
72
+ unless (@tracker = record_for(@project.trackers, :tracker)) && @tracker.custom_fields.where(id: notice_hash_field.id).first
73
+ render nothing: true, status: :bad_request
74
+ return
75
+ end
76
+
77
+ # Category
78
+ @category = record_for(@project.issue_categories, :category)
79
+
80
+ # Priority
81
+ @priority = record_for(IssuePriority, :priority) || IssuePriority.default
82
+
83
+ # Assignee
84
+ @assignee = record_for(@project.users, :assignee, [:id, :login])
73
85
  end
74
- helper_method :redmine_params
75
86
 
76
- def subject
77
- if @notice[:error][:message].starts_with?("#{@notice[:error][:class]}:")
78
- "[#{notice_hash[0..7]}] #{@notice[:error][:message]}"[0..254]
79
- else
80
- "[#{notice_hash[0..7]}] #{@notice[:error][:class]} #{@notice[:error][:message]}"[0..254]
87
+ def record_for(on, param_key, fields=[:id, :name])
88
+ fields.each do |field|
89
+ val = on.where(field => @notice.params[param_key]).first
90
+ return val if val.present?
81
91
  end
92
+
93
+ project_setting(param_key)
82
94
  end
83
95
 
84
- def backtrace
85
- if @notice[:error][:backtrace][:line].is_a?(Hash)
86
- [@notice[:error][:backtrace][:line]]
96
+ def project_setting(key)
97
+ return nil if @project.airbrake_settings.blank?
98
+ @project.airbrake_settings.send(key) if @project.airbrake_settings.respond_to?(key)
99
+ end
100
+
101
+ def subject
102
+ if @notice.error[:message].starts_with?("#{@notice.error[:class]}:")
103
+ "[#{notice_hash[0..7]}] #{@notice.error[:message]}"[0..254]
87
104
  else
88
- @notice[:error][:backtrace][:line]
105
+ "[#{notice_hash[0..7]}] #{@notice.error[:class]} #{@notice.error[:message]}"[0..254]
89
106
  end
90
107
  end
91
- helper_method :backtrace
92
108
 
93
109
  def notice_hash
94
110
  h = []
95
- h << @notice[:error][:class]
96
- h << @notice[:error][:message]
97
- h += backtrace.collect{|element| "#{element[:file]}|#{element[:method].gsub(/_\d+_/, '')}|#{element[:number]}"}
111
+ h << @notice.error[:class]
112
+ h << @notice.error[:message]
113
+ h += normalized_backtrace if @notice.error[:backtrace].present?
98
114
 
99
115
  Digest::MD5.hexdigest(h.compact.join("\n"))
100
116
  end
101
117
 
118
+ def normalized_backtrace
119
+ @notice.error[:backtrace].collect do |e|
120
+ "#{e[:file]}|#{e[:method].gsub(/_\d+_/, '')}|#{e[:number]}"
121
+ end
122
+ end
123
+
102
124
  def notice_hash_field
103
125
  custom_field(:hash_field)
104
126
  end
@@ -112,71 +134,20 @@ class AirbrakeController < ::ApplicationController
112
134
  end
113
135
 
114
136
  def reopen?
115
- return false if project_setting(:reopen_regexp).blank?
116
- !!(@notice[:server_environment][:environment_name] =~ /#{project_setting(:reopen_regexp)}/i)
137
+ return false if @notice.env.blank? || @notice.env[:environment_name].blank? || project_setting(:reopen_regexp).blank?
138
+ !!(@notice.env[:environment_name] =~ /#{project_setting(:reopen_regexp)}/i)
117
139
  end
118
140
 
119
141
  def setting(key)
120
142
  Setting.plugin_redmine_airbrake_backend[key]
121
143
  end
122
144
 
123
- def project_setting(key)
124
- return nil if @project.airbrake_settings.blank?
125
- @project.airbrake_settings.send(key) if @project.airbrake_settings.respond_to?(key)
126
- end
127
-
128
- def parse_notice
129
- @notice = params[:notice]
130
-
131
- return @notice if @notice[:request].blank?
132
-
133
- doc = Hpricot::XML(request.body)
134
-
135
- convert_request_vars(:params, :params)
136
- convert_request_vars(:cgi_data, :'cgi-data')
137
- convert_request_vars(:session, :session)
138
-
139
- unless @notice[:request][:params].blank?
140
- @notice[:request][:params].delete(:action) # already known
141
- @notice[:request][:params].delete(:controller) # already known
142
- end
143
- rescue
144
- @notice = nil
145
- render nothing: true, status: :bad_request
146
- end
147
-
148
- def convert_request_vars(type, pathname)
149
- unless @notice[:request][type.to_sym].blank?
150
- vars = convert_var_elements(doc/"/notice/request/#{pathname}/var")
151
- @notice[:request][type.to_sym] = vars
152
- end
153
- end
154
-
155
- def convert_var_elements(elements)
156
- result = {}
157
- elements.each do |elem|
158
- result[elem.attributes['key']] = elem.inner_text
145
+ def render_description
146
+ if template_exists?("issue_description_#{@notice.params[:type]}", 'airbrake', true)
147
+ render_to_string(partial: "issue_description_#{@notice.params[:type]}")
148
+ else
149
+ render_to_string(partial: 'issue_description')
159
150
  end
160
- result.delete_if{|k,v| k.strip.blank?}
161
- result.symbolize_keys
162
- end
163
-
164
- def init_vars
165
- # Project
166
- @project = Project.where(identifier: redmine_params[:project]).first
167
- return unless @project
168
-
169
- # Tracker
170
- @tracker = @project.trackers.where(id: redmine_params[:tracker]).first || @project.trackers.where(name: redmine_params[:tracker]).first || project_setting(:tracker)
171
-
172
- # Category
173
- @category = @project.issue_categories.where(id: redmine_params[:category]).first || @project.issue_categories.where(name: redmine_params[:category]).first || project_setting(:category)
174
-
175
- # Priority
176
- @priority = IssuePriority.where(id: redmine_params[:priority]).first || IssuePriority.where(name: redmine_params[:priority]).first || project_setting(:priority) || IssuePriority.default
177
-
178
- # Assignee
179
- @assignee = @project.users.where(id: redmine_params[:assignee]).first || @project.users.where(login: redmine_params[:assignee]).first
180
151
  end
181
152
 
182
153
  end
@@ -5,14 +5,11 @@ class AirbrakeProjectSettingsController < ::ApplicationController
5
5
 
6
6
  def update
7
7
  @airbrake_project_setting = @project.airbrake_settings || AirbrakeProjectSetting.new(project: @project)
8
-
9
- @airbrake_project_setting.tracker = @project.trackers.where(id: params[:airbrake_project_setting][:tracker_id]).first
10
- @airbrake_project_setting.category = @project.issue_categories.where(id: params[:airbrake_project_setting][:category_id]).first
11
- @airbrake_project_setting.priority = IssuePriority.where(id: params[:airbrake_project_setting][:priority_id]).first
12
- @airbrake_project_setting.reopen_regexp = params[:airbrake_project_setting][:reopen_regexp]
8
+ @airbrake_project_setting.safe_attributes = params[:airbrake_project_setting]
13
9
 
14
10
  @airbrake_project_setting.save
15
11
 
12
+ flash[:notice] = l(:notice_successful_update)
16
13
  redirect_to settings_project_path(@project, :tab => 'airbrake')
17
14
  end
18
15
 
@@ -13,7 +13,7 @@ module AirbrakeHelper
13
13
  "* *#{name}:* #{value}"
14
14
  end
15
15
 
16
- def format_backtrace(element)
16
+ def format_backtrace_element(element)
17
17
  @htmlentities ||= HTMLEntities.new
18
18
 
19
19
  repository = repository_for_backtrace_element(element)
@@ -34,8 +34,8 @@ module AirbrakeHelper
34
34
  def repository_for_backtrace_element(element)
35
35
  if element[:file].start_with?('[PROJECT_ROOT]')
36
36
  file = element[:file][14..-1]
37
- if redmine_params.key?(:repository)
38
- r = @project.repositories.where(identifier: (redmine_params[:repository] || '')).first
37
+ if @notice.params.key?(:repository)
38
+ r = @project.repositories.where(identifier: (@notice.params[:repository] || '')).first
39
39
  return r if r.present? && r.entry(file)
40
40
  else
41
41
  return @project.repositories.select{|r| r.entry(file)}.first
@@ -1,4 +1,6 @@
1
1
  class AirbrakeProjectSetting < ActiveRecord::Base
2
+ include Redmine::SafeAttributes
3
+
2
4
  belongs_to :project
3
5
  Project.has_one :airbrake_settings, class_name: AirbrakeProjectSetting.name, dependent: :destroy
4
6
 
@@ -12,4 +14,6 @@ class AirbrakeProjectSetting < ActiveRecord::Base
12
14
  IssuePriority.has_many :airbrake_project_settings, dependent: :nullify
13
15
 
14
16
  validates_presence_of :project_id
17
+
18
+ safe_attributes :tracker_id, :category_id, :priority_id, :reopen_regexp
15
19
  end
@@ -1,38 +1,40 @@
1
- h1. <%= @notice[:error][:message] %>
1
+ h1. <%= @notice.error[:message] %>
2
2
 
3
+ <% if @notice.error.present? && @notice.error[:backtrace].present? %>
3
4
  h2. Stacktrace:
4
5
 
5
- p((. <%=raw backtrace.collect{|e| format_backtrace(e)}.join("\n") %>
6
+ p((. <%=raw @notice.error[:backtrace].collect{|e| format_backtrace_element(e)}.join("\n") %>
7
+ <% end %>
6
8
 
7
- <% if @notice[:request].present? %>
9
+ <% if @notice.request.present? %>
8
10
  h2. Request:
9
11
 
10
- <%=format_list_item('URL', @notice[:request][:url]) %>
11
- <%=format_list_item('Component', @notice[:request][:component]) %>
12
- <%=format_list_item('Action', @notice[:request][:action]) %>
12
+ <%=format_list_item('URL', @notice.request[:url]) %>
13
+ <%=format_list_item('Component', @notice.request[:component]) %>
14
+ <%=format_list_item('Action', @notice.request[:action]) %>
13
15
 
14
- <% if @notice[:request][:params].present? %>
16
+ <% if @notice.request[:params].present? %>
15
17
  h2. Parameters:
16
18
 
17
- <%=format_table(@notice[:request][:params]) %>
19
+ <%=format_table(@notice.request[:params]) %>
18
20
  <% end %>
19
21
 
20
- <% if @notice[:request][:cgi_data].present? %>
22
+ <% if @notice.request[:cgi_data].present? %>
21
23
  h2. Headers:
22
24
 
23
- <%=format_table(@notice[:request][:cgi_data]) %>
25
+ <%=format_table(@notice.request[:cgi_data]) %>
24
26
  <% end %>
25
27
 
26
- <% if @notice[:request][:session].present? %>
28
+ <% if @notice.request[:session].present? %>
27
29
  h2. Session:
28
30
 
29
- <%=format_table(@notice[:request][:session]) %>
31
+ <%=format_table(@notice.request[:session]) %>
30
32
  <% end %>
31
33
  <% end %>
32
34
 
33
- <% if @notice[:server_environment].present? %>
35
+ <% if @notice.env.present? %>
34
36
  h2. Environment
35
37
 
36
- <%=format_list_item('Directory', @notice[:server_environment][:project_root]) %>
37
- <%=format_list_item('Hostname', @notice[:server_environment][:hostname]) %>
38
+ <%=format_list_item('Directory', @notice.env[:project_root]) %>
39
+ <%=format_list_item('Hostname', @notice.env[:hostname]) %>
38
40
  <% end %>
@@ -1,5 +1,7 @@
1
1
  <% @airbrake_project_setting ||= (@project.airbrake_settings || AirbrakeProjectSetting.new(project: @project)) %>
2
2
 
3
+ <%= error_messages_for 'airbrake_project_setting' %>
4
+
3
5
  <h2><%= l(:label_airbrake) %></h2>
4
6
 
5
7
  <%= labelled_form_for :airbrake_project_setting, @airbrake_project_setting, url: airbrake_settings_project_path(@project), html: {method: :post, id: 'airbrake-form'} do |f| %>
@@ -7,19 +9,15 @@
7
9
 
8
10
  <div class="box tabular">
9
11
  <p>
10
- <%=f.label :tracker_id %>
11
12
  <%=f.select :tracker_id, options_from_collection_for_select(@project.trackers, :id, :name, @airbrake_project_setting.tracker_id), include_blank: true %>
12
13
  </p>
13
14
  <p>
14
- <%=f.label :category_id %>
15
15
  <%=f.select :category_id, options_from_collection_for_select(@project.issue_categories, :id, :name, @airbrake_project_setting.category_id), include_blank: true %>
16
16
  </p>
17
17
  <p>
18
- <%=f.label :priority_id %>
19
18
  <%=f.select :priority_id, options_from_collection_for_select(IssuePriority.order(:position), :id, :name, @airbrake_project_setting.priority_id), include_blank: true %>
20
19
  </p>
21
20
  <p>
22
- <%=f.label :reopen_regexp %>
23
21
  <%=f.text_field :reopen_regexp %>
24
22
  <br />
25
23
  (<%=l(:text_regexp_info)%>)
@@ -0,0 +1,85 @@
1
+ require 'json'
2
+ require 'hpricot'
3
+ require 'htmlentities'
4
+
5
+ module RedmineAirbrakeBackend
6
+ class Notice
7
+ SUPPORTED_API_VERSIONS = %w(2.4)
8
+
9
+ class NoticeInvalid < StandardError; end
10
+ class UnsupportedVersion < StandardError; end
11
+
12
+ attr_reader :version, :params, :notifier, :error, :request, :env
13
+
14
+ def initialize(version, params, notifier, options={})
15
+ @version = version
16
+ @params = params
17
+ @notifier = notifier
18
+
19
+ @error = options.delete(:error)
20
+ @request = options.delete(:request)
21
+ @env = options.delete(:env)
22
+ end
23
+
24
+ def self.parse(xml_data)
25
+ doc = Hpricot::XML(xml_data)
26
+
27
+ raise NoticeInvalid if (notice = doc.at('notice')).blank?
28
+ raise NoticeInvalid if (version = notice.attributes['version']).blank?
29
+ raise UnsupportedVersion unless SUPPORTED_API_VERSIONS.include?(version)
30
+
31
+ params = JSON.parse(notice.at('api-key').inner_text).symbolize_keys rescue nil
32
+ raise NoticeInvalid if params.blank?
33
+
34
+ raise NoticeInvalid if (notifier = convert_element(notice.at('notifier'))).blank?
35
+
36
+ raise NoticeInvalid if (error = convert_element(notice.at('error'))).blank?
37
+ raise NoticeInvalid if error[:class].blank? || error[:message].blank?
38
+ error[:backtrace] = error[:backtrace][:line].is_a?(Array) ? error[:backtrace][:line] : [error[:backtrace][:line]] unless error[:backtrace].nil?
39
+
40
+ request = convert_element(notice.at('request'))
41
+ env = convert_element(notice.at('server-environment'))
42
+
43
+ new(version, params, notifier, error: error, request: request, env: env)
44
+ end
45
+
46
+ private
47
+
48
+ def self.convert_element(elem)
49
+ return nil if elem.nil?
50
+
51
+ return elem.children.first.inner_text if !elem.children.nil? && elem.children.count == 1 && elem.children.first.is_a?(Hpricot::Text)
52
+
53
+ return elem.attributes.to_hash.symbolize_keys if elem.children.nil?
54
+
55
+ return convert_var_elements(elem.children) if elem.children.count == elem.children.select{|c| c.name == 'var'}.count
56
+
57
+ h = {}
58
+ elem.children.each do |e|
59
+ key = format_hash_key(e.name)
60
+ if h.key?(key)
61
+ h[key] = [h[key]] unless h[key].is_a?(Array)
62
+ h[key] << convert_element(e)
63
+ else
64
+ h[key] = convert_element(e)
65
+ end
66
+ end
67
+ h.delete_if{|k,v| k.strip.blank?}
68
+ h.symbolize_keys
69
+ end
70
+
71
+ def self.convert_var_elements(elements)
72
+ vars = {}
73
+ elements.each do |elem|
74
+ vars[format_hash_key(elem.attributes['key'])] = elem.inner_text
75
+ end
76
+ vars.delete_if{|k,v| k.strip.blank?}
77
+ vars.symbolize_keys
78
+ end
79
+
80
+ def self.format_hash_key(key)
81
+ key.to_s.gsub(/-/, '_')
82
+ end
83
+
84
+ end
85
+ end
@@ -1,3 +1,3 @@
1
1
  module RedmineAirbrakeBackend
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  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: 0.1.0
4
+ version: 0.2.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: 2013-07-09 00:00:00.000000000 Z
11
+ date: 2013-07-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -107,6 +107,7 @@ files:
107
107
  - db/migrate/20130708102357_create_airbrake_project_settings.rb
108
108
  - lib/redmine_airbrake_backend.rb
109
109
  - lib/redmine_airbrake_backend/engine.rb
110
+ - lib/redmine_airbrake_backend/notice.rb
110
111
  - lib/redmine_airbrake_backend/project_helper_patch.rb
111
112
  - lib/redmine_airbrake_backend/version.rb
112
113
  - redmine_airbrake_backend.gemspec