exception_hunter 0.1.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -7
  3. data/app/assets/stylesheets/exception_hunter/base.css +62 -8
  4. data/app/assets/stylesheets/exception_hunter/errors.css +166 -24
  5. data/app/assets/stylesheets/exception_hunter/navigation.css +20 -5
  6. data/app/assets/stylesheets/exception_hunter/sessions.css +71 -0
  7. data/app/controllers/concerns/exception_hunter/authorization.rb +23 -0
  8. data/app/controllers/exception_hunter/application_controller.rb +2 -0
  9. data/app/controllers/exception_hunter/errors_controller.rb +27 -4
  10. data/app/controllers/exception_hunter/resolved_errors_controller.rb +11 -0
  11. data/app/helpers/exception_hunter/errors_helper.rb +7 -0
  12. data/app/helpers/exception_hunter/sessions_helper.rb +16 -0
  13. data/app/models/exception_hunter/application_record.rb +8 -0
  14. data/app/models/exception_hunter/error.rb +21 -8
  15. data/app/models/exception_hunter/error_group.rb +24 -5
  16. data/app/presenters/exception_hunter/dashboard_presenter.rb +54 -0
  17. data/app/presenters/exception_hunter/error_group_presenter.rb +25 -0
  18. data/app/presenters/exception_hunter/error_presenter.rb +10 -1
  19. data/app/views/exception_hunter/devise/sessions/new.html.erb +24 -0
  20. data/app/views/exception_hunter/errors/_error_row.erb +44 -0
  21. data/app/views/exception_hunter/errors/_error_summary.erb +23 -10
  22. data/app/views/exception_hunter/errors/_error_user_data.erb +4 -5
  23. data/app/views/exception_hunter/errors/_errors_table.erb +1 -0
  24. data/app/views/exception_hunter/errors/_last_7_days_errors_table.erb +12 -0
  25. data/app/views/exception_hunter/errors/index.html.erb +71 -30
  26. data/app/views/exception_hunter/errors/pagy/_pagy_nav.html.erb +15 -15
  27. data/app/views/exception_hunter/errors/show.html.erb +58 -22
  28. data/app/views/layouts/exception_hunter/application.html.erb +65 -6
  29. data/app/views/layouts/exception_hunter/exception_hunter_logged_out.html.erb +24 -0
  30. data/config/rails_best_practices.yml +2 -3
  31. data/config/routes.rb +19 -1
  32. data/lib/exception_hunter.rb +12 -2
  33. data/lib/exception_hunter/config.rb +8 -1
  34. data/lib/exception_hunter/devise.rb +17 -0
  35. data/lib/exception_hunter/engine.rb +5 -0
  36. data/{app/services → lib}/exception_hunter/error_creator.rb +17 -5
  37. data/lib/exception_hunter/error_reaper.rb +12 -0
  38. data/lib/exception_hunter/middleware/delayed_job_hunter.rb +69 -0
  39. data/lib/exception_hunter/middleware/request_hunter.rb +71 -0
  40. data/lib/exception_hunter/middleware/sidekiq_hunter.rb +59 -0
  41. data/lib/exception_hunter/tracking.rb +17 -0
  42. data/lib/exception_hunter/user_attributes_collector.rb +4 -0
  43. data/lib/exception_hunter/version.rb +1 -1
  44. data/lib/generators/exception_hunter/create_users/create_users_generator.rb +8 -1
  45. data/lib/generators/exception_hunter/install/install_generator.rb +3 -1
  46. data/lib/generators/exception_hunter/install/templates/create_exception_hunter_error_groups.rb.erb +3 -0
  47. data/lib/generators/exception_hunter/install/templates/exception_hunter.rb.erb +23 -0
  48. data/lib/tasks/exception_hunter_tasks.rake +6 -4
  49. metadata +25 -10
  50. data/config/initializers/exception_hunter.rb +0 -16
  51. data/lib/exception_hunter/railtie.rb +0 -11
  52. data/lib/exception_hunter/request_hunter.rb +0 -41
@@ -3,9 +3,8 @@
3
3
  Unfortunately, no user information has been registered for this error.
4
4
  </div>
5
5
  <% else %>
6
- <% error.user_data.transform_keys(&:to_sym).each do |key, value| %>
7
- <div class="column column-15">
8
- <b><%= key %></b>: <%= value %>
9
- </div>
10
- <% end %>
6
+ <pre class="tracked-data">
7
+
8
+ <%= format_tracked_data(error.user_data) %>
9
+ </pre>
11
10
  <% end %>
@@ -0,0 +1 @@
1
+ <%= render partial: 'exception_hunter/errors/error_row', collection: errors, as: :error %>
@@ -0,0 +1,12 @@
1
+ <% today_errors = errors.select { |error| error.show_for_day?(Date.current) } %>
2
+ <%= render partial: 'exception_hunter/errors/error_row', collection: today_errors, as: :error %>
3
+
4
+ <% yesterday_errors = errors.select { |error| error.show_for_day?(Date.yesterday) } %>
5
+ <div class="errors-date-group">Yesterday</div>
6
+ <%= render partial: 'exception_hunter/errors/error_row', collection: yesterday_errors, as: :error %>
7
+
8
+ <% (2..6).each do |i| %>
9
+ <% errors_on_day = errors.select { |error| error.show_for_day?(i.days.ago) } %>
10
+ <div class="errors-date-group"><%= ExceptionHunter::ErrorGroupPresenter.format_occurrence_day(i.days.ago) %></div>
11
+ <%= render partial: 'exception_hunter/errors/error_row', collection: errors_on_day, as: :error %>
12
+ <% end %>
@@ -1,36 +1,77 @@
1
- <div class="row statistics-row">
2
- <div class="column column-offset-50 column-25">
3
- <div class="statistics__cell">
4
- <%= number_with_delimiter(@errors_count) %> Total errors
5
- </div>
6
- </div>
7
- <div class="column column-25">
8
- <div class="statistics__cell">
9
- <%= number_with_delimiter(@month_errors) %> Errors this month
1
+ <div class="row">
2
+ <div class="column column-80">
3
+ <div class="errors-tabs">
4
+ <div class="errors-tab <%= 'errors-tab--active' if @dashboard.tab_active?(@dashboard.class::LAST_7_DAYS_TAB) %>">
5
+ <%= link_to errors_path(tab: @dashboard.class::LAST_7_DAYS_TAB) do %>
6
+ <div class="errors-tab__content">
7
+ <div class="errors-tab__badge">
8
+ <%= @dashboard.errors_count(@dashboard.class::LAST_7_DAYS_TAB) %>
9
+ </div>
10
+ <div class="errors-tab__description">
11
+ Errors in the last 7 days
12
+ </div>
13
+ </div>
14
+ <% end %>
15
+ </div>
16
+
17
+ <div class="errors-tab <%= 'errors-tab--active' if @dashboard.tab_active?(@dashboard.class::CURRENT_MONTH_TAB) %>">
18
+ <%= link_to errors_path(tab: @dashboard.class::CURRENT_MONTH_TAB) do %>
19
+ <div class="errors-tab__content">
20
+ <div class="errors-tab__badge">
21
+ <%= @dashboard.errors_count(@dashboard.class::CURRENT_MONTH_TAB) %>
22
+ </div>
23
+ <div class="errors-tab__description">
24
+ Errors this month
25
+ </div>
26
+ </div>
27
+ <% end %>
28
+ </div>
29
+
30
+ <div class="errors-tab <%= 'errors-tab--active' if @dashboard.tab_active?(@dashboard.class::TOTAL_ERRORS_TAB) %>">
31
+ <%= link_to errors_path(tab: @dashboard.class::TOTAL_ERRORS_TAB) do %>
32
+ <div class="errors-tab__content">
33
+ <div class="errors-tab__badge">
34
+ <%= @dashboard.errors_count(@dashboard.class::TOTAL_ERRORS_TAB) %>
35
+ </div>
36
+ <div class="errors-tab__description">
37
+ Total errors
38
+ </div>
39
+ </div>
40
+ <% end %>
41
+ </div>
42
+
43
+ <div class="errors-tab errors-tab__resolved <%= 'errors-tab--active' if @dashboard.tab_active?(@dashboard.class::RESOLVED_ERRORS_TAB) %>">
44
+ <%= link_to errors_path(tab: @dashboard.class::RESOLVED_ERRORS_TAB) do %>
45
+ <div class="errors-tab__content">
46
+ <div class="errors-tab__badge">
47
+ <%= @dashboard.errors_count(@dashboard.class::RESOLVED_ERRORS_TAB) || '-' %>
48
+ </div>
49
+ <div class="errors-tab__description">
50
+ Resolved
51
+ </div>
52
+ </div>
53
+ <% end %>
54
+ </div>
10
55
  </div>
11
56
  </div>
12
- </div>
13
57
 
14
- <div class="row error-row error-row--header">
15
- <div class="column column-75">Message</div>
16
- <div class="column column-15">Last Occurrence</div>
17
- <div class="column column-10">Occurrences</div>
58
+ <div class="column column-10 column-offset-10">
59
+ <%= button_to 'Purge', purge_errors_path,
60
+ class: %w[button purge-button],
61
+ method: :delete,
62
+ data: { confirm: 'This will delete all stale errors, do you want to continue?' } %>
63
+ </div>
18
64
  </div>
19
65
 
20
- <% @errors.each do |error| %>
21
- <div class="row error-row">
22
- <div class="column column-75 error-cell">
23
- <%= link_to error.message, error_path(error.id) %>
24
- </div>
25
- <div class="column column-15 error-cell error-cell--highlight">
26
- <% if error.last_occurrence.present? %>
27
- <%= time_ago_in_words(error.last_occurrence) %> ago
28
- <% else %>
29
- Never
30
- <% end %>
31
- </div>
32
- <div class="column column-10 error-cell error-cell--highlight">
33
- <%= error.total_occurrences %>
34
- </div>
66
+ <div class="errors__container">
67
+ <div class="row error-row error-row--header">
68
+ <div class="column column-10">Tags</div>
69
+ <div class="column column-40">Message</div>
70
+ <div class="column column-15">First Occurrence</div>
71
+ <div class="column column-15">Last Occurrence</div>
72
+ <div class="column column-10">Total</div>
73
+ <div class="column column-10"></div>
35
74
  </div>
36
- <% end %>
75
+
76
+ <%= render partial: @dashboard.partial_for_tab, locals: { errors: @errors } %>
77
+ </div>
@@ -1,17 +1,17 @@
1
- <% link = pagy_link_proc(pagy) %>
2
- <nav aria-label="pager" class="pagy_nav pagination" role="navigation">
3
- <% if pagy.prev %>
4
- <span class="page prev"><%== link.call(pagy.prev, '◄', 'aria-label="previous"') %></span>
5
- <% else %>
6
- <span class="page prev disabled">
7
-
8
- </span>
1
+ <div class="error-occurrences__nav">
2
+ <%= link_to pagy_url_for(1, pagy) do %>
3
+ <%= button_tag 'First', class: %w[button button-outline error-occurrences__nav-link], disabled: pagy.prev.nil? %>
9
4
  <% end %>
10
- <% if pagy.next %>
11
- <span class="page next"><%== link.call(pagy.next, '►', 'aria-label="next"') %></span>
12
- <% else %>
13
- <span class="page next disabled">
14
-
15
- </span>
5
+ <%= link_to pagy_url_for(pagy.prev, pagy) do %>
6
+ <%= button_tag '<', class: %w[button button-outline error-occurrences__nav-link], disabled: pagy.prev.nil? %>
16
7
  <% end %>
17
- </nav>
8
+ <div class="error-occurrences__nav-current">
9
+ <%= occurred_at %> (<%= pagy.page %>/<%= pagy.last %>)
10
+ </div>
11
+ <%= link_to pagy_url_for(pagy.next, pagy) do %>
12
+ <%= button_tag '>', class: %w[button button-outline error-occurrences__nav-link], disabled: pagy.next.nil? %>
13
+ <% end %>
14
+ <%= link_to pagy_url_for(pagy.last, pagy) do %>
15
+ <%= button_tag 'Last', class: %w[button button-outline error-occurrences__nav-link], disabled: pagy.next.nil? %>
16
+ <% end %>
17
+ </div>
@@ -1,33 +1,69 @@
1
- <div class="row">
2
- <div class="column column-offset-80 column-20">
3
- <%= render partial: 'exception_hunter/errors/pagy/pagy_nav', locals: { pagy: @pagy } %>
1
+ <div class="row error-occurrence__header">
2
+ <div class="column column-10">
3
+ <% @error.tags.each do|tag| %>
4
+ <div class="error-cell__tags">
5
+ <span class="error-tag"><%= tag %></span>
6
+ </div>
7
+ <% end %>
8
+ </div>
9
+ <div class="column column-80">
10
+ <div class="error-title">
11
+ <%= @error.class_name %>: <%= @error.message %>
12
+ </div>
13
+ </div>
14
+ <div class="column column-10">
15
+ <%= button_to('Resolve', resolved_errors_path(error_group: { id: @error.error_group_id }),
16
+ method: :post,
17
+ class: %w[button resolve-button],
18
+ data: { confirm: 'Are you sure you want to resolve this error?' }) %>
4
19
  </div>
5
20
  </div>
6
21
 
7
- <div class="error-title">
8
- <%= @error.class_name %>: <%= @error.message %>
9
- </div>
10
- <div class="error-occurred_at">
11
- <%= @error.occurred_at %>
22
+ <div class="row">
23
+ <div class="column column-50">
24
+ <ul data-tabs class="errors-tabs">
25
+ <li class="errors-tab">
26
+ <a data-tabby-default href="#summary">
27
+ <div class="errors-tab__content">
28
+ Summary
29
+ </div>
30
+ </a>
31
+ </li>
32
+ <li class="errors-tab">
33
+ <a href="#backtrace">
34
+ <div class="errors-tab__content">
35
+ Backtrace
36
+ </div>
37
+ </a>
38
+ </li>
39
+ <li class="errors-tab">
40
+ <a href="#user-data">
41
+ <div class="errors-tab__content">
42
+ User Data
43
+ </div>
44
+ </a>
45
+ </li>
46
+ </ul>
47
+ </div>
48
+ <div class="column column-50">
49
+ <%= render partial: 'exception_hunter/errors/pagy/pagy_nav', locals: { pagy: @pagy, occurred_at: @error.occurred_at } %>
50
+ </div>
12
51
  </div>
13
52
 
14
- <ul data-tabs>
15
- <li><a data-tabby-default href="#summary">Summary</a></li>
16
- <li><a href="#backtrace">Backtrace</a></li>
17
- <li><a href="#user-data">User Data</a></li>
18
- </ul>
19
53
 
20
- <div class="tab-content">
21
- <div id="summary">
22
- <%= render partial: 'exception_hunter/errors/error_summary', locals: { error: @error } %>
23
- </div>
54
+ <div class="errors__container">
55
+ <div class="tab-content">
56
+ <div id="summary">
57
+ <%= render partial: 'exception_hunter/errors/error_summary', locals: { error: @error } %>
58
+ </div>
24
59
 
25
- <div id="backtrace">
26
- <%= render partial: 'exception_hunter/errors/error_backtrace', locals: { error: @error } %>
27
- </div>
60
+ <div id="backtrace">
61
+ <%= render partial: 'exception_hunter/errors/error_backtrace', locals: { error: @error } %>
62
+ </div>
28
63
 
29
- <div id="user-data">
30
- <%= render partial: 'exception_hunter/errors/error_user_data', locals: { error: @error } %>
64
+ <div id="user-data">
65
+ <%= render partial: 'exception_hunter/errors/error_user_data', locals: { error: @error } %>
66
+ </div>
31
67
  </div>
32
68
  </div>
33
69
 
@@ -7,12 +7,11 @@
7
7
 
8
8
  <%= favicon_link_tag 'exception_hunter/logo.png' %>
9
9
 
10
- <link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
10
+ <link rel="stylesheet" href="https://rsms.me/inter/inter.css">
11
11
  <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.css">
12
12
  <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/milligram/1.3.0/milligram.css">
13
13
 
14
14
  <!-- Get patch fixes within a minor version -->
15
- <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/cferdinandi/tabby@12.0/dist/css/tabby-ui.min.css">
16
15
  <script src="https://cdn.jsdelivr.net/gh/cferdinandi/tabby@12.0/dist/js/tabby.polyfills.min.js"></script>
17
16
 
18
17
  <%= stylesheet_link_tag "exception_hunter/application", media: "all" %>
@@ -22,18 +21,78 @@
22
21
  <div class="wrapper">
23
22
  <nav class="nav">
24
23
  <div class="container container__nav">
25
- <div class="nav__logo">
26
- <%= link_to errors_path do %>
27
- <%= image_tag 'exception_hunter/logo.png', alt: 'Logo' %>
28
- <% end %>
24
+ <div class="row">
25
+ <div class="column column-40">
26
+ <%= link_to errors_path do %>
27
+ <div class="nav__title">Exception Hunter</div>
28
+ <% end %>
29
+ </div>
30
+ <div class="column column-10 column-offset-50">
31
+ <div class="logout">
32
+ <% if current_admin_user? %>
33
+ <div class="sign_out">
34
+ <%= link_to exception_hunter_logout_path do %>
35
+ Sign out
36
+ <% end %>
37
+ </div>
38
+ <% end %>
39
+ </div>
40
+ </div>
29
41
  </div>
30
42
  </div>
31
43
  </nav>
32
44
 
33
45
  <div class="container">
46
+ <% if flash[:notice] %>
47
+ <div id="flash-notice" class="flash flash--notice row">
48
+ <div class="column column-80">
49
+ <%= flash[:notice] %>
50
+ </div>
51
+ <div class="column column-20">
52
+ <div class="button button-clear button-dismiss" data-dismiss="#flash-notice">
53
+ <%= ['Cool!', 'Nice!', 'Ok', 'Dismiss', 'Fine. Whatever.', 'I know that'].sample %>
54
+ </div>
55
+ </div>
56
+ </div>
57
+ <% end %>
58
+
34
59
  <%= yield %>
35
60
  </div>
61
+
62
+ <div class="footer">
63
+ Made with ♥ at <a href="https://www.rootstrap.com/" class="text--underline">Rootstrap</a>
64
+ </div>
36
65
  </div>
37
66
 
67
+ <script type="text/javascript" charset="utf-8">
68
+ function confirmable(element) {
69
+ const message = element.getAttribute('data-confirm')
70
+ element.form.addEventListener('submit', confirm(message))
71
+
72
+ function confirm(message) {
73
+ return function (event) {
74
+ if (!window.confirm(message)) {
75
+ event.preventDefault()
76
+ }
77
+ }
78
+ }
79
+ }
80
+
81
+ function dismissible(element) {
82
+ element.addEventListener('click', dismiss(element))
83
+
84
+ function dismiss(element) {
85
+ return function () {
86
+ const selector = element.getAttribute('data-dismiss')
87
+ const elementToDismiss = document.querySelector(selector)
88
+ elementToDismiss.remove()
89
+ }
90
+ }
91
+ }
92
+
93
+ Array.from(document.querySelectorAll('[data-confirm]')).forEach(confirmable)
94
+ Array.from(document.querySelectorAll('[data-dismiss]')).forEach(dismissible)
95
+ </script>
96
+
38
97
  </body>
39
98
  </html>
@@ -0,0 +1,24 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Exception Hunter</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
9
+ <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.css">
10
+ <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/milligram/1.3.0/milligram.css">
11
+ <link rel="stylesheet" href="https://rsms.me/inter/inter.css">
12
+
13
+ <!-- Get patch fixes within a minor version -->
14
+ <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/cferdinandi/tabby@12.0/dist/css/tabby-ui.min.css">
15
+ <script src="https://cdn.jsdelivr.net/gh/cferdinandi/tabby@12.0/dist/js/tabby.polyfills.min.js"></script>
16
+
17
+ <%= stylesheet_link_tag "exception_hunter/application", media: "all" %>
18
+ </head>
19
+ <body style="background-color: #E5E5E5;">
20
+ <div class="container">
21
+ <%= yield %>
22
+ </div>
23
+ </body>
24
+ </html>
@@ -15,9 +15,8 @@ MoveCodeIntoModelCheck: { use_count: 2 }
15
15
  MoveFinderToNamedScopeCheck: { }
16
16
  MoveModelLogicIntoModelCheck: { use_count: 4 }
17
17
  NeedlessDeepNestingCheck: { nested_count: 2 }
18
- NotRescueExceptionCheck: { ignored_files: 'request_hunter.rb' }
19
18
  NotUseDefaultRouteCheck: { }
20
- NotUseTimeAgoInWordsCheck: { ignored_files: ['index.html.erb'] }
19
+ #NotUseTimeAgoInWordsCheck: { ignored_files: ['index.html.erb'] }
21
20
  OveruseRouteCustomizationsCheck: { customize_count: 3 }
22
21
  ProtectMassAssignmentCheck: { }
23
22
  RemoveEmptyHelpersCheck: { }
@@ -30,7 +29,7 @@ ReplaceComplexCreationWithFactoryMethodCheck: { attribute_assignment_count: 2 }
30
29
  ReplaceInstanceVariableWithLocalVariableCheck: { }
31
30
  RestrictAutoGeneratedRoutesCheck: { }
32
31
  SimplifyRenderInControllersCheck: { }
33
- SimplifyRenderInViewsCheck: { }
32
+ #SimplifyRenderInViewsCheck: { }
34
33
  #UseBeforeFilterCheck: { customize_count: 2 }
35
34
  UseModelAssociationCheck: { }
36
35
  UseMultipartAlternativeAsContentTypeOfEmailCheck: { }
@@ -1,3 +1,21 @@
1
1
  ExceptionHunter::Engine.routes.draw do
2
- resources :errors, only: %i[index show]
2
+ resources :errors, only: %i[index show] do
3
+ delete 'purge', on: :collection, to: 'errors#destroy', as: :purge
4
+ end
5
+
6
+ resources :resolved_errors, only: %i[create]
7
+
8
+ get '/', to: redirect('/exception_hunter/errors')
9
+
10
+ if ExceptionHunter::Config.auth_enabled?
11
+ admin_user_class = ExceptionHunter::Config.admin_user_class.underscore.to_sym
12
+
13
+ devise_scope admin_user_class do
14
+ get '/login', to: 'devise/sessions#new', as: :exception_hunter_login
15
+ post '/login', to: 'devise/sessions#create', as: :exception_hunter_create_session
16
+ get '/logout', to: 'devise/sessions#destroy', as: :exception_hunter_logout
17
+ end
18
+
19
+ devise_for admin_user_class, only: []
20
+ end
3
21
  end
@@ -1,15 +1,25 @@
1
+ require 'pagy'
2
+
1
3
  require 'exception_hunter/engine'
2
- require 'exception_hunter/railtie'
4
+ require 'exception_hunter/middleware/request_hunter'
3
5
  require 'exception_hunter/config'
6
+ require 'exception_hunter/error_creator'
7
+ require 'exception_hunter/error_reaper'
8
+ require 'exception_hunter/tracking'
4
9
  require 'exception_hunter/user_attributes_collector'
5
- require 'pagy'
6
10
 
7
11
  module ExceptionHunter
12
+ autoload :Devise, 'exception_hunter/devise'
13
+
14
+ extend ::ExceptionHunter::Tracking
15
+
8
16
  def self.setup(&block)
9
17
  block.call(Config)
10
18
  end
11
19
 
12
20
  def self.routes(router)
21
+ return unless Config.enabled
22
+
13
23
  router.mount(ExceptionHunter::Engine, at: 'exception_hunter')
14
24
  end
15
25
  end