whoops 0.3.5 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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
-