Commit ba398235a740b4902fc2993b45a8e2f82b4530dd

Authored by Nathan Broadbent
2 parents 2525542a 2baa0302
Exists in master and in 1 other branch production

Merge branch 'notifications' of https://github.com/amaabca/errbit into amaabca-notifications

Conflicts:
	Gemfile.lock
@@ -5,6 +5,9 @@ gem 'rails', '3.2.8' @@ -5,6 +5,9 @@ gem 'rails', '3.2.8'
5 gem 'nokogiri' 5 gem 'nokogiri'
6 gem 'mongoid', '~> 2.4.10' 6 gem 'mongoid', '~> 2.4.10'
7 7
  8 +# force SSL
  9 +gem 'rack-ssl', :require => 'rack/ssl'
  10 +
8 gem 'haml' 11 gem 'haml'
9 gem 'htmlentities', "~> 4.3.0" 12 gem 'htmlentities', "~> 4.3.0"
10 13
@@ -30,6 +33,7 @@ gem 'kaminari' @@ -30,6 +33,7 @@ gem 'kaminari'
30 gem 'rack-ssl-enforcer' 33 gem 'rack-ssl-enforcer'
31 gem 'fabrication', "~> 1.3.0" # Both for tests, and loading demo data 34 gem 'fabrication', "~> 1.3.0" # Both for tests, and loading demo data
32 gem 'rails_autolink', '~> 1.0.9' 35 gem 'rails_autolink', '~> 1.0.9'
  36 +gem 'campy'
33 37
34 platform :ruby do 38 platform :ruby do
35 gem 'mongo', '= 1.6.2' 39 gem 'mongo', '= 1.6.2'
@@ -45,7 +49,6 @@ group :development, :test do @@ -45,7 +49,6 @@ group :development, :test do
45 gem 'webmock', :require => false 49 gem 'webmock', :require => false
46 unless ENV["CI"] 50 unless ENV["CI"]
47 gem 'ruby-debug', :platform => :mri_18 51 gem 'ruby-debug', :platform => :mri_18
48 - gem 'debugger', :platform => :mri_19  
49 end 52 end
50 # gem 'rpm_contrib' 53 # gem 'rpm_contrib'
51 # gem 'newrelic_rpm' 54 # gem 'newrelic_rpm'
@@ -40,6 +40,8 @@ GEM @@ -40,6 +40,8 @@ GEM
40 bson_ext (1.6.2) 40 bson_ext (1.6.2)
41 bson (~> 1.6.2) 41 bson (~> 1.6.2)
42 builder (3.0.3) 42 builder (3.0.3)
  43 + campy (0.1.3)
  44 + multi_json (~> 1.0)
43 capistrano (2.13.3) 45 capistrano (2.13.3)
44 highline 46 highline
45 net-scp (>= 1.0.0) 47 net-scp (>= 1.0.0)
@@ -62,13 +64,6 @@ GEM @@ -62,13 +64,6 @@ GEM
62 rdoc 64 rdoc
63 daemons (1.1.8) 65 daemons (1.1.8)
64 database_cleaner (0.6.7) 66 database_cleaner (0.6.7)
65 - debugger (1.2.0)  
66 - columnize (>= 0.3.1)  
67 - debugger-linecache (~> 1.1.1)  
68 - debugger-ruby_core_source (~> 1.1.3)  
69 - debugger-linecache (1.1.2)  
70 - debugger-ruby_core_source (>= 1.1.1)  
71 - debugger-ruby_core_source (1.1.3)  
72 devise (1.5.3) 67 devise (1.5.3)
73 bcrypt-ruby (~> 3.0) 68 bcrypt-ruby (~> 3.0)
74 orm_adapter (~> 0.0.3) 69 orm_adapter (~> 0.0.3)
@@ -289,10 +284,10 @@ DEPENDENCIES @@ -289,10 +284,10 @@ DEPENDENCIES
289 actionmailer_inline_css (~> 1.3.0) 284 actionmailer_inline_css (~> 1.3.0)
290 bson (= 1.6.2) 285 bson (= 1.6.2)
291 bson_ext (= 1.6.2) 286 bson_ext (= 1.6.2)
  287 + campy
292 capistrano 288 capistrano
293 capybara 289 capybara
294 database_cleaner (~> 0.6.0) 290 database_cleaner (~> 0.6.0)
295 - debugger  
296 devise (~> 1.5.3) 291 devise (~> 1.5.3)
297 email_spec 292 email_spec
298 execjs 293 execjs
@@ -313,6 +308,7 @@ DEPENDENCIES @@ -313,6 +308,7 @@ DEPENDENCIES
313 omniauth-github 308 omniauth-github
314 oruen_redmine_client 309 oruen_redmine_client
315 pivotal-tracker 310 pivotal-tracker
  311 + rack-ssl
316 rack-ssl-enforcer 312 rack-ssl-enforcer
317 rails (= 3.2.8) 313 rails (= 3.2.8)
318 rails_autolink (~> 1.0.9) 314 rails_autolink (~> 1.0.9)
app/assets/images/campfire_create.png 0 → 100644

3.19 KB

app/assets/images/campfire_goto.png 0 → 100644

3.19 KB

app/assets/images/campfire_inactive.png 0 → 100644

2.8 KB

app/assets/javascripts/form.js
@@ -8,6 +8,9 @@ $(function(){ @@ -8,6 +8,9 @@ $(function(){
8 if($('div.issue_tracker.nested').length) 8 if($('div.issue_tracker.nested').length)
9 activateTypeSelector('issue_tracker', 'tracker_params'); 9 activateTypeSelector('issue_tracker', 'tracker_params');
10 10
  11 + if($('div.notification_service.nested').length)
  12 + activateTypeSelector('notification_service', 'notification_params');
  13 +
11 $('body').addClass('has-js'); 14 $('body').addClass('has-js');
12 $('.label_radio').click(function(){ 15 $('.label_radio').click(function(){
13 activateLabelIcons(); 16 activateLabelIcons();
app/assets/stylesheets/application.css.erb
@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 *= require jquery.alerts 3 *= require jquery.alerts
4 *= require errbit 4 *= require errbit
5 *= require issue_tracker_icons 5 *= require issue_tracker_icons
  6 + *= require notification_service_icons
6 *= require_self 7 *= require_self
7 */ 8 */
8 9
app/assets/stylesheets/errbit.css
@@ -535,10 +535,11 @@ a.button.active { @@ -535,10 +535,11 @@ a.button.active {
535 display: inline-block; 535 display: inline-block;
536 } 536 }
537 537
538 -/* Watchers and Issue Tracker Forms */  
539 -div.watcher.nested .watcher_params, div.issue_tracker.nested .tracker_params { 538 +/* Watchers / Issue Tracker / Notification Forms */
  539 +div.watcher.nested .watcher_params, div.issue_tracker.nested .tracker_params, div.notification_service.nested .notification_params {
540 display: none; 540 display: none;
541 } 541 }
  542 +
542 div.nested .chosen { 543 div.nested .chosen {
543 display: block !important; 544 display: block !important;
544 } 545 }
@@ -546,35 +547,35 @@ div.nested .choose { @@ -546,35 +547,35 @@ div.nested .choose {
546 margin-bottom: 0.5em; 547 margin-bottom: 0.5em;
547 } 548 }
548 549
549 -div.issue_tracker.nested .choose { 550 +div.issue_tracker.nested .choose, div.notification_service.nested .choose {
550 background-color: #ebebeb; 551 background-color: #ebebeb;
551 border: 1px solid #dddddd; 552 border: 1px solid #dddddd;
552 margin: 0 0 15px; 553 margin: 0 0 15px;
553 padding: 12px; 554 padding: 12px;
554 } 555 }
555 -div.issue_tracker.nested img { 556 +div.issue_tracker.nested img, div.notification_service.nested img {
556 vertical-align: middle; 557 vertical-align: middle;
557 } 558 }
558 559
559 /* Icons for Issue Tracker Radio Buttons */ 560 /* Icons for Issue Tracker Radio Buttons */
560 -div.issue_tracker.nested label.label_radio { 561 +div.issue_tracker.nested label.label_radio, div.notification_service.nested label.label_radio {
561 color: #929292; 562 color: #929292;
562 padding-left: 33px; 563 padding-left: 33px;
563 margin-bottom: 6px; 564 margin-bottom: 6px;
564 margin-right: 8px; 565 margin-right: 8px;
565 line-height: 30px; 566 line-height: 30px;
566 } 567 }
567 -div.issue_tracker.nested .choose { 568 +div.issue_tracker.nested .choose, div.notification_service.nested .choose {
568 padding-bottom: 6px; 569 padding-bottom: 6px;
569 } 570 }
570 -div.issue_tracker.nested label.label_radio:hover { 571 +div.issue_tracker.nested label.label_radio:hover, div.notification_service.nested label.label_radio:hover {
571 color: #696969; 572 color: #696969;
572 } 573 }
573 -div.issue_tracker.nested .label_radio input { 574 +div.issue_tracker.nested .label_radio input, div.notification_service.nested .label_radio input {
574 position: absolute; left: -9999px; 575 position: absolute; left: -9999px;
575 } 576 }
576 577
577 -div.issue_tracker.nested label.r_on, div.issue_tracker.nested label.r_on:hover { 578 +div.issue_tracker.nested label.r_on, div.issue_tracker.nested label.r_on:hover, div.notification_service.nested label.r_on, div.notification_service.nested label.r_on:hover {
578 color: #191919; 579 color: #191919;
579 } 580 }
580 581
app/assets/stylesheets/issue_tracker_icons.css.erb
1 /* Issue Tracker inactive, select, create and goto icons */ 1 /* Issue Tracker inactive, select, create and goto icons */
2 <% trackers = IssueTracker.subclasses.map{|t| t.label } << 'none' %> 2 <% trackers = IssueTracker.subclasses.map{|t| t.label } << 'none' %>
  3 +
3 <% trackers.each do |tracker| %> 4 <% trackers.each do |tracker| %>
4 div.issue_tracker.nested label.<%= tracker %> { 5 div.issue_tracker.nested label.<%= tracker %> {
5 background: url(/assets/<%= tracker %>_inactive.png) no-repeat; 6 background: url(/assets/<%= tracker %>_inactive.png) no-repeat;
@@ -14,3 +15,4 @@ div.issue_tracker.nested label.r_on.&lt;%= tracker %&gt; { @@ -14,3 +15,4 @@ div.issue_tracker.nested label.r_on.&lt;%= tracker %&gt; {
14 background: transparent url(/assets/<%= tracker %>_goto.png) 6px 5px no-repeat; 15 background: transparent url(/assets/<%= tracker %>_goto.png) 6px 5px no-repeat;
15 } 16 }
16 <% end %> 17 <% end %>
  18 +
app/assets/stylesheets/notification_service_icons.css.erb 0 → 100644
@@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
  1 + /* Notification Service inactive, select, create and goto icons */
  2 +<% notification_services = NotificationService.subclasses.map{|t| t.label } << 'none' %>
  3 +
  4 +<% notification_services.each do |notification_service| %>
  5 +div.notification_service.nested label.<%= notification_service %> {
  6 + background: url(/assets/<%= notification_service %>_inactive.png) no-repeat;
  7 +}
  8 +div.notification_service.nested label.r_on.<%= notification_service %> {
  9 + background: url(/assets/<%= notification_service %>_create.png) no-repeat;
  10 +}
  11 +#action-bar a.<%= notification_service %>_create {
  12 + background: transparent url(/assets/<%= notification_service %>_create.png) 6px 5px no-repeat;
  13 +}
  14 +#action-bar a.<%= notification_service %>_goto {
  15 + background: transparent url(/assets/<%= notification_service %>_goto.png) 6px 5px no-repeat;
  16 +}
  17 +<% end %>
  18 +
app/controllers/apps_controller.rb
@@ -29,12 +29,14 @@ class AppsController &lt; InheritedResources::Base @@ -29,12 +29,14 @@ class AppsController &lt; InheritedResources::Base
29 def create 29 def create
30 @app = App.new(params[:app]) 30 @app = App.new(params[:app])
31 initialize_subclassed_issue_tracker 31 initialize_subclassed_issue_tracker
  32 + initialize_subclassed_notification_service
32 create! 33 create!
33 end 34 end
34 35
35 def update 36 def update
36 @app = resource 37 @app = resource
37 initialize_subclassed_issue_tracker 38 initialize_subclassed_issue_tracker
  39 + initialize_subclassed_notification_service
38 update! 40 update!
39 end 41 end
40 42
@@ -70,6 +72,7 @@ class AppsController &lt; InheritedResources::Base @@ -70,6 +72,7 @@ class AppsController &lt; InheritedResources::Base
70 end 72 end
71 73
72 def initialize_subclassed_issue_tracker 74 def initialize_subclassed_issue_tracker
  75 + # set the app's issue tracker
73 if params[:app][:issue_tracker_attributes] && tracker_type = params[:app][:issue_tracker_attributes][:type] 76 if params[:app][:issue_tracker_attributes] && tracker_type = params[:app][:issue_tracker_attributes][:type]
74 if IssueTracker.subclasses.map(&:name).concat(["IssueTracker"]).include?(tracker_type) 77 if IssueTracker.subclasses.map(&:name).concat(["IssueTracker"]).include?(tracker_type)
75 @app.issue_tracker = tracker_type.constantize.new(params[:app][:issue_tracker_attributes]) 78 @app.issue_tracker = tracker_type.constantize.new(params[:app][:issue_tracker_attributes])
@@ -77,6 +80,15 @@ class AppsController &lt; InheritedResources::Base @@ -77,6 +80,15 @@ class AppsController &lt; InheritedResources::Base
77 end 80 end
78 end 81 end
79 82
  83 + def initialize_subclassed_notification_service
  84 + # set the app's notification service
  85 + if params[:app][:notification_service_attributes] && notification_type = params[:app][:notification_service_attributes][:type]
  86 + if NotificationService.subclasses.map(&:name).concat(["NotificationService"]).include?(notification_type)
  87 + @app.notification_service = notification_type.constantize.new(params[:app][:notification_service_attributes])
  88 + end
  89 + end
  90 + end
  91 +
80 def begin_of_association_chain 92 def begin_of_association_chain
81 # Filter the @apps collection to apps watched by the current user, unless user is an admin. 93 # Filter the @apps collection to apps watched by the current user, unless user is an admin.
82 # If user is an admin, then no filter is applied, and all apps are shown. 94 # If user is an admin, then no filter is applied, and all apps are shown.
@@ -90,6 +102,7 @@ class AppsController &lt; InheritedResources::Base @@ -90,6 +102,7 @@ class AppsController &lt; InheritedResources::Base
90 def plug_params app 102 def plug_params app
91 app.watchers.build if app.watchers.none? 103 app.watchers.build if app.watchers.none?
92 app.issue_tracker = IssueTracker.new unless app.issue_tracker_configured? 104 app.issue_tracker = IssueTracker.new unless app.issue_tracker_configured?
  105 + app.notification_service = NotificationService.new unless app.notification_service_configured?
93 app.copy_attributes_from(params[:copy_attributes_from]) if params[:copy_attributes_from] 106 app.copy_attributes_from(params[:copy_attributes_from]) if params[:copy_attributes_from]
94 end 107 end
95 108
app/helpers/apps_helper.rb
@@ -16,6 +16,11 @@ module AppsHelper @@ -16,6 +16,11 @@ module AppsHelper
16 @any_github_repos 16 @any_github_repos
17 end 17 end
18 18
  19 + def any_notification_services?
  20 + detect_any_apps_with_attributes unless @any_notification_services
  21 + @any_notification_services
  22 + end
  23 +
19 def any_issue_trackers? 24 def any_issue_trackers?
20 detect_any_apps_with_attributes unless @any_issue_trackers 25 detect_any_apps_with_attributes unless @any_issue_trackers
21 @any_issue_trackers 26 @any_issue_trackers
@@ -29,11 +34,12 @@ module AppsHelper @@ -29,11 +34,12 @@ module AppsHelper
29 private 34 private
30 35
31 def detect_any_apps_with_attributes 36 def detect_any_apps_with_attributes
32 - @any_github_repos = @any_issue_trackers = @any_deploys = false 37 + @any_github_repos = @any_issue_trackers = @any_deploys = @any_notification_services = false
33 @apps.each do |app| 38 @apps.each do |app|
34 @any_github_repos ||= app.github_repo? 39 @any_github_repos ||= app.github_repo?
35 @any_issue_trackers ||= app.issue_tracker_configured? 40 @any_issue_trackers ||= app.issue_tracker_configured?
36 @any_deploys ||= !!app.last_deploy_at 41 @any_deploys ||= !!app.last_deploy_at
  42 + @any_notification_services ||= app.notification_service_configured?
37 end 43 end
38 end 44 end
39 end 45 end
app/models/app.rb
@@ -17,6 +17,8 @@ class App @@ -17,6 +17,8 @@ class App
17 embeds_many :watchers 17 embeds_many :watchers
18 embeds_many :deploys 18 embeds_many :deploys
19 embeds_one :issue_tracker 19 embeds_one :issue_tracker
  20 + embeds_one :notification_service
  21 +
20 has_many :problems, :inverse_of => :app, :dependent => :destroy 22 has_many :problems, :inverse_of => :app, :dependent => :destroy
21 23
22 before_validation :generate_api_key, :on => :create 24 before_validation :generate_api_key, :on => :create
@@ -33,7 +35,8 @@ class App @@ -33,7 +35,8 @@ class App
33 :reject_if => proc { |attrs| attrs[:user_id].blank? && attrs[:email].blank? } 35 :reject_if => proc { |attrs| attrs[:user_id].blank? && attrs[:email].blank? }
34 accepts_nested_attributes_for :issue_tracker, :allow_destroy => true, 36 accepts_nested_attributes_for :issue_tracker, :allow_destroy => true,
35 :reject_if => proc { |attrs| !IssueTracker.subclasses.map(&:to_s).include?(attrs[:type].to_s) } 37 :reject_if => proc { |attrs| !IssueTracker.subclasses.map(&:to_s).include?(attrs[:type].to_s) }
36 - 38 + accepts_nested_attributes_for :notification_service, :allow_destroy => true,
  39 + :reject_if => proc { |attrs| !NotificationService.subclasses.map(&:to_s).include?(attrs[:type].to_s) }
37 40
38 # Processes a new error report. 41 # Processes a new error report.
39 # 42 #
@@ -121,6 +124,11 @@ class App @@ -121,6 +124,11 @@ class App
121 !!(issue_tracker && issue_tracker.class < IssueTracker && issue_tracker.project_id.present?) 124 !!(issue_tracker && issue_tracker.class < IssueTracker && issue_tracker.project_id.present?)
122 end 125 end
123 126
  127 + def notification_service_configured?
  128 + !!(notification_service && notification_service.class < NotificationService && notification_service.api_token.present?)
  129 + end
  130 +
  131 +
124 def notification_recipients 132 def notification_recipients
125 if notify_all_users 133 if notify_all_users
126 (User.all.map(&:email).reject(&:blank?) + watchers.map(&:address)).uniq 134 (User.all.map(&:email).reject(&:blank?) + watchers.map(&:address)).uniq
@@ -137,7 +145,7 @@ class App @@ -137,7 +145,7 @@ class App
137 self.send("#{k}=", copy_app.send(k)) 145 self.send("#{k}=", copy_app.send(k))
138 end 146 end
139 # Clone the embedded objects that can be changed via apps/edit (ignore errs & deploys, etc.) 147 # Clone the embedded objects that can be changed via apps/edit (ignore errs & deploys, etc.)
140 - %w(watchers issue_tracker).each do |relation| 148 + %w(watchers issue_tracker notification_service).each do |relation|
141 if obj = copy_app.send(relation) 149 if obj = copy_app.send(relation)
142 self.send("#{relation}=", obj.is_a?(Array) ? obj.map(&:clone) : obj.clone) 150 self.send("#{relation}=", obj.is_a?(Array) ? obj.map(&:clone) : obj.clone)
143 end 151 end
app/models/issue_tracker.rb
@@ -14,6 +14,7 @@ class IssueTracker @@ -14,6 +14,7 @@ class IssueTracker
14 field :username, :type => String 14 field :username, :type => String
15 field :password, :type => String 15 field :password, :type => String
16 field :ticket_properties, :type => String 16 field :ticket_properties, :type => String
  17 + field :subdomain, :type => String
17 18
18 validate :check_params 19 validate :check_params
19 20
app/models/notice_observer.rb
@@ -4,6 +4,11 @@ class NoticeObserver &lt; Mongoid::Observer @@ -4,6 +4,11 @@ class NoticeObserver &lt; Mongoid::Observer
4 def after_create notice 4 def after_create notice
5 return unless should_notify? notice 5 return unless should_notify? notice
6 6
  7 + # if the app has a notficiation service, fire it off
  8 + unless notice.app.notification_service.nil?
  9 + notice.app.notification_service.create_notification(notice.problem)
  10 + end
  11 +
7 Mailer.err_notification(notice).deliver 12 Mailer.err_notification(notice).deliver
8 end 13 end
9 14
@@ -15,5 +20,4 @@ class NoticeObserver &lt; Mongoid::Observer @@ -15,5 +20,4 @@ class NoticeObserver &lt; Mongoid::Observer
15 (Errbit::Config.per_app_email_at_notices && app.email_at_notices || Errbit::Config.email_at_notices).include?(notice.problem.notices_count) && 20 (Errbit::Config.per_app_email_at_notices && app.email_at_notices || Errbit::Config.email_at_notices).include?(notice.problem.notices_count) &&
16 app.notification_recipients.any? 21 app.notification_recipients.any?
17 end 22 end
18 -  
19 end 23 end
app/models/notification_service.rb 0 → 100644
@@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
  1 +class NotificationService
  2 + include Mongoid::Document
  3 +
  4 + field :room_id, :type => String
  5 + field :api_token, :type => String
  6 + field :subdomain, :type => String
  7 +
  8 + embedded_in :app, :inverse_of => :notification_service
  9 +
  10 + validate :check_params
  11 +
  12 + # Subclasses are responsible for overwriting this method.
  13 + def check_params; true; end
  14 +
  15 + def notification_description(problem)
  16 + "[#{ problem.environment }][#{ problem.where }] #{problem.message.to_s.truncate(100)}"
  17 + end
  18 +
  19 + # Allows us to set the issue tracker class from a single form.
  20 + def type; self._type; end
  21 + def type=(t); self._type=t; end
  22 +
  23 + # Retrieve tracker label from either class or instance.
  24 + Label = ''
  25 + def self.label; self::Label; end
  26 + def label; self.class.label; end
  27 +end
app/models/notification_services/campfire_service.rb 0 → 100644
@@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
  1 +class NotificationService::CampfireService < NotificationService
  2 + Label = "campfire"
  3 + Fields = [
  4 + [:subdomain, {
  5 + :placeholder => "Campfire Subdomain"
  6 + }],
  7 + [:api_token, {
  8 + :placeholder => "API Token"
  9 + }],
  10 + [:room_id, {
  11 + :placeholder => "Room ID",
  12 + :label => "Room ID"
  13 + }],
  14 + ]
  15 +
  16 + def check_params
  17 + if Fields.detect {|f| self[f[0]].blank? }
  18 + errors.add :base, 'You must specify your Campfire Subdomain, API token and Room ID'
  19 + end
  20 + end
  21 +
  22 + def create_notification(problem)
  23 + # build the campfire client
  24 + campy = Campy::Room.new(:account => subdomain, :token => api_token, :room_id => room_id)
  25 +
  26 + # post the issue to the campfire room
  27 + campy.speak "[errbit] http://#{Errbit::Config.host}/apps/#{problem.app.id.to_s} #{notification_description problem}"
  28 + end
  29 +end
0 \ No newline at end of file 30 \ No newline at end of file
app/views/apps/_fields.html.haml
@@ -46,4 +46,5 @@ @@ -46,4 +46,5 @@
46 = f.label :resolve_errs_on_deploy, 'Resolve errs on deploy' 46 = f.label :resolve_errs_on_deploy, 'Resolve errs on deploy'
47 47
48 = render "issue_tracker_fields", :f => f 48 = render "issue_tracker_fields", :f => f
  49 += render "service_notification_fields", :f => f
49 50
app/views/apps/_service_notification_fields.html.haml 0 → 100644
@@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
  1 +%fieldset
  2 + %legend Notification Service
  3 + = f.fields_for :notification_service do |w|
  4 + %div.notification_service.nested
  5 + %div.choose
  6 + = label_tag :type_none, :for => label_for_attr(w, 'type_notificationservice'), :class => "label_radio none" do
  7 + = w.radio_button :type, "NotificationService", 'data-section' => 'none'
  8 + (None)
  9 + - NotificationService.subclasses.each do |notification_service|
  10 + = label_tag "type_#{notification_service.label}:", :for => label_for_attr(w, "type_#{notification_service.name.downcase.gsub(':','')}"), :class => "label_radio #{notification_service.label}" do
  11 + = w.radio_button :type, notification_service.name, 'data-section' => notification_service.label
  12 + = notification_service.name[/::(.*)Service/,1].titleize
  13 +
  14 + %div.notification_params.none{:class => (w.object && !(w.object.class < NotificationService)) ? 'chosen' : nil}
  15 + - NotificationService.subclasses.each do |notification_service|
  16 + %div.notification_params{:class => (w.object.is_a?(notification_service) ? 'chosen ' : '') << notification_service.label}
  17 + - notification_service::Fields.each do |field, field_info|
  18 + = w.label field, field_info[:label] || field.to_s.titleize
  19 + - field_type = field == :password ? :password_field : :text_field
  20 + = w.send field_type, field, :placeholder => field_info[:placeholder], :value => w.object.send(field)
  21 +
  22 + .image_preloader
  23 + - (NotificationService.subclasses.map{|t| t.label } << 'none').each do |notification_service|
  24 + = image_tag "#{notification_service}_inactive.png"
  25 + = image_tag "#{notification_service}_create.png"
app/views/apps/index.html.haml
@@ -8,6 +8,8 @@ @@ -8,6 +8,8 @@
8 %th Name 8 %th Name
9 - if any_github_repos? 9 - if any_github_repos?
10 %th GitHub Repo 10 %th GitHub Repo
  11 + - if any_notification_services?
  12 + %th Notification Service
11 - if any_issue_trackers? 13 - if any_issue_trackers?
12 %th Tracker 14 %th Tracker
13 - if any_deploys? 15 - if any_deploys?
@@ -21,6 +23,10 @@ @@ -21,6 +23,10 @@
21 %td.github_repo 23 %td.github_repo
22 - if app.github_repo? 24 - if app.github_repo?
23 = link_to(app.github_repo, app.github_url, :target => '_blank') 25 = link_to(app.github_repo, app.github_url, :target => '_blank')
  26 + - if any_notification_services?
  27 + %td.notification_service
  28 + - if app.notification_service_configured?
  29 + = image_tag("#{app.notification_service.label}_goto.png")
24 - if any_issue_trackers? 30 - if any_issue_trackers?
25 %td.issue_tracker 31 %td.issue_tracker
26 - if app.issue_tracker_configured? 32 - if app.issue_tracker_configured?
config/environments/production.rb
@@ -59,5 +59,8 @@ Errbit::Application.configure do @@ -59,5 +59,8 @@ Errbit::Application.configure do
59 59
60 # Send deprecation notices to registered listeners 60 # Send deprecation notices to registered listeners
61 config.active_support.deprecation = :notify 61 config.active_support.deprecation = :notify
  62 +
  63 + # enable HTTPS
  64 + config.middleware.insert_before Rack::Lock, "Rack::SSL"
62 end 65 end
63 66
spec/fabricators/issue_tracker_fabricator.rb
@@ -24,4 +24,3 @@ Fabricator :github_issues_tracker, :from =&gt; :issue_tracker, :class_name =&gt; &quot;Issu @@ -24,4 +24,3 @@ Fabricator :github_issues_tracker, :from =&gt; :issue_tracker, :class_name =&gt; &quot;Issu
24 project_id 'test_account/test_project' 24 project_id 'test_account/test_project'
25 username 'test_username' 25 username 'test_username'
26 end 26 end
27 -  
spec/fabricators/notification_service_fabricator.rb 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +Fabricator :notification_service do
  2 + app!
  3 + room_id { sequence :word }
  4 + api_token { sequence :word }
  5 + subdomain { sequence :word }
  6 +end
  7 +
  8 +%w(campfire).each do |t|
  9 + Fabricator "#{t}_notification_service".to_sym, :from => :notification_service, :class_name => "NotificationService::#{t.camelcase}Service"
  10 +end
spec/models/notice_observer_spec.rb
@@ -25,7 +25,6 @@ describe NoticeObserver do @@ -25,7 +25,6 @@ describe NoticeObserver do
25 end 25 end
26 26
27 describe "email notifications for a resolved issue" do 27 describe "email notifications for a resolved issue" do
28 -  
29 before do 28 before do
30 Errbit::Config.per_app_email_at_notices = true 29 Errbit::Config.per_app_email_at_notices = true
31 @app = Fabricate(:app_with_watcher, :email_at_notices => [1]) 30 @app = Fabricate(:app_with_watcher, :email_at_notices => [1])
@@ -43,4 +42,28 @@ describe NoticeObserver do @@ -43,4 +42,28 @@ describe NoticeObserver do
43 Fabricate(:notice, :err => @err) 42 Fabricate(:notice, :err => @err)
44 end 43 end
45 end 44 end
  45 +
  46 + describe "should send a notification if a notification service is configured" do
  47 + let(:app) { app = Fabricate(:app, :email_at_notices => [1], :notification_service => Fabricate(:campfire_notification_service))}
  48 + let(:err) { Fabricate(:err, :problem => Fabricate(:problem, :app => app, :notices_count => 100)) }
  49 +
  50 + before do
  51 + Errbit::Config.per_app_email_at_notices = true
  52 + end
  53 +
  54 + after do
  55 + Errbit::Config.per_app_email_at_notices = false
  56 + end
  57 +
  58 + it "should create a campfire notification" do
  59 + err.problem.stub(:notices_count) { 1 }
  60 + app.notification_service.stub!(:create_notification).and_return(true)
  61 + app.stub!(:notification_recipients => %w('ryan@system88.com'))
  62 + app.notification_service.should_receive(:create_notification)
  63 +
  64 + Notice.create!(:err => err, :message => 'FooError: Too Much Bar', :server_environment => {'environment-name' => 'production'},
  65 + :backtrace => [{ :error => 'Le Broken' }], :notifier => { 'name' => 'Notifier', 'version' => '1', 'url' => 'http://toad.com' })
  66 + end
  67 + end
  68 +
46 end 69 end
spec/models/notification_service/campfire_service_spec.rb 0 → 100644
@@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
  1 +require 'spec_helper'
  2 +
  3 +describe NotificationService::CampfireService do
  4 + it "it should send a notification to campfire" do
  5 + # setup
  6 + notice = Fabricate :notice
  7 + notification_service = Fabricate :campfire_notification_service, :app => notice.app
  8 + problem = notice.problem
  9 +
  10 + #campy stubbing
  11 + campy = mock('CampfireService')
  12 + Campy::Room.stub(:new).and_return(campy)
  13 + campy.stub(:speak) { true }
  14 +
  15 + #assert
  16 + campy.should_receive(:speak)
  17 +
  18 + notification_service.create_notification(problem)
  19 + end
  20 +end
  21 +