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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/app/controllers/airbrake_controller.rb +62 -91
- data/app/controllers/airbrake_project_settings_controller.rb +2 -5
- data/app/helpers/airbrake_helper.rb +3 -3
- data/app/models/airbrake_project_setting.rb +4 -0
- data/app/views/airbrake/_issue_description.erb +17 -15
- data/app/views/projects/settings/_airbrake.html.erb +2 -4
- data/lib/redmine_airbrake_backend/notice.rb +85 -0
- data/lib/redmine_airbrake_backend/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c4df3337dec81bfdd744a3ca6a7b741b29ceb59
|
4
|
+
data.tar.gz: 2e43509ade9e2934870111dbd47e8fe2fa5baf58
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79d9a308477cf2ee7b1df7c825a9ecde9e10d34278d9656e3c221f6598424f5ae855cbcb6a023c10a6d2fbbf6401b3ee97bb2684b4004c8cf969e7a6f2cdd4c5
|
7
|
+
data.tar.gz: ed5d5aa24babd1201e680fdb5c761ae7e6cc1050e6bb15e6cb189e8a1790b58df9aa447a9ba23e98cf759b45fe258521478711d1b8c870a6fe78cb0d4ccc77e0
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,24 +1,14 @@
|
|
1
|
-
require '
|
2
|
-
require 'hpricot'
|
3
|
-
require 'htmlentities'
|
1
|
+
require 'redmine_airbrake_backend/notice'
|
4
2
|
|
5
3
|
class AirbrakeController < ::ApplicationController
|
6
|
-
|
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:
|
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[:
|
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
|
68
|
-
|
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
|
72
|
-
|
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
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
85
|
-
if @
|
86
|
-
|
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
|
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
|
96
|
-
h << @notice
|
97
|
-
h +=
|
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[:
|
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
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|
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
|
38
|
-
r = @project.repositories.where(identifier: (
|
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
|
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|
|
6
|
+
p((. <%=raw @notice.error[:backtrace].collect{|e| format_backtrace_element(e)}.join("\n") %>
|
7
|
+
<% end %>
|
6
8
|
|
7
|
-
<% if @notice
|
9
|
+
<% if @notice.request.present? %>
|
8
10
|
h2. Request:
|
9
11
|
|
10
|
-
<%=format_list_item('URL', @notice
|
11
|
-
<%=format_list_item('Component', @notice
|
12
|
-
<%=format_list_item('Action', @notice
|
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
|
16
|
+
<% if @notice.request[:params].present? %>
|
15
17
|
h2. Parameters:
|
16
18
|
|
17
|
-
<%=format_table(@notice
|
19
|
+
<%=format_table(@notice.request[:params]) %>
|
18
20
|
<% end %>
|
19
21
|
|
20
|
-
<% if @notice
|
22
|
+
<% if @notice.request[:cgi_data].present? %>
|
21
23
|
h2. Headers:
|
22
24
|
|
23
|
-
<%=format_table(@notice
|
25
|
+
<%=format_table(@notice.request[:cgi_data]) %>
|
24
26
|
<% end %>
|
25
27
|
|
26
|
-
<% if @notice
|
28
|
+
<% if @notice.request[:session].present? %>
|
27
29
|
h2. Session:
|
28
30
|
|
29
|
-
<%=format_table(@notice
|
31
|
+
<%=format_table(@notice.request[:session]) %>
|
30
32
|
<% end %>
|
31
33
|
<% end %>
|
32
34
|
|
33
|
-
<% if @notice
|
35
|
+
<% if @notice.env.present? %>
|
34
36
|
h2. Environment
|
35
37
|
|
36
|
-
<%=format_list_item('Directory', @notice[:
|
37
|
-
<%=format_list_item('Hostname', @notice[:
|
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
|
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.
|
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-
|
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
|