whoops 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/Gemfile +2 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +25 -0
  5. data/app/controllers/event_groups_controller.rb +34 -0
  6. data/app/controllers/events_controller.rb +24 -0
  7. data/app/helpers/event_groups_helper.rb +32 -0
  8. data/app/helpers/events_helper.rb +28 -0
  9. data/app/helpers/notifications_helper.rb +2 -0
  10. data/app/lib/field_names.rb +9 -0
  11. data/app/models/whoops/event.rb +27 -0
  12. data/app/models/whoops/event_group.rb +32 -0
  13. data/app/models/whoops/filter.rb +15 -0
  14. data/app/views/event_groups/_list.html.haml +19 -0
  15. data/app/views/event_groups/index.html.haml +14 -0
  16. data/app/views/events/_detail.html.haml +5 -0
  17. data/app/views/events/_details.html.haml +8 -0
  18. data/app/views/events/index.html.haml +25 -0
  19. data/app/views/layouts/whoops.html.haml +39 -0
  20. data/config/initializers/date_formats.rb +3 -0
  21. data/config/routes.rb +7 -0
  22. data/lib/generators/whoops/assets_generator.rb +16 -0
  23. data/lib/generators/whoops/templates/assets/images/_ui/breadcrumb_divider.png +0 -0
  24. data/lib/generators/whoops/templates/assets/images/_ui/btn_submit.png +0 -0
  25. data/lib/generators/whoops/templates/assets/images/_ui/btn_submit_2.png +0 -0
  26. data/lib/generators/whoops/templates/assets/images/_ui/btn_view_site.png +0 -0
  27. data/lib/generators/whoops/templates/assets/images/_ui/header_bg.png +0 -0
  28. data/lib/generators/whoops/templates/assets/images/_ui/header_shadow.png +0 -0
  29. data/lib/generators/whoops/templates/assets/images/_ui/icn_add_user.png +0 -0
  30. data/lib/generators/whoops/templates/assets/images/_ui/icn_alert_error.png +0 -0
  31. data/lib/generators/whoops/templates/assets/images/_ui/icn_alert_info.png +0 -0
  32. data/lib/generators/whoops/templates/assets/images/_ui/icn_alert_success.png +0 -0
  33. data/lib/generators/whoops/templates/assets/images/_ui/icn_alert_warning.png +0 -0
  34. data/lib/generators/whoops/templates/assets/images/_ui/icn_audio.png +0 -0
  35. data/lib/generators/whoops/templates/assets/images/_ui/icn_categories.png +0 -0
  36. data/lib/generators/whoops/templates/assets/images/_ui/icn_edit.png +0 -0
  37. data/lib/generators/whoops/templates/assets/images/_ui/icn_edit_article.png +0 -0
  38. data/lib/generators/whoops/templates/assets/images/_ui/icn_folder.png +0 -0
  39. data/lib/generators/whoops/templates/assets/images/_ui/icn_jump_back.png +0 -0
  40. data/lib/generators/whoops/templates/assets/images/_ui/icn_logout.png +0 -0
  41. data/lib/generators/whoops/templates/assets/images/_ui/icn_new_article.png +0 -0
  42. data/lib/generators/whoops/templates/assets/images/_ui/icn_photo.png +0 -0
  43. data/lib/generators/whoops/templates/assets/images/_ui/icn_profile.png +0 -0
  44. data/lib/generators/whoops/templates/assets/images/_ui/icn_search.png +0 -0
  45. data/lib/generators/whoops/templates/assets/images/_ui/icn_security.png +0 -0
  46. data/lib/generators/whoops/templates/assets/images/_ui/icn_settings.png +0 -0
  47. data/lib/generators/whoops/templates/assets/images/_ui/icn_tags.png +0 -0
  48. data/lib/generators/whoops/templates/assets/images/_ui/icn_trash.png +0 -0
  49. data/lib/generators/whoops/templates/assets/images/_ui/icn_user.png +0 -0
  50. data/lib/generators/whoops/templates/assets/images/_ui/icn_video.png +0 -0
  51. data/lib/generators/whoops/templates/assets/images/_ui/icn_view_users.png +0 -0
  52. data/lib/generators/whoops/templates/assets/images/_ui/module_footer_bg.png +0 -0
  53. data/lib/generators/whoops/templates/assets/images/_ui/post_message.png +0 -0
  54. data/lib/generators/whoops/templates/assets/images/_ui/secondary_bar.png +0 -0
  55. data/lib/generators/whoops/templates/assets/images/_ui/secondary_bar_shadow.png +0 -0
  56. data/lib/generators/whoops/templates/assets/images/_ui/sidebar.png +0 -0
  57. data/lib/generators/whoops/templates/assets/images/_ui/sidebar_divider.png +0 -0
  58. data/lib/generators/whoops/templates/assets/images/_ui/sidebar_shadow.png +0 -0
  59. data/lib/generators/whoops/templates/assets/images/_ui/table_sorter_header.png +0 -0
  60. data/lib/generators/whoops/templates/assets/javascripts/application.js +35 -0
  61. data/lib/generators/whoops/templates/assets/javascripts/hideshow.js +39 -0
  62. data/lib/generators/whoops/templates/assets/javascripts/jquery-1.5.2.min.js +16 -0
  63. data/lib/generators/whoops/templates/assets/javascripts/jquery.equalHeight.js +20 -0
  64. data/lib/generators/whoops/templates/assets/javascripts/jquery.form.js +825 -0
  65. data/lib/generators/whoops/templates/assets/javascripts/jquery.tablesorter.min.js +4 -0
  66. data/lib/generators/whoops/templates/assets/javascripts/ui.js +28 -0
  67. data/lib/generators/whoops/templates/assets/stylesheets/ie.css +49 -0
  68. data/lib/generators/whoops/templates/assets/stylesheets/sass/_base.scss +18 -0
  69. data/lib/generators/whoops/templates/assets/stylesheets/sass/ie.scss +43 -0
  70. data/lib/generators/whoops/templates/assets/stylesheets/sass/screen.scss +945 -0
  71. data/lib/generators/whoops/templates/assets/stylesheets/screen.css +1082 -0
  72. data/lib/whoops.rb +5 -0
  73. data/lib/whoops/engine.rb +4 -0
  74. metadata +356 -0
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
@@ -0,0 +1,20 @@
1
+ Copyright 2011 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,3 @@
1
+ = Whoops
2
+
3
+ This project rocks and uses MIT-LICENSE.
@@ -0,0 +1,25 @@
1
+ # encoding: UTF-8
2
+ require 'rubygems'
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rake'
10
+ require 'rake/rdoctask'
11
+
12
+ require 'rspec/core'
13
+ require 'rspec/core/rake_task'
14
+
15
+ RSpec::Core::RakeTask.new(:spec)
16
+
17
+ task :default => :spec
18
+
19
+ Rake::RDocTask.new(:rdoc) do |rdoc|
20
+ rdoc.rdoc_dir = 'rdoc'
21
+ rdoc.title = 'Whoops'
22
+ rdoc.options << '--line-numbers' << '--inline-source'
23
+ rdoc.rdoc_files.include('README.rdoc')
24
+ rdoc.rdoc_files.include('lib/**/*.rb')
25
+ end
@@ -0,0 +1,34 @@
1
+ class EventGroupsController < ApplicationController
2
+ layout 'whoops'
3
+ before_filter :update_event_group_filter
4
+ helper_method :event_group_filter
5
+
6
+ def index
7
+ @event_groups = Whoops::EventGroup.where(event_group_filter.to_query_document).desc(:last_recorded_at).paginate(
8
+ :page => params[:page],
9
+ :per_page => 20
10
+ )
11
+
12
+ respond_to do |format|
13
+ format.html
14
+ format.js { render :partial => 'list' }
15
+ end
16
+ end
17
+
18
+ def show
19
+ @event_group = Whoops::EventGroup.find(params[:id])
20
+ end
21
+
22
+ def update_event_group_filter
23
+ self.event_group_filter = params[:whoops_filter] if params[:whoops_filter]
24
+ end
25
+
26
+ def event_group_filter
27
+ session[:event_group_filter] ||= Whoops::Filter.new
28
+ end
29
+
30
+ def event_group_filter=(filter)
31
+ session[:event_group_filter] = Whoops::Filter.new(filter)
32
+ end
33
+
34
+ end
@@ -0,0 +1,24 @@
1
+ class EventsController < ApplicationController
2
+ layout 'whoops'
3
+
4
+ def index
5
+ @event_group = Whoops::EventGroup.find(params[:whoops_event_group_id])
6
+ @events = @event_group.events.desc(:event_time).paginate(
7
+ :page => params[:page],
8
+ :per_page => 20
9
+ )
10
+ end
11
+
12
+ def show
13
+ @event = Whoops::Event.find(params[:id])
14
+ respond_to do |format|
15
+ format.js { render :partial => 'details', :object => @event, :as => :event}
16
+ end
17
+ end
18
+
19
+ # TODO break this out into a more metal-y controller
20
+ def create
21
+ Whoops::Event.record(params[:event])
22
+ render :status => 200, :nothing => true
23
+ end
24
+ end
@@ -0,0 +1,32 @@
1
+ module EventGroupsHelper
2
+ def event_group_scoped_link(event_group, scope)
3
+ new_filter = {:whoops_filter => event_group_filter.to_query_document.merge(scope => event_group.send(scope))}
4
+ link_to(event_group.send(scope), whoops_event_groups_path(new_filter))
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
+
12
+ @filter_options = Hash.new{|h, k| h[k] = []}
13
+
14
+ @filter_options["service"] = Whoops::EventGroup.services.collect{|s| [s, s]}
15
+
16
+ all_event_groups.each do |event_group|
17
+ mundane_fields = (Whoops::Filter.field_names & Whoops::EventGroup.field_names) - ["service", "message"]
18
+ mundane_fields.each do |field_name|
19
+ @filter_options[field_name] << [event_group.send(field_name), event_group.send(field_name)]
20
+ end
21
+ end
22
+
23
+ # add the field name as an empty option
24
+ @filter_options.keys.each do |field_name|
25
+ @filter_options[field_name].compact!
26
+ @filter_options[field_name].sort!{|a, b| a.first <=> b.first}.uniq! if @filter_options[field_name]
27
+ @filter_options[field_name].unshift(["", "--" + field_name.humanize.downcase + " filter--"])
28
+ end
29
+
30
+ @filter_options
31
+ end
32
+ end
@@ -0,0 +1,28 @@
1
+ module EventsHelper
2
+ def format_detail(detail)
3
+ case detail
4
+ when String, Numeric: detail
5
+ when Array: simple_format(detail.join("\n"), :class => "simple")
6
+ when Hash: detail_table(detail)
7
+ else
8
+ detail.to_s
9
+ end
10
+ end
11
+
12
+ def detail_table(detail)
13
+ render :partial => 'events/detail', :object => detail
14
+ end
15
+
16
+ def breadcrumbs(*groups)
17
+ breadcrumbs = ""
18
+ groups.each do |group|
19
+ breadcrumbs << "<div class='breadcrumb_divider'></div>"
20
+ if groups.last == group
21
+ breadcrumbs << "<a class='current'>#{group.first}</a>"
22
+ else
23
+ breadcrumbs << link_to(group.first, group.last)
24
+ end
25
+ end
26
+ breadcrumbs.html_safe
27
+ end
28
+ end
@@ -0,0 +1,2 @@
1
+ module NotificationsHelper
2
+ end
@@ -0,0 +1,9 @@
1
+ module FieldNames
2
+ extend ActiveSupport::Concern
3
+
4
+ module ClassMethods
5
+ def field_names
6
+ self.fields.keys - ["_id", "_type"]
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,27 @@
1
+ class Whoops::Event
2
+ include Mongoid::Document
3
+ include FieldNames
4
+
5
+ belongs_to :event_group, :class_name => "Whoops::EventGroup"
6
+
7
+ field :details
8
+ field :message, :type => String
9
+ field :event_time, :type => DateTime
10
+
11
+ validates_presence_of :message
12
+
13
+ def self.record(params)
14
+ params = params.with_indifferent_access
15
+
16
+ event_group_params = params.slice(*Whoops::EventGroup.field_names)
17
+ event_group_params[:identifier] = params[:event_group_identifier]
18
+ event_group_params[:last_recorded_at] = params[:event_time]
19
+
20
+ event_group = Whoops::EventGroup.first(:conditions => event_group_params.slice(*Whoops::EventGroup.identifying_fields))
21
+ event_group ||= Whoops::EventGroup.create(event_group_params)
22
+
23
+ event_params = params.slice(*Whoops::Event.field_names)
24
+ event_group.events.create(event_params)
25
+ end
26
+
27
+ end
@@ -0,0 +1,32 @@
1
+ class Whoops::EventGroup
2
+ # notifier responsible for creating identifier from notice details
3
+ include Mongoid::Document
4
+ include FieldNames
5
+
6
+ [:service, :environment, :event_type, :message, :identifier].each do |string_field|
7
+ field string_field, :type => String
8
+ end
9
+ field :last_recorded_at, :type => DateTime
10
+
11
+ has_many :events, :class_name => "Whoops::Event"
12
+
13
+ validates_presence_of :identifier, :event_type, :service, :message
14
+
15
+ def self.identifying_fields
16
+ field_names - ["message", "last_recorded_at"]
17
+ end
18
+
19
+ # @return sorted set of all applicable namespaces
20
+ def self.services
21
+ services = SortedSet.new
22
+ previous_service = []
23
+ all(:sort => [[:service, :asc]]).each do |group|
24
+ services << group.service
25
+ split = group.service.split(".")
26
+ common = (previous_service & split)
27
+ services << common.join(".") unless common.blank?
28
+ previous_service = split
29
+ end
30
+ services
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ class Whoops::Filter
2
+ include Mongoid::Document
3
+ include FieldNames
4
+
5
+ [:service, :environment, :event_type, :message, :details].each do |document_field|
6
+ field document_field
7
+ end
8
+
9
+ def to_query_document
10
+ doc = attributes.except(:_id).delete_if{|k, v| v.blank?}
11
+ # match all services under namespace. ie, if "app" given, match "app.web", "app.backend" etc
12
+ doc[:service] = /#{doc[:service]}(\..*)?/ if doc[:service]
13
+ doc
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ = will_paginate(@event_groups)
2
+ %table.event-group-list
3
+ %thead
4
+ %tr
5
+ %th.header Service
6
+ %th.header Message
7
+ %th.header Type
8
+ %th.header Env
9
+ %th.header Last Occurrence
10
+ %th.header
11
+ - @event_groups.each do |event_group|
12
+ %tr
13
+ %td= event_group_scoped_link(event_group, :service)
14
+ %td= link_to event_group.message, whoops_event_group_events_path(event_group)
15
+ %td= event_group_scoped_link(event_group, :event_type)
16
+ %td= event_group_scoped_link(event_group, :environment)
17
+ %td= event_group.last_recorded_at.to_s(:whoops_default)
18
+ %td= event_group.events.count
19
+ = will_paginate(@event_groups)
@@ -0,0 +1,14 @@
1
+ - content_for :sidebar do
2
+ .space
3
+ %h3 Filters
4
+ - form_for event_group_filter, :url => whoops_event_groups_path, :html => {:method => "get"} do |f|
5
+ %ul.filter_selects
6
+ - filter_options.each do |field_name, options|
7
+ %li= f.select field_name, options_from_collection_for_select(options, :first, :last, event_group_filter.send(field_name))
8
+ %li
9
+ %button#reset reset
10
+
11
+ %article.module.width_full
12
+ %header
13
+ %h3 Events
14
+ = render :partial => 'list'
@@ -0,0 +1,5 @@
1
+ %ul.detail
2
+ - detail.to_a.sort{|a,b| a[0] <=> b[0]}.each do |sub_detail|
3
+ %li
4
+ %span.label= sub_detail.first + ":"
5
+ = format_detail(sub_detail.last)
@@ -0,0 +1,8 @@
1
+ %table.details
2
+ %tr
3
+ %td.label Message
4
+ %td.value= event.message
5
+ - event.details.to_a.sort{|a,b| a[0] <=> b[0]}.each do |detail|
6
+ %tr
7
+ %td.label= detail.first
8
+ %td.value= format_detail(detail.last)
@@ -0,0 +1,25 @@
1
+ - content_for :breadcrumbs do
2
+ = breadcrumbs(["Event Details", whoops_event_group_path(@event_group)])
3
+
4
+ - content_for :sidebar do
5
+ %h3 Instances
6
+ = will_paginate @event, :previous_label => "", :next_label => ""
7
+ %ul#instances
8
+ - @events.each_with_index do |event, index|
9
+ %li{:class => ("selected" if index == 0)}= link_to event.event_time.to_s(:whoops_default), event
10
+
11
+ %article.module.width_full
12
+ %header
13
+ %h3= @event_group.message
14
+ %table
15
+ - [:environment, :event_type, :last_recorded_at].each do |field|
16
+ %tr
17
+ %td.label= field.to_s.humanize
18
+ %td= @event_group.send(field)
19
+
20
+ %article.module.width_full
21
+ %header
22
+ %h3 Details
23
+ #event-details
24
+ = render :partial => "events/details", :object => @events.first, :as => :event
25
+
@@ -0,0 +1,39 @@
1
+ !!!
2
+ %html
3
+ %head
4
+ %title Whoops
5
+ = stylesheet_link_tag 'screen.css', :media => 'screen, projection'
6
+ /[if IE]
7
+ = stylesheet_link_tag 'ie.css', :media => 'screen, projection'
8
+
9
+ = javascript_include_tag 'jquery-1.5.2.min', 'jquery.equalHeight', 'jquery.tablesorter.min', 'application.js'
10
+ = yield :extra_javascripts
11
+ = csrf_meta_tag
12
+ %body
13
+ %header#header
14
+ %hgroup
15
+ %h1.site_title
16
+ = link_to "Whoops", whoops_event_groups_path
17
+ %h2.section_title
18
+
19
+ / end of header bar
20
+ %section#secondary_bar
21
+ .user
22
+ .breadcrumbs_container
23
+ %article.breadcrumbs
24
+ = link_to "All Events", whoops_event_groups_path, :class => ("current" if controller_name == 'event_groups')
25
+ = yield :breadcrumbs
26
+ / end of secondary bar
27
+ %aside#sidebar.column
28
+ = yield :sidebar
29
+ %footer
30
+ %hr/
31
+ %p
32
+ %strong Whoops!
33
+
34
+ / end of sidebar
35
+ %section#main.column
36
+ = yield
37
+ / end of styles article
38
+ .spacer
39
+
@@ -0,0 +1,3 @@
1
+ DateTime::DATE_FORMATS[:whoops_default]="%Y-%m-%d %H:%M"
2
+ Time::DATE_FORMATS[:whoops_default] = "%Y-%m-%d %H:%M"
3
+ Date::DATE_FORMATS[:whoops_default] = "%Y-%m-%d"
@@ -0,0 +1,7 @@
1
+ Rails.application.routes.draw do
2
+ resources :event_groups, :as => "whoops_event_groups" do
3
+ resources :events
4
+ end
5
+
6
+ resources :events, :as => "whoops_events"
7
+ end
@@ -0,0 +1,16 @@
1
+ require 'rails/generators'
2
+
3
+ module Whoops
4
+ class AssetsGenerator < Rails::Generators::Base
5
+ source_root File.expand_path('../templates', __FILE__)
6
+ desc 'Installs assets into your public directory.'
7
+
8
+ def install_assets
9
+ directory 'assets', 'public/'
10
+ end
11
+
12
+ def self.source_root
13
+ @source_root ||= File.join(File.dirname(__FILE__), 'templates')
14
+ end
15
+ end
16
+ end