Commit ba398235a740b4902fc2993b45a8e2f82b4530dd
Exists in
master
and in
1 other branch
Merge branch 'notifications' of https://github.com/amaabca/errbit into amaabca-notifications
Conflicts: Gemfile.lock
Showing
25 changed files
with
224 additions
and
24 deletions
Show diff stats
Gemfile
@@ -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' |
Gemfile.lock
@@ -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) |
3.19 KB
3.19 KB
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
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.<%= tracker %> { | @@ -14,3 +15,4 @@ div.issue_tracker.nested label.r_on.<%= tracker %> { | ||
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 < InheritedResources::Base | @@ -29,12 +29,14 @@ class AppsController < 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 < InheritedResources::Base | @@ -70,6 +72,7 @@ class AppsController < 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 < InheritedResources::Base | @@ -77,6 +80,15 @@ class AppsController < 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 < InheritedResources::Base | @@ -90,6 +102,7 @@ class AppsController < 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 < Mongoid::Observer | @@ -4,6 +4,11 @@ class NoticeObserver < 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 < Mongoid::Observer | @@ -15,5 +20,4 @@ class NoticeObserver < 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 |
@@ -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 |
@@ -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 |
@@ -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 => :issue_tracker, :class_name => "Issu | @@ -24,4 +24,3 @@ Fabricator :github_issues_tracker, :from => :issue_tracker, :class_name => "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 | - |
@@ -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 | + |