whoops 0.4.0 → 0.5.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.
@@ -1,5 +1,4 @@
1
- class EventGroupsController < ApplicationController
2
- layout 'whoops'
1
+ class EventGroupsController < WhoopsController
3
2
  before_filter :update_event_group_filter
4
3
  helper_method :event_group_filter
5
4
 
@@ -25,11 +24,13 @@ class EventGroupsController < ApplicationController
25
24
  end
26
25
 
27
26
  def event_group_filter
28
- session[:event_group_filter] ||= Whoops::Filter.new
27
+ session[:event_group_filter] ||= new_whoops_filter
29
28
  end
30
29
 
31
30
  def event_group_filter=(filter)
32
- session[:event_group_filter] = Whoops::Filter.new_from_params(filter)
31
+ filter = Whoops::Filter.new_from_params(filter)
32
+ filter.authorized_service_lookup = authorized_service_lookup
33
+ session[:event_group_filter] = filter
33
34
  end
34
35
 
35
36
  def update
@@ -1,8 +1,6 @@
1
- class EventsController < ApplicationController
2
- layout 'whoops'
3
-
1
+ class EventsController < WhoopsController
4
2
  def index
5
- @event_group = Whoops::EventGroup.find(params[:whoops_event_group_id])
3
+ @event_group = Whoops::EventGroup.where(new_whoops_filter.to_query_document).find(params[:whoops_event_group_id])
6
4
 
7
5
  events_base = @event_group.events
8
6
  unless params[:query].blank?
@@ -22,7 +20,7 @@ class EventsController < ApplicationController
22
20
 
23
21
  # TODO break this out into a more metal-y controller
24
22
  def create
25
- Whoops::Event.record(params[:event])
23
+ Whoops::NewEvent.new(params[:event]).record!
26
24
  render :status => 200, :nothing => true
27
25
  end
28
26
  end
@@ -1,6 +1,4 @@
1
- class NotificationSubscriptionsController < ApplicationController
2
- layout 'whoops'
3
-
1
+ class NotificationSubscriptionsController < WhoopsController
4
2
  def index
5
3
  @notification_subscription = Whoops::NotificationSubscription.new
6
4
  @notification_subscription.build_filter
@@ -0,0 +1,17 @@
1
+ class WhoopsController < ApplicationController
2
+ layout 'whoops'
3
+
4
+ private
5
+ helper_method :authorized_service_lookup
6
+
7
+ # overwrite this to implement rules for hiding services
8
+ def authorized_service_lookup
9
+ @authorized_service_lookup ||= Whoops::AuthorizedServiceLookup.new(nil)
10
+ end
11
+
12
+ def new_whoops_filter
13
+ filter = Whoops::Filter.new
14
+ filter.authorized_service_lookup = authorized_service_lookup
15
+ filter
16
+ end
17
+ end
@@ -11,7 +11,7 @@ module EventGroupsHelper
11
11
  # group services by root, eg "sv1.web" and "sv1.resque" are in the
12
12
  # same sub array
13
13
  previous_service_root = ""
14
- Whoops::EventGroup.services.to_a.sort.each { |service|
14
+ authorized_service_lookup.filter_authorized(Whoops::EventGroup.services).to_a.sort.each { |service|
15
15
  service_root = (/(.*?)\./ =~ service && $~[1]) || service
16
16
  if service_root == previous_service_root
17
17
  @filter_field_allowed_values["service"].last << service
@@ -0,0 +1,45 @@
1
+ # This class is meant to be overwritten in host applications
2
+ #
3
+ # Authorized Service Lookups interact with filters to limit the
4
+ # services which a user is allowed to see. This allows host
5
+ # applications to more easily implement authorization.
6
+ #
7
+ # For example, if your Whoops installation is used by many different
8
+ # teams, but you don't want the teams to see each others' data, you
9
+ # could create a mapping between the team members' email addresses and
10
+ # the services they're allowed to see.
11
+ #
12
+ # Since filters are used when viewing events or sending notifications,
13
+ # the authorized service lookup allows you to modify the filters to
14
+ # prevent the unauthorized services from being seen
15
+ class Whoops::AuthorizedServiceLookup
16
+
17
+ # @param key the value used to look up authorized services
18
+ def initialize(key)
19
+ @key = key
20
+ end
21
+
22
+ # if there are services given, then show all services
23
+ # however, if we're looking at authorized services, then "all
24
+ # services" means "all authorized services"
25
+ #
26
+ # if there is a filter on service then only allow authorized
27
+ # services
28
+ #
29
+ # one thing to note is that if both services and authorized_services
30
+ # are blank, then no filter will be applied to service at all
31
+ def filter_authorized(services)
32
+ matches = services.select{ |s| service_authorized?(s) }
33
+ matches.empty? ? authorized_services : matches
34
+ end
35
+
36
+ # Overwrite this in your subclasses if you want to implement
37
+ # authorized services
38
+ def authorized_services
39
+ []
40
+ end
41
+
42
+ def service_authorized?(service)
43
+ authorized_services.blank? || /^(#{authorized_services.join("|")})/ =~ service
44
+ end
45
+ end
@@ -15,18 +15,6 @@ class Whoops::Event
15
15
 
16
16
  before_save :set_keywords, :sanitize_details
17
17
 
18
- def self.record(params)
19
- params = params.with_indifferent_access
20
-
21
- event_group_params = params.slice(*Whoops::EventGroup.field_names)
22
- event_group_params[:last_recorded_at] = params[:event_time]
23
- event_group_params
24
- event_group = Whoops::EventGroup.handle_new_event(event_group_params)
25
-
26
- event_params = params.slice(*Whoops::Event.field_names)
27
- event_group.events.create(event_params)
28
- end
29
-
30
18
  def self.search(query)
31
19
  conditions = Whoops::MongoidSearchParser.new(query).conditions
32
20
  where(conditions)
@@ -17,28 +17,6 @@ class Whoops::EventGroup
17
17
  field :last_recorded_at, :type => DateTime
18
18
  field :archived, :type => Boolean, :default => false
19
19
  field :event_count, :type => Integer, :default => 0
20
-
21
- class << self
22
- def handle_new_event(params)
23
- identifying_params = params.slice(*Whoops::EventGroup.identifying_fields)
24
- event_group = Whoops::EventGroup.first(:conditions => identifying_params)
25
-
26
- if event_group
27
- event_group.attributes = params
28
- else
29
- event_group = Whoops::EventGroup.new(params)
30
- end
31
-
32
- if event_group.valid?
33
- event_group.send_notifications
34
- event_group.archived = false
35
- event_group.event_count += 1
36
- event_group.save
37
- end
38
-
39
- event_group
40
- end
41
- end
42
20
 
43
21
  has_many :events, :class_name => "Whoops::Event"
44
22
 
@@ -52,14 +30,4 @@ class Whoops::EventGroup
52
30
  def self.services
53
31
  all.distinct(:service).sort
54
32
  end
55
-
56
- def should_send_notifications?
57
- (archived || new_record) && Rails.application.config.whoops_sender
58
- end
59
-
60
- def send_notifications
61
- return unless should_send_notifications?
62
- matcher = Whoops::NotificationSubscription::Matcher.new(self)
63
- Whoops::NotificationMailer.event_notification(self, matcher.matching_emails).deliver unless matcher.matching_emails.empty?
64
- end
65
33
  end
@@ -1,4 +1,6 @@
1
1
  class Whoops::Filter
2
+ attr_accessor :authorized_service_lookup
3
+
2
4
  include Mongoid::Document
3
5
  include FieldNames
4
6
 
@@ -13,7 +15,7 @@ class Whoops::Filter
13
15
  def to_query_document
14
16
  doc = attributes.except(:_id, "_id", :_type, "_type").delete_if{|k, v| v.blank?}
15
17
  # match all services under namespace. ie, if "app" given, match "app.web", "app.backend" etc
16
- doc["service"] = doc["service"].collect{ |d| /^#{d}/ } if doc["service"]
18
+ doc["service"] = service.collect{ |d| /^#{d}/ } unless service.blank?
17
19
  doc.inject({}) do |hash, current|
18
20
  hash[current.first.to_sym.in] = current.last unless current.last.empty?
19
21
  hash
@@ -34,6 +36,24 @@ class Whoops::Filter
34
36
  update_attributes(self.class.clean_params(params))
35
37
  end
36
38
 
39
+ def service
40
+ if authorized_services_provided?
41
+ authorized_services
42
+ else
43
+ attributes["service"]
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def authorized_services_provided?
50
+ authorized_service_lookup
51
+ end
52
+
53
+ def authorized_services
54
+ authorized_service_lookup.filter_authorized(attributes["service"].to_a)
55
+ end
56
+
37
57
  class << self
38
58
  def new_from_params(params)
39
59
  if params
@@ -0,0 +1,55 @@
1
+ # Receives new event params and processes them
2
+ class Whoops::NewEvent
3
+ def initialize(params)
4
+ @params = params.with_indifferent_access
5
+ end
6
+
7
+ # both records and sends notifications
8
+ def record!
9
+ find_or_build_event_group
10
+ update_event_group_attributes
11
+ send_notifications_for_event_group
12
+
13
+ @event_group.archived = false
14
+ @event_group.save
15
+ @event_group.events.create(event_params)
16
+ @event_group
17
+ end
18
+
19
+ private
20
+
21
+ def find_or_build_event_group
22
+ @event_group = Whoops::EventGroup.first(:conditions => event_group_identifying_fields) || Whoops::EventGroup.new(event_group_params)
23
+ end
24
+
25
+ def update_event_group_attributes
26
+ if @event_group.valid?
27
+ @event_group.attributes = event_group_params
28
+ @event_group.last_recorded_at = Time.now
29
+ @event_group.event_count += 1
30
+ end
31
+ end
32
+
33
+ def should_send_notifications?
34
+ @event_group.valid? && (@event_group.archived || @event_group.new_record) && Rails.application.config.whoops_sender
35
+ end
36
+
37
+ def send_notifications_for_event_group
38
+ return unless should_send_notifications?
39
+ matcher = Whoops::NotificationSubscription::Matcher.new(@event_group)
40
+ Whoops::NotificationMailer.event_notification(@event_group, matcher.matching_emails).deliver unless matcher.matching_emails.empty?
41
+ end
42
+
43
+ # TODO does it make sense to have a separate params object?
44
+ def event_group_params
45
+ @params.slice(*Whoops::EventGroup.field_names)
46
+ end
47
+
48
+ def event_group_identifying_fields
49
+ @params.slice(*Whoops::EventGroup.identifying_fields)
50
+ end
51
+
52
+ def event_params
53
+ @params.slice(*Whoops::Event.field_names)
54
+ end
55
+ end
@@ -21,7 +21,10 @@ class Whoops::NotificationSubscription
21
21
  end
22
22
 
23
23
  def matching_emails
24
- Whoops::NotificationSubscription.all.select{ |ns| ns.filter.matches_event_group?(self.event_group) }.collect(&:email)
24
+ Whoops::NotificationSubscription.all.select do |ns|
25
+ lookup = Whoops::AuthorizedServiceLookup.new(ns.email)
26
+ ns.filter.matches_event_group?(self.event_group)
27
+ end.collect(&:email)
25
28
  end
26
29
  end
27
30
  end
@@ -1,4 +1,4 @@
1
- - filter_field_allowed_values.each do |field_name, allowed_value_groups|
1
+ - %w{service environment event_type}.map{|k| [k, filter_field_allowed_values[k]]}.each do |field_name, allowed_value_groups|
2
2
  %h4= field_name
3
3
  %ul.filter-selects.inputs-list
4
4
  - allowed_value_groups.each do |allowed_values|
@@ -9,4 +9,4 @@
9
9
  = allowed_value
10
10
  - unless allowed_values == allowed_value_groups.last
11
11
  %li.divider
12
- %hr
12
+ %hr
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: 15
4
+ hash: 11
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 4
8
+ - 5
9
9
  - 0
10
- version: 0.4.0
10
+ version: 0.5.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-11-28 00:00:00 Z
18
+ date: 2012-12-03 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: rails
@@ -187,17 +187,19 @@ files:
187
187
  - app/controllers/event_groups_controller.rb
188
188
  - app/controllers/events_controller.rb
189
189
  - app/controllers/notification_subscriptions_controller.rb
190
+ - app/controllers/whoops_controller.rb
190
191
  - app/helpers/event_groups_helper.rb
191
192
  - app/helpers/events_helper.rb
192
193
  - app/helpers/filters_helper.rb
193
194
  - app/helpers/notifications_helper.rb
194
195
  - app/lib/field_names.rb
195
196
  - app/mailers/whoops/notification_mailer.rb
197
+ - app/models/whoops/authorized_service_lookup.rb
196
198
  - app/models/whoops/event.rb
197
199
  - app/models/whoops/event_group.rb
198
200
  - app/models/whoops/filter.rb
199
201
  - app/models/whoops/mongoid_search_parser.rb
200
- - app/models/whoops/notification_rule.rb
202
+ - app/models/whoops/new_event.rb
201
203
  - app/models/whoops/notification_subscription.rb
202
204
  - app/views/event_groups/_list.html.haml
203
205
  - app/views/event_groups/index.html.haml
@@ -1,63 +0,0 @@
1
- class Whoops::NotificationRule
2
- include Mongoid::Document
3
-
4
- field :email, :type => String
5
- field :matchers, :type => Array
6
-
7
- validates_presence_of :email
8
-
9
- # This might come in handy in the future?
10
- # class << self.fields["matchers"]
11
- # def set(object)
12
- # object = object.split("\n").collect{ |m| m.strip } if object.is_a?(String)
13
- # object
14
- # end
15
- # end
16
-
17
- before_save :downcase_email
18
-
19
- def downcase_email
20
- self.email.downcase!
21
- end
22
-
23
- def matchers=(matchers)
24
- write_attribute(:matchers, split_matchers(matchers).sort)
25
- end
26
-
27
- def add_matchers(new_matchers)
28
- split = split_matchers(new_matchers)
29
- write_attribute(:matchers, (self.matchers | split).sort)
30
- self.save
31
- end
32
-
33
- def split_matchers(new_matchers)
34
- new_matchers.split("\n").collect{ |m| m.strip }
35
- end
36
-
37
- def self.add_rules(params)
38
- params[:email] = params[:email].downcase
39
- if rule = first(:conditions => {:email => params[:email]})
40
- rule.add_matchers(params[:matchers])
41
- else
42
- create(params)
43
- end
44
- end
45
-
46
- class Matcher
47
- attr_accessor :event_group
48
-
49
- # @param [ Whoops::EventGroup ]
50
- def initialize(event_group)
51
- self.event_group = event_group
52
- end
53
-
54
- def matching_emails
55
- matches.collect{|m| m.email}.uniq
56
- end
57
-
58
- def matches
59
- service_matches = event_group.service.split(".").inject([]){|collection, part| collection << [collection.last, part].compact.join(".")}.join("|")
60
- @matches ||= Whoops::NotificationRule.where(:matchers => /^((#{service_matches})\S*$|(#{service_matches}).*#{event_group.environment})/)
61
- end
62
- end
63
- end