whoops 0.4.0 → 0.5.0

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