whoops 0.3.5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile CHANGED
@@ -10,6 +10,7 @@ group :development do
10
10
  gem 'ruby-debug-base19', '0.11.23' if RUBY_VERSION.include? '1.9.1'
11
11
  gem 'ruby-debug19', :platforms => :ruby_19
12
12
  gem 'ruby-debug', :platforms => :mri_18
13
+ gem 'pry'
13
14
  end
14
15
  end
15
16
 
@@ -3,7 +3,7 @@ Whoops
3
3
  Daniel Higginbotham <daniel@flyingmachinestudios.com>
4
4
  2011-07-09
5
5
 
6
- image::https://secure.travis-ci.org/driv3r/whoops.png?branch=master[]
6
+ image::https://secure.travis-ci.org/driv3r/whoops.png?branch=master[link="https://secure.travis-ci.org/driv3r/whoops"]
7
7
 
8
8
  == What is Whoops?
9
9
 
@@ -1,8 +1,36 @@
1
1
  Whoops = {
2
2
  setupFilters: function() {
3
- $("#new_whoops_filter input").change(function(){
4
- $("#new_whoops_filter").submit()
5
- })
3
+ function checkboxBehavior(afterChecked) {
4
+ if (afterChecked === undefined) {
5
+ afterChecked = function(){}
6
+ }
7
+
8
+ return function(i, list){
9
+ var all = $($(list).find("input").get(0));
10
+ var allowedValues = $(list).find("input").slice(1);
11
+ var form = $(this).parents("form");
12
+
13
+ all.change(function(event){
14
+ if ($(this).attr("checked")) {
15
+ allowedValues.attr("checked", false);
16
+ afterChecked(form);
17
+ } else {
18
+ $(this).attr("checked", true);
19
+ }
20
+ })
21
+
22
+ $(allowedValues).change(function(event){
23
+ if ($(this).attr("checked")) {
24
+ all.attr("checked", false);
25
+ }
26
+ afterChecked(form);
27
+ })
28
+ }
29
+ }
30
+
31
+ $("#new_whoops_filter ul").each(checkboxBehavior(function(form){form.submit()}));
32
+ $("#new-notification-rule .filters ul").each(checkboxBehavior());
33
+
6
34
  $("#reset").click(function(){
7
35
  window.location = window.location.pathname
8
36
  return false
@@ -25,6 +25,10 @@ li {
25
25
  }
26
26
  }
27
27
 
28
+ #main {
29
+ padding-bottom:1em;
30
+ }
31
+
28
32
  /* sidebar */
29
33
  .container-fluid .sidebar {
30
34
  margin-left:-20px;
@@ -37,24 +41,28 @@ li {
37
41
  input[type=text] {
38
42
  width:190px;
39
43
  }
40
-
41
-
42
44
  }
43
45
 
44
46
  /* for kaminari pagination */
45
- .sidebar .pagination {
46
- margin:0;
47
+ .sidebar {
48
+ .pagination {
49
+ margin:0;
50
+
51
+ a, .current {
52
+ line-height: ($baseline * 2) - 2;
53
+ padding:0;
54
+ border:none;
55
+ display:block;
56
+ float:left;
57
+ margin-right:2px;
58
+ }
47
59
 
48
- a, .current {
49
- line-height: ($baseline * 2) - 2;
50
- padding:0;
51
- border:none;
52
- display:block;
53
- float:left;
54
- margin-right:2px;
60
+ .next, .first, .prev, .last { display:none; }
55
61
  }
62
+ }
56
63
 
57
- .next, .first, .prev, .last { display:none; }
64
+ .nofloat {
65
+ float:none;
58
66
  }
59
67
 
60
68
  .content .pagination {
@@ -122,8 +130,19 @@ li {
122
130
  }
123
131
  }
124
132
 
125
- .filter_selects label {
126
- cursor: pointer;
133
+ .filter-selects {
134
+ hr {
135
+ margin:0;
136
+ border-bottom: 1px dotted #ccc;
137
+ }
138
+
139
+ li.filter-option:hover {
140
+ background:#e8e5cc;
141
+ }
142
+
143
+ label {
144
+ cursor: pointer;
145
+ }
127
146
  }
128
147
 
129
148
  /* events for event group */
@@ -163,4 +182,17 @@ ul.detail {
163
182
 
164
183
  header form {
165
184
  float:right;
185
+ }
186
+
187
+ #notification-subscriptions {
188
+ .email {
189
+ width: 20%;
190
+ }
191
+
192
+ span {
193
+ padding:0 3px;
194
+ background: #cbdbe5;
195
+ margin-right:4px;
196
+ border-radius:3px;
197
+ }
166
198
  }
@@ -0,0 +1,33 @@
1
+ class NotificationSubscriptionsController < ApplicationController
2
+ layout 'whoops'
3
+
4
+ def index
5
+ @notification_subscription = Whoops::NotificationSubscription.new
6
+ @notification_subscription.build_filter
7
+ @notification_subscriptions = Whoops::NotificationSubscription.asc(:email)
8
+ @filter = Whoops::Filter.new
9
+ end
10
+
11
+ def create
12
+ ns = Whoops::NotificationSubscription.create(params[:notification_subscription])
13
+ ns.filter = Whoops::Filter.new_from_params(params[:whoops_filter])
14
+ ns.filter.save
15
+ redirect_to whoops_notification_subscriptions_path
16
+ end
17
+
18
+ def edit
19
+ @notification_subscription = Whoops::NotificationSubscription.find(params[:id])
20
+ end
21
+
22
+ def update
23
+ @notification_subscription = Whoops::NotificationSubscription.find(params[:id])
24
+ @notification_subscription.update_attributes(params[:notification_subscription])
25
+ @notification_subscription.filter.update_from_params(params[:whoops_filter])
26
+ redirect_to whoops_notification_subscriptions_path
27
+ end
28
+
29
+ def destroy
30
+ Whoops::NotificationSubscription.find(params[:id]).destroy
31
+ redirect_to whoops_notification_subscriptions_path
32
+ end
33
+ end
@@ -3,29 +3,36 @@ module EventGroupsHelper
3
3
  new_filter = {:whoops_filter => event_group_filter.to_query_document.merge(scope => event_group.send(scope))}
4
4
  link_to(event_group.send(scope), whoops_event_groups_path(new_filter))
5
5
  end
6
-
7
- # meant for consumption by options_from_collection_for_select
8
- def filter_options
9
- all_event_groups = Whoops::EventGroup.all
10
- return @filter_options if @filter_options
11
6
 
12
- @filter_options = Hash.new{|h, k| h[k] = []}
13
-
14
- @filter_options["service"] = Whoops::EventGroup.services.to_a
15
- @filter_options["environment"] = Whoops::EventGroup.all.distinct("environment")
16
- @filter_options["event_type"] = Whoops::EventGroup.all.distinct("event_type")
7
+ def filter_field_allowed_values
8
+ return @filter_field_allowed_values if @filter_field_allowed_values
9
+ @filter_field_allowed_values = Hash.new{|h, k| h[k] = [["all"]]}
17
10
 
18
- # add the field name as an empty option
19
- @filter_options.keys.each do |field_name|
20
- @filter_options[field_name].compact!
21
- @filter_options[field_name].sort!{|a, b| a.first <=> b.first}.uniq! if @filter_options[field_name]
22
- end
11
+ # group services by root, eg "sv1.web" and "sv1.resque" are in the
12
+ # same sub array
13
+ previous_service_root = ""
14
+ Whoops::EventGroup.services.to_a.sort.each { |service|
15
+ service_root = (/(.*?)\./ =~ service && $~[1]) || service
16
+ if service_root == previous_service_root
17
+ @filter_field_allowed_values["service"].last << service
18
+ else
19
+ @filter_field_allowed_values["service"] << ["#{service_root}.*", service]
20
+ previous_service_root = service_root
21
+ end
22
+ }
23
23
 
24
- @filter_options
24
+ @filter_field_allowed_values["environment"] << Whoops::EventGroup.all.distinct("environment")
25
+ @filter_field_allowed_values["event_type"] << Whoops::EventGroup.all.distinct("event_type")
26
+ @filter_field_allowed_values
25
27
  end
26
28
 
27
- def filter_checked?(field_name, option)
28
- filtered_field = session[:event_group_filter].send(field_name)
29
- filtered_field && filtered_field.include?(option)
29
+ def allowed_value_checked?(field_name, allowed_value, filter)
30
+ filtered_field = filter.send(field_name)
31
+ (allowed_value == "all" && filtered_field_allows_all?(filtered_field)) ||
32
+ filtered_field.try(:include?, allowed_value)
33
+ end
34
+
35
+ def filtered_field_allows_all?(filtered_field)
36
+ filtered_field.blank?
30
37
  end
31
38
  end
@@ -0,0 +1,5 @@
1
+ module FiltersHelper
2
+ def summarize_filter(arr)
3
+ arr.blank? ? "all" : arr.sort.collect{|x| "<span>#{x}</span>".html_safe }.join(" ").html_safe
4
+ end
5
+ end
@@ -2,11 +2,16 @@ class Whoops::NotificationMailer < ActionMailer::Base
2
2
  def event_notification(event_group, addresses)
3
3
  @event_group = event_group
4
4
  @addresses = addresses
5
+ body = <<-BODY
6
+ #{whoops_event_group_events_url(event_group.id)}
7
+
8
+ #{event_group.service}: #{event_group.environment}: #{event_group.message}
9
+ BODY
5
10
  mail(
6
11
  :to => addresses.join(", "),
7
12
  :from => Rails.application.config.whoops_sender,
8
13
  :subject => "Whoops Notification | #{event_group.service}: #{event_group.environment}: #{event_group.message}",
9
- :body => "#{event_group.service}: #{event_group.environment}: #{event_group.message}"
14
+ :body => body
10
15
  )
11
16
  end
12
17
  end
@@ -59,7 +59,7 @@ class Whoops::EventGroup
59
59
 
60
60
  def send_notifications
61
61
  return unless should_send_notifications?
62
- matcher = Whoops::NotificationRule::Matcher.new(self)
63
- Whoops::NotificationMailer.event_notification(self, matcher.matches.collect(&:email)).deliver unless matcher.matches.empty?
62
+ matcher = Whoops::NotificationSubscription::Matcher.new(self)
63
+ Whoops::NotificationMailer.event_notification(self, matcher.matching_emails).deliver unless matcher.matching_emails.empty?
64
64
  end
65
65
  end
@@ -1,13 +1,17 @@
1
1
  class Whoops::Filter
2
2
  include Mongoid::Document
3
3
  include FieldNames
4
+
5
+ FILTERED_FIELDS = [:service, :environment, :event_type, :message, :details]
4
6
 
5
- [:service, :environment, :event_type, :message, :details].each do |document_field|
7
+ FILTERED_FIELDS.each do |document_field|
6
8
  field document_field, :type => Array
7
9
  end
10
+
11
+ belongs_to :filterable, :polymorphic => true
8
12
 
9
13
  def to_query_document
10
- doc = attributes.except(:_id, "_id").delete_if{|k, v| v.blank?}
14
+ doc = attributes.except(:_id, "_id", :_type, "_type").delete_if{|k, v| v.blank?}
11
15
  # match all services under namespace. ie, if "app" given, match "app.web", "app.backend" etc
12
16
  doc["service"] = doc["service"].collect{ |d| /^#{d}/ } if doc["service"]
13
17
  doc.inject({}) do |hash, current|
@@ -16,13 +20,35 @@ class Whoops::Filter
16
20
  end
17
21
  end
18
22
 
23
+ def matches_event_group?(event_group)
24
+ FILTERED_FIELDS.all? do |field|
25
+ if self.send(field).blank?
26
+ true
27
+ else
28
+ /^(#{self.send(field).join("|")})$/ =~ event_group.send(field)
29
+ end
30
+ end
31
+ end
32
+
33
+ def update_from_params(params)
34
+ update_attributes(self.class.clean_params(params))
35
+ end
36
+
19
37
  class << self
20
38
  def new_from_params(params)
21
39
  if params
22
- new(params.inject({}){|hash, current| hash[current.first] = current.last.keys; hash})
40
+ f = new(clean_params(params))
23
41
  else
24
42
  new
25
43
  end
26
44
  end
27
- end
45
+
46
+ def clean_params(params)
47
+ params.inject({}){ |hash, current|
48
+ allowed_values = current.last.keys
49
+ hash[current.first] = allowed_values.include?("all") ? [] : allowed_values
50
+ hash
51
+ }
52
+ end
53
+ end
28
54
  end
@@ -0,0 +1,27 @@
1
+ class Whoops::NotificationSubscription
2
+ include Mongoid::Document
3
+
4
+ has_one :filter, :as => :filterable, :class_name => "Whoops::Filter"
5
+ validates_presence_of :email
6
+
7
+ field :email, :type => String
8
+
9
+ before_save :downcase_email
10
+
11
+ def downcase_email
12
+ self.email.downcase!
13
+ end
14
+
15
+ class Matcher
16
+ attr_accessor :event_group
17
+
18
+ # @param [ Whoops::EventGroup ]
19
+ def initialize(event_group)
20
+ self.event_group = event_group
21
+ end
22
+
23
+ def matching_emails
24
+ Whoops::NotificationSubscription.all.select{ |ns| ns.filter.matches_event_group?(self.event_group) }.collect(&:email)
25
+ end
26
+ end
27
+ end
@@ -3,14 +3,7 @@
3
3
  %h3 Filters
4
4
  = form_for event_group_filter, :url => whoops_event_groups_path, :html => {:method => "get"} do |f|
5
5
  = hidden_field_tag 'updating_filters', true
6
- - filter_options.each do |field_name, options|
7
- %h4= field_name
8
- %ul.filter_selects.inputs-list
9
- - options.each do |option|
10
- %li
11
- %label
12
- %input{:type => 'checkbox', :name => "whoops_filter[#{field_name}][#{option}]", :checked => filter_checked?(field_name, option)}
13
- = option
6
+ = render(:partial => "filters/filters", :locals => {:filter => session[:event_group_filter]} )
14
7
  .space
15
8
  %h3 Keyword Search
16
9
  = form_tag whoops_event_groups_path, :method => "get" do |f|
@@ -0,0 +1,12 @@
1
+ - filter_field_allowed_values.each do |field_name, allowed_value_groups|
2
+ %h4= field_name
3
+ %ul.filter-selects.inputs-list
4
+ - allowed_value_groups.each do |allowed_values|
5
+ - allowed_values.each do |allowed_value|
6
+ %li.filter-option
7
+ %label
8
+ %input{:type => 'checkbox', :name => "whoops_filter[#{field_name}][#{allowed_value}]", :checked => allowed_value_checked?(field_name, allowed_value, filter)}
9
+ = allowed_value
10
+ - unless allowed_values == allowed_value_groups.last
11
+ %li.divider
12
+ %hr
@@ -0,0 +1,9 @@
1
+ .services
2
+ %strong services:
3
+ = summarize_filter(filter.service)
4
+ .environments
5
+ %strong environments:
6
+ = summarize_filter(filter.environment)
7
+ .event_types
8
+ %strong event types:
9
+ = summarize_filter(filter.event_type)
@@ -15,9 +15,9 @@
15
15
  .listfill
16
16
  .container-fluid
17
17
  = link_to "Event Groups", whoops_event_groups_path, :class => "brand"
18
- = link_to "Notification Rules", whoops_notification_rules_path, :class => "brand"
18
+ = link_to "Notification Subscriptions", whoops_notification_subscriptions_path, :class => "brand"
19
19
  = link_to "Whoops Documentation", "http://www.whoopsapp.com/", :class => "documentation"
20
- .container-fluid
20
+ .container-fluid#main
21
21
  .sidebar
22
22
  = yield :sidebar
23
23
  .content
@@ -0,0 +1,11 @@
1
+ - new_record = @notification_subscription.new_record?
2
+ - options = new_record ? {:url => whoops_notification_subscriptions_path, :method => :post} : {:url => whoops_notification_subscription_path(@notification_subscription), :method => :put}
3
+ = form_for :notification_subscription, options do |f|
4
+ #new-notification-rule
5
+ %label.nofloat
6
+ Email
7
+ %br
8
+ =f.text_field :email
9
+ .filters
10
+ = render(:partial => "filters/filters", :locals => {:filter => @notification_subscription.filter} )
11
+ = submit_tag (new_record ? "Create" : "Update"), :class => "btn primary"
@@ -0,0 +1,8 @@
1
+ %h3 Edit Notification Subscription for #{@notification_subscription.email}
2
+
3
+ = render :partial => "form"
4
+
5
+ %h4 Delete This Rule
6
+
7
+ = form_for :notification_subscription, :method => :delete, :url => whoops_notification_subscription_path(@notification_subscription) do |f|
8
+ = f.submit :class => "btn", :value => "Delete it Forever! There's no going back!"
@@ -0,0 +1,19 @@
1
+ %h3 Notification Subscriptions
2
+
3
+ - content_for :sidebar do
4
+ .space
5
+ %h3 New Subscription
6
+ = render :partial => "form"
7
+
8
+
9
+ %table#notification-subscriptions
10
+ %tr
11
+ %th.email Email Address
12
+ %th.filter Filter
13
+ - @notification_subscriptions.each do |ns|
14
+ %tr
15
+ %td.email= ns.email
16
+ %td.filter= render :partial => "filters/summary", :locals => {:filter => ns.filter}
17
+ %td.update= link_to "update", edit_whoops_notification_subscription_path(ns)
18
+
19
+
@@ -4,5 +4,5 @@ Rails.application.routes.draw do
4
4
  end
5
5
 
6
6
  resources :events, :as => "whoops_events"
7
- resources :notification_rules, :as => "whoops_notification_rules"
7
+ resources :notification_subscriptions, :as => "whoops_notification_subscriptions"
8
8
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: whoops
3
3
  version: !ruby/object:Gem::Version
4
- hash: 25
4
+ hash: 15
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 3
9
- - 5
10
- version: 0.3.5
8
+ - 4
9
+ - 0
10
+ version: 0.4.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Daniel Higginbotham
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-08-29 00:00:00 Z
18
+ date: 2012-11-28 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: rails
@@ -175,7 +175,6 @@ files:
175
175
  - app/assets/stylesheets/bootstrap.scss
176
176
  - app/assets/stylesheets/forms.scss
177
177
  - app/assets/stylesheets/ie.css
178
- - app/assets/stylesheets/main.css.scss
179
178
  - app/assets/stylesheets/mixins.scss
180
179
  - app/assets/stylesheets/patterns.scss
181
180
  - app/assets/stylesheets/reset.scss
@@ -187,9 +186,10 @@ files:
187
186
  - app/assets/stylesheets/whoops.css.scss
188
187
  - app/controllers/event_groups_controller.rb
189
188
  - app/controllers/events_controller.rb
190
- - app/controllers/notification_rules_controller.rb
189
+ - app/controllers/notification_subscriptions_controller.rb
191
190
  - app/helpers/event_groups_helper.rb
192
191
  - app/helpers/events_helper.rb
192
+ - app/helpers/filters_helper.rb
193
193
  - app/helpers/notifications_helper.rb
194
194
  - app/lib/field_names.rb
195
195
  - app/mailers/whoops/notification_mailer.rb
@@ -198,15 +198,18 @@ files:
198
198
  - app/models/whoops/filter.rb
199
199
  - app/models/whoops/mongoid_search_parser.rb
200
200
  - app/models/whoops/notification_rule.rb
201
+ - app/models/whoops/notification_subscription.rb
201
202
  - app/views/event_groups/_list.html.haml
202
203
  - app/views/event_groups/index.html.haml
203
204
  - app/views/events/_detail.html.haml
204
205
  - app/views/events/_details.html.haml
205
206
  - app/views/events/index.html.haml
207
+ - app/views/filters/_filters.html.haml
208
+ - app/views/filters/_summary.html.haml
206
209
  - app/views/layouts/whoops.html.haml
207
- - app/views/notification_rules/_form.html.haml
208
- - app/views/notification_rules/edit.html.haml
209
- - app/views/notification_rules/index.html.haml
210
+ - app/views/notification_subscriptions/_form.html.haml
211
+ - app/views/notification_subscriptions/edit.html.haml
212
+ - app/views/notification_subscriptions/index.html.haml
210
213
  - lib/tasks/whoops.rake
211
214
  - lib/whoops/engine.rb
212
215
  - lib/whoops.rb
@@ -251,3 +254,4 @@ specification_version: 3
251
254
  summary: A Rails engine which receives logs and provides an interface for them
252
255
  test_files: []
253
256
 
257
+ has_rdoc:
File without changes
@@ -1,29 +0,0 @@
1
- class NotificationRulesController < ApplicationController
2
- layout 'whoops'
3
-
4
- def index
5
- @notification_rule = Whoops::NotificationRule.new
6
- @notification_rules = Whoops::NotificationRule.asc(:email)
7
- end
8
-
9
- def create
10
- Whoops::NotificationRule.add_rules(params[:notification_rule])
11
- redirect_to whoops_notification_rules_path
12
- end
13
-
14
- def edit
15
- @notification_rule = Whoops::NotificationRule.find(params[:id])
16
- end
17
-
18
- def update
19
- @notification_rule = Whoops::NotificationRule.find(params[:id])
20
- @notification_rule.update_attributes(params[:notification_rule])
21
- notification_rules = Whoops::NotificationRule.asc(:email)
22
- redirect_to whoops_notification_rules_path
23
- end
24
-
25
- def destroy
26
- Whoops::NotificationRule.find(params[:id]).destroy
27
- redirect_to whoops_notification_rules_path
28
- end
29
- end
@@ -1,15 +0,0 @@
1
- %h3 New
2
- - options = @notification_rule.new_record? ? {:url => whoops_notification_rules_path, :method => :post} : {:url => whoops_notification_rule_path(@notification_rule), :method => :put}
3
- = form_for :notification_rule, options do |f|
4
- #new-notification-rule
5
- %p
6
- Email
7
- %br
8
- =f.text_field :email
9
- %p
10
- Matchers
11
- %br
12
- = f.text_area :matchers, :value => @notification_rule.matchers.to_a.join("\n")
13
- %p= f.submit(:class => "primary btn", :value => "Save Notification Rule")
14
-
15
- %p= link_to "Back to notification rules", whoops_notification_rules_path
@@ -1,8 +0,0 @@
1
- %h3 Edit Notification Rules for #{@notification_rule.email}
2
-
3
- = render :partial => "form"
4
-
5
- %h4 Delete This Rule
6
-
7
- = form_for :notification_rule, :method => :delete, :url => whoops_notification_rule_path(@notification_rule) do |f|
8
- = f.submit :class => "btn", :value => "Delete it Forever! There's no going back!"
@@ -1,21 +0,0 @@
1
- %h3 Notification Rules
2
-
3
- - content_for :sidebar do
4
- .space
5
- = render :partial => "form"
6
-
7
-
8
-
9
- %table
10
- %tr
11
- %th Email Address
12
- %th Services to Monitor
13
- %th
14
-
15
- - @notification_rules.each do |nr|
16
- %tr
17
- %td= nr.email
18
- %td= nr.matchers.sort.join(", ")
19
- %td= link_to "update", edit_whoops_notification_rule_path(nr)
20
-
21
-