redmine_airbrake_backend 0.1.0 → 0.2.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: 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