Commit 08a1ac92e9bbb23ca5699ff42592dc91d1f65a81

Authored by Nick Recobra
2 parents b8070297 44600a70
Exists in master and in 1 other branch production

Merge branch 'email_at_notices_per_app' of https://github.com/crossroads/errbit into pull-request-52

app/controllers/apps_controller.rb
... ... @@ -2,6 +2,7 @@ class AppsController < ApplicationController
2 2  
3 3 before_filter :require_admin!, :except => [:index, :show]
4 4 before_filter :find_app, :except => [:index, :new, :create]
  5 + before_filter :parse_email_at_notices_or_set_default, :only => [:create, :update]
5 6  
6 7 def index
7 8 @apps = current_user.admin? ? App.all : current_user.apps.all
... ... @@ -73,4 +74,21 @@ class AppsController < ApplicationController
73 74 # apparently finding by 'watchers.email' and 'id' is broken
74 75 raise(Mongoid::Errors::DocumentNotFound.new(App,@app.id)) unless current_user.admin? || current_user.watching?(@app)
75 76 end
  77 +
  78 + # email_at_notices is edited as a string, and stored as an array.
  79 + def parse_email_at_notices_or_set_default
  80 + if params[:app] && val = params[:app][:email_at_notices]
  81 + # Sanitize negative values, split on comma,
  82 + # strip, parse as integer, remove all '0's.
  83 + # If empty, set as default and show an error message.
  84 + email_at_notices = val.gsub(/-\d+/,"").split(",").map{|v| v.strip.to_i }.reject{|v| v == 0}
  85 + if email_at_notices.any?
  86 + params[:app][:email_at_notices] = email_at_notices
  87 + else
  88 + default_array = params[:app][:email_at_notices] = Errbit::Config.default_email_at_notices
  89 + flash[:error] = "Couldn't parse your notification frequency. Value was reset to default (#{default_array.join(', ')})."
  90 + end
  91 + end
  92 + end
76 93 end
  94 +
... ...
app/models/app.rb
... ... @@ -8,6 +8,7 @@ class App
8 8 field :resolve_errs_on_deploy, :type => Boolean, :default => false
9 9 field :notify_on_errs, :type => Boolean, :default => true
10 10 field :notify_on_deploys, :type => Boolean, :default => true
  11 + field :email_at_notices, :type => Array, :default => Errbit::Config.default_email_at_notices
11 12  
12 13 # Some legacy apps may have sting as key instead of BSON::ObjectID
13 14 identity :type => String
... ...
app/models/notice.rb
... ... @@ -55,11 +55,11 @@ class Notice
55 55 agent_string = env_vars['HTTP_USER_AGENT']
56 56 agent_string.blank? ? nil : UserAgent.parse(agent_string)
57 57 end
58   -
  58 +
59 59 def self.in_app_backtrace_line? line
60 60 !!(line['file'] =~ %r{^\[PROJECT_ROOT\]/(?!(vendor))})
61 61 end
62   -
  62 +
63 63 def request
64 64 read_attribute(:request) || {}
65 65 end
... ... @@ -83,11 +83,11 @@ class Notice
83 83 def cache_last_notice_at
84 84 err.update_attributes(:last_notice_at => created_at)
85 85 end
86   -
  86 +
87 87 protected
88 88  
89 89 def should_notify?
90   - err.app.notify_on_errs? && Errbit::Config.email_at_notices.include?(err.notices.count) && err.app.watchers.any?
  90 + err.app.notify_on_errs? && err.app.email_at_notices.include?(err.notices.count) && err.app.watchers.any?
91 91 end
92 92  
93 93  
... ... @@ -122,3 +122,4 @@ class Notice
122 122 end
123 123 end
124 124 end
  125 +
... ...
app/views/apps/_fields.html.haml
... ... @@ -4,21 +4,34 @@
4 4 = f.label :name
5 5 = f.text_field :name
6 6  
7   -%div.checkbox
8   - = f.check_box :notify_on_errs
9   - = f.label :notify_on_errs, 'Notify on errors'
10   -
11 7 %div
12 8 = f.label :github_url
13 9 = f.text_field :github_url
14 10  
  11 +%fieldset
  12 + %legend Notifications
  13 + %div.checkbox
  14 + = f.check_box :notify_on_errs
  15 + = f.label :notify_on_errs, 'Notify on errors'
  16 + %div.email_at_notices_nested{:style => f.object.notify_on_errs ? '' : 'display: none;'}
  17 + .field-helpertext Send a notification every
  18 + -# Edit the email_at_notices array as a CSV string
  19 + = f.text_field :email_at_notices, :value => f.object.email_at_notices.join(", ")
  20 + .field-helpertext times an error occurs. (CSV)
  21 + %div.checkbox
  22 + = f.check_box :notify_on_deploys
  23 + = f.label :notify_on_deploys, 'Notify on deploys'
  24 +
  25 +:javascript
  26 + $('#app_notify_on_errs').change(function(){
  27 + var el = $('.email_at_notices_nested');
  28 + this.checked ? el.show() : el.hide();
  29 + });
  30 +
15 31 %div.checkbox
16 32 = f.check_box :resolve_errs_on_deploy
17 33 = f.label :resolve_errs_on_deploy, 'Resolve errs on deploy'
18 34  
19   -%div.checkbox
20   - = f.check_box :notify_on_deploys
21   - = f.label :notify_on_deploys, 'Notify on deploys'
22 35  
23 36 %fieldset.nested-wrapper
24 37 %legend Watchers
... ...
config/config.example.yml
... ... @@ -14,11 +14,13 @@ host: errbit.example.com
14 14 # will be sent from.
15 15 email_from: errbit@example.com
16 16  
17   -# Configure when emails are sent for an error.
  17 +# Configure the default value for when emails are sent for an error.
18 18 # [1,3,7] = 1st, 3rd, and 7th occurence triggers
19 19 # an email notification.
20   -email_at_notices: [1, 10, 100]
  20 +# Each app can be configured individually.
  21 +default_email_at_notices: [1, 10, 100]
21 22  
22   -# Set to false to suppress confirmation when
  23 +# Set to false to suppress confirmation when
23 24 # resolving errors.
24 25 confirm_resolve_err: true
  26 +
... ...
config/initializers/_load_config.rb
... ... @@ -4,7 +4,7 @@ if ENV['HEROKU']
4 4 Errbit::Config = OpenStruct.new
5 5 Errbit::Config.host = ENV['ERRBIT_HOST']
6 6 Errbit::Config.email_from = ENV['ERRBIT_EMAIL_FROM']
7   - Errbit::Config.email_at_notices = [1,3,10] #ENV['ERRBIT_EMAIL_AT_NOTICES']
  7 + Errbit::Config.default_email_at_notices = [1,3,10] #ENV['ERRBIT_EMAIL_AT_NOTICES']
8 8 Errbit::Application.config.action_mailer.smtp_settings = {
9 9 :address => "smtp.sendgrid.net",
10 10 :port => "25",
... ... @@ -25,4 +25,5 @@ end
25 25 # Set config specific values
26 26 (Errbit::Application.config.action_mailer.default_url_options ||= {}).tap do |default|
27 27 default.merge! :host => Errbit::Config.host if default[:host].blank?
28   -end
29 28 \ No newline at end of file
  29 +end
  30 +
... ...
public/stylesheets/application.css
... ... @@ -350,6 +350,17 @@ form .error-messages ul {
350 350 list-style-type: square;
351 351 }
352 352  
  353 +form .field-helpertext {
  354 + display: inline;
  355 + text-transform: uppercase;
  356 +}
  357 +
  358 +form input#app_email_at_notices {
  359 + width: 130px;
  360 + margin: 0 5px;
  361 +}
  362 +
  363 +
353 364 /* Tables */
354 365 table {
355 366 width: 100%;
... ... @@ -683,3 +694,4 @@ span.click_span {
683 694 #deploys_div, #repository_div, #watchers_div {
684 695 display: none;
685 696 }
  697 +
... ...
spec/controllers/apps_controller_spec.rb
... ... @@ -241,6 +241,27 @@ describe AppsController do
241 241 end
242 242 end
243 243  
  244 + context "changing email_at_notices" do
  245 + it "should parse legal csv values" do
  246 + put :update, :id => @app.id, :app => { :email_at_notices => '1, 4, 7,8, 10' }
  247 + @app.reload
  248 + @app.email_at_notices.should == [1, 4, 7, 8, 10]
  249 + end
  250 + context "failed parsing of CSV" do
  251 + it "should set the default value" do
  252 + @app = Factory(:app, :email_at_notices => [1, 2, 3, 4])
  253 + put :update, :id => @app.id, :app => { :email_at_notices => 'asdf, -1,0,foobar,gd00,0,abc' }
  254 + @app.reload
  255 + @app.email_at_notices.should == Errbit::Config.default_email_at_notices
  256 + end
  257 +
  258 + it "should display a message" do
  259 + put :update, :id => @app.id, :app => { :email_at_notices => 'qwertyuiop' }
  260 + request.flash[:error].should match(/Couldn't parse/)
  261 + end
  262 + end
  263 + end
  264 +
244 265 context "setting up issue tracker", :cur => true do
245 266 context "unknown tracker type" do
246 267 before(:each) do
... ... @@ -386,3 +407,4 @@ describe AppsController do
386 407 end
387 408  
388 409 end
  410 +
... ...
spec/factories/app_factories.rb
1 1 Factory.define(:app) do |p|
2 2 p.name { Factory.next :app_name }
  3 + p.email_at_notices { [1, 10, 100] }
3 4 end
4 5  
5 6 Factory.define(:app_with_watcher, :parent => :app) do |p|
... ... @@ -26,3 +27,4 @@ Factory.define(:deploy) do |d|
26 27 d.environment 'production'
27 28 d.revision ActiveSupport::SecureRandom.hex(10)
28 29 end
  30 +
... ...
spec/models/notice_spec.rb
... ... @@ -21,15 +21,15 @@ describe Notice do
21 21 notice.errors[:notifier].should include("can't be blank")
22 22 end
23 23 end
24   -
  24 +
25 25 context '.in_app_backtrace_line?' do
26 26 let(:backtrace) do [{
27 27 'number' => rand(999),
28   - 'file' => '[GEM_ROOT]/gems/actionpack-3.0.4/lib/action_controller/metal/rescue.rb',
  28 + 'file' => '[GEM_ROOT]/gems/actionpack-3.0.4/lib/action_controller/metal/rescue.rb',
29 29 'method' => ActiveSupport.methods.shuffle.first
30 30 }, {
31 31 'number' => rand(999),
32   - 'file' => '[PROJECT_ROOT]/vendor/plugins/seamless_database_pool/lib/seamless_database_pool/controller_filter.rb',
  32 + 'file' => '[PROJECT_ROOT]/vendor/plugins/seamless_database_pool/lib/seamless_database_pool/controller_filter.rb',
33 33 'method' => ActiveSupport.methods.shuffle.first
34 34 }, {
35 35 'number' => rand(999),
... ... @@ -50,7 +50,7 @@ describe Notice do
50 50 Notice.in_app_backtrace_line?(backtrace[2]).should == true
51 51 end
52 52 end
53   -
  53 +
54 54 context '#from_xml' do
55 55 before do
56 56 @xml = Rails.root.join('spec','fixtures','hoptoad_test_notice.xml').read
... ... @@ -159,20 +159,22 @@ describe Notice do
159 159 notice.user_agent.browser.should == 'Chrome'
160 160 notice.user_agent.version.to_s.should =~ /^10\.0/
161 161 end
162   -
  162 +
163 163 it "should be nil if HTTP_USER_AGENT is blank" do
164 164 notice = Factory.build(:notice)
165 165 notice.user_agent.should == nil
166 166 end
167 167 end
168   -
169   - describe "email notifications" do
  168 +
  169 + describe "email notifications (configured individually for each app)" do
  170 + custom_thresholds = [2, 4, 8, 16, 32, 64]
  171 +
170 172 before do
171   - @app = Factory(:app_with_watcher)
  173 + @app = Factory(:app_with_watcher, :email_at_notices => custom_thresholds)
172 174 @err = Factory(:err, :app => @app)
173 175 end
174 176  
175   - Errbit::Config.email_at_notices.each do |threshold|
  177 + custom_thresholds.each do |threshold|
176 178 it "sends an email notification after #{threshold} notice(s)" do
177 179 @err.notices.stub(:count).and_return(threshold)
178 180 Mailer.should_receive(:err_notification).
... ... @@ -183,3 +185,4 @@ describe Notice do
183 185 end
184 186  
185 187 end
  188 +
... ...