snitch_reporting 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +23 -3
  3. data/app/assets/javascripts/snitch_reporting/snitch_report.js +60 -0
  4. data/app/assets/stylesheets/snitch_reporting/_variables.scss +5 -0
  5. data/app/assets/stylesheets/snitch_reporting/components.scss +120 -0
  6. data/app/assets/stylesheets/snitch_reporting/containers.scss +6 -0
  7. data/app/assets/stylesheets/snitch_reporting/default.scss +18 -0
  8. data/app/assets/stylesheets/snitch_reporting/forms.scss +36 -0
  9. data/app/assets/stylesheets/snitch_reporting/navigation.scss +37 -0
  10. data/app/assets/stylesheets/snitch_reporting/snitch_report.scss +1 -0
  11. data/app/assets/stylesheets/snitch_reporting/tables.scss +127 -0
  12. data/app/controllers/snitch_reporting/application_controller.rb +4 -0
  13. data/app/controllers/snitch_reporting/snitch_reports_controller.rb +167 -0
  14. data/app/helpers/snitch_reporting/params_helper.rb +38 -0
  15. data/app/helpers/snitch_reporting/snitch_report_helper.rb +2 -0
  16. data/app/models/snitch_reporting/service/json_wrapper.rb +5 -0
  17. data/app/models/snitch_reporting/snitch_comment.rb +7 -0
  18. data/app/models/snitch_reporting/snitch_history.rb +7 -0
  19. data/app/models/snitch_reporting/snitch_occurrence.rb +183 -0
  20. data/app/models/snitch_reporting/snitch_report.rb +301 -0
  21. data/app/models/snitch_reporting/snitch_tracker.rb +17 -0
  22. data/app/views/snitch_reporting/snitch_reports/_filters.html.erb +16 -0
  23. data/app/views/snitch_reporting/snitch_reports/_navigation.html.erb +0 -0
  24. data/app/views/snitch_reporting/snitch_reports/edit.html.erb +19 -0
  25. data/app/views/snitch_reporting/snitch_reports/index.html.erb +75 -0
  26. data/app/views/snitch_reporting/snitch_reports/show.html.erb +189 -0
  27. data/config/routes.rb +4 -1
  28. data/lib/generators/snitch_reporting/install/install_generator.rb +24 -0
  29. data/lib/generators/snitch_reporting/install/templates/install_snitch_reporting.rb +62 -0
  30. data/lib/snitch_reporting/engine.rb +3 -0
  31. data/lib/snitch_reporting/rack.rb +29 -0
  32. data/lib/snitch_reporting/version.rb +1 -1
  33. data/lib/snitch_reporting.rb +3 -1
  34. metadata +58 -3
@@ -0,0 +1,17 @@
1
+ # belongs_to :snitch_report
2
+ # date :date
3
+ # bigint :count
4
+
5
+ class SnitchReporting::SnitchTracker < ApplicationRecord
6
+ belongs_to :report, class_name: "SnitchReporting::SnitchReport"
7
+
8
+ def self.tracker_for_date(date=nil)
9
+ date ||= Date.today
10
+
11
+ find_or_create_by(date: date)
12
+ end
13
+
14
+ # def self.count_for_date_range(start, end)
15
+ # where(date: start..end).sum(:count)
16
+ # end
17
+ end
@@ -0,0 +1,16 @@
1
+ <div class="tabs-container">
2
+ <% all_path = current_params.merge(param => :all) %>
3
+ <% current_tabs = (current_params[param] || []).map(&:to_sym) %>
4
+ <%= link_to "All", all_path, class: "tab white-label-text #{'selected' if current_tabs.none?}" %>
5
+ <% available_tabs.each do |tab_label| %>
6
+ <% new_tabs = current_tabs.include?(tab_label) ? (current_tabs - [tab_label]) : (current_tabs + [tab_label]) %>
7
+ <%
8
+ new_path = if new_tabs.sort == available_tabs.sort
9
+ all_path
10
+ else
11
+ {param => new_tabs}.reverse_merge(all_path)
12
+ end
13
+ %>
14
+ <%= link_to tab_label.to_s.titleize, new_path, class: "tab white-label-text #{'selected' if current_tabs.include?(tab_label)}" %>
15
+ <% end %>
16
+ </div>
@@ -0,0 +1,19 @@
1
+ <div class="skinny-container">
2
+ <%= form_for @bug_report do |f| %>
3
+ <%= hidden_field_tag :redirect_to_report, true %>
4
+
5
+ <div class="form-field">
6
+ <%= f.label :title %>
7
+ <%= f.text_area :title %>
8
+ </div>
9
+
10
+ <div class="form-field">
11
+ <%= f.label :custom_details, "Notes" %>
12
+ <%= f.text_area :custom_details %>
13
+ </div>
14
+
15
+ <div class="form-field">
16
+ <%= f.submit "Submit", class: "orca-btn" %>
17
+ </div>
18
+ <% end %>
19
+ </div>
@@ -0,0 +1,75 @@
1
+ <div class="snitch-reporting">
2
+ <!-- <div class="snitch-nav">
3
+ <div class="nav-tab">
4
+ <input type="search" name="search" value="<%= params[:search] %>" placeholder="Search">
5
+ </div>
6
+ <a href="<%# %>">Status</a>
7
+ <a href="<%# %>">Assignee</a>
8
+ <a href="<%# %>">Occurred</a>
9
+ <a href="<%# %>">Ignored</a>
10
+ </div> -->
11
+ <div class="filters">
12
+ <% @filter_sets.each do |filter_name, filter_set| %>
13
+ <div class="snitch-table filter-table">
14
+ <div class="snitch-tr">
15
+ <div class="snitch-th"><%= filter_name.to_s.titleize %></div>
16
+ </div>
17
+
18
+ <% selected_filter_value = @filters[filter_name] %>
19
+ <% default_filter_value = filter_set[:default] %>
20
+ <% filter_set[:values].each do |filter_value| %>
21
+ <div class="snitch-tr">
22
+ <% url_value = filter_value == default_filter_value ? @filters[:set_filters].except(filter_name) : @filters[:set_filters].merge(filter_name => filter_value) %>
23
+ <%= link_to filter_value.to_s.titleize, url_value, class: "snitch-td link-cell #{'selected' if selected_filter_value == filter_value}" %>
24
+ </div>
25
+ <% end %>
26
+ </div>
27
+ <% end %>
28
+ </div>
29
+
30
+ <div class="snitch-index">
31
+ <div class="snitch-title-section">
32
+ <h1><%= Rails.application.class.parent.name %></h1>
33
+ <!-- Sort -->
34
+ </div>
35
+ <div class="snitch-center">
36
+ <%= paginate @reports %>
37
+ <br>
38
+ </div>
39
+
40
+ <div class="snitch-errors">
41
+ <div class="snitch-table bordered padded">
42
+ <div class="snitch-thead">
43
+ <div class="snitch-tr">
44
+ <div class="snitch-th">Error</div>
45
+ <div class="snitch-th" style="width: 150px;">Last</div>
46
+ <div class="snitch-th" style="width: 1px;">Times</div>
47
+ <div class="snitch-th" style="width: 1px;">Assigned</div>
48
+ <div class="snitch-th" style="width: 1px;">Status</div>
49
+ </div>
50
+ </div>
51
+ <div class="snitch-tbody">
52
+ <% @reports.each do |report| %>
53
+ <div class="snitch-tr">
54
+ <%= link_to report, class: "snitch-td link-cell" do %>
55
+ <div class="report-title-wrapper">
56
+ <span class="report-title"><%= report.error %></span>
57
+ <% if report.klass.present? && report.action.present? %>
58
+ in <span class="report-location"><%= report.klass %>#<%= report.action %></span>
59
+ <% end %>
60
+ </div>
61
+ <small class="report-message"><%= truncate(report.message, length: 100) %></small>
62
+ <% end %>
63
+ <div class="snitch-td"><%= report.last_occurrence_at %></div>
64
+ <div class="snitch-td"><%= number_with_delimiter(report.occurrence_count) %></div>
65
+ <div class="snitch-td"><%= report.assigned_to.try(:name).presence || "-" %></div>
66
+ <div class="snitch-td">
67
+ <%= content_tag :input, "", type: :checkbox, name: :resolved, class: "snitch-resolution-switch", checked: report.resolved?, data: { mark_resolution_url: snitch_report_url(report) } %>
68
+ </div>
69
+ </div>
70
+ <% end %>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </div>
@@ -0,0 +1,189 @@
1
+ <div class="snitch-reporting">
2
+ <div class="snitch-nav">
3
+ <a href="#summary">Summary</a>
4
+ <a href="#comments">Comments</a>
5
+ <a href="#backtrace">Backtrace</a>
6
+ <a href="#context">Context</a>
7
+ <a href="#params">Params</a>
8
+ <a href="#environment">Environment</a>
9
+ <a href="#history">History</a>
10
+ </div>
11
+
12
+ <div class="snitch-breadcrumbs">
13
+ <%= link_to snitch_reports_path, class: "snitch-btn primary" do %>
14
+ &larr;
15
+ <%= Rails.application.class.parent.name %>
16
+ <% end %>
17
+ </div>
18
+
19
+ <% if @report.ignored? %>
20
+ <div class="snitch-banner">
21
+ <div>Report is ignored- you will not be notified for future occurrences of this error.</div>
22
+ </div>
23
+ <% end %>
24
+
25
+ <div class="snitch-title-section">
26
+ <div class="snitch-center">
27
+ <%= link_to "« First", snitch_report_path(@report, occurrence: @paged_ids[:first]) if @paged_ids[:first].present? %>
28
+ <%= link_to "‹ Previous", snitch_report_path(@report, occurrence: @paged_ids[:prev]) if @paged_ids[:prev].present? %>
29
+ <%= link_to "Next ›", snitch_report_path(@report, occurrence: @paged_ids[:next]) if @paged_ids[:next].present? %>
30
+ <%= link_to "Last »", snitch_report_path(@report, occurrence: @paged_ids[:last]) if @paged_ids[:last].present? %>
31
+ </div>
32
+ <!-- <%= '-' if @paged_ids.present? %>
33
+ <todo>(a few seconds ago)</todo> -->
34
+ <div class="flex-row">
35
+ <div class="flex-cell">
36
+ <h2><%= @report.error %></h2>
37
+ <% if @report.klass.present? %>
38
+ in <%= @report.klass %>#<%= @report.action %>
39
+ <% end %>
40
+ This error has occurred <%= number_with_delimiter(@report.occurrence_count) %> <%= "time".pluralize(@report.occurrence_count) %> since <%= @report.first_occurrence_at.strftime("%m/%d/%Y @ %I:%M%P %:z") %>
41
+ </div>
42
+ <div class="flex-cell">
43
+ <%= content_tag :input, "", type: :checkbox, name: :resolved, class: "snitch-resolution-switch", checked: @report.resolved?, data: { mark_resolution_url: snitch_report_url(@report) } %>
44
+ </div>
45
+ </div>
46
+ </div>
47
+
48
+ <div id="summary" class="snitch-section">
49
+ <div class="snitch-table padded">
50
+ <div class="snitch-tr">
51
+ <div class="snitch-td">Error</div>
52
+ <div class="snitch-td">
53
+ <div class="scrollable">
54
+ <%= @report.error %>: <br>
55
+ <code><%= @report.message %></code>
56
+ </div>
57
+ </div>
58
+ </div>
59
+ <div class="snitch-tr">
60
+ <div class="snitch-td">Occurred</div>
61
+ <div class="snitch-td"><%= @occurrence.created_at %></div>
62
+ </div>
63
+ <div class="snitch-tr">
64
+ <div class="snitch-td">Backtrace</div>
65
+ <div class="snitch-td">
66
+ <%= (@occurrence.filtered_backtrace.first || @occurrence.backtrace.first).split(":in").first %>
67
+ </div>
68
+ </div>
69
+ <div class="snitch-tr">
70
+ <div class="snitch-td">URL</div>
71
+ <div class="snitch-td">
72
+ <% if @occurrence.http_method %>
73
+ [<%= @occurrence.http_method %>]
74
+ <% end %>
75
+ <%= @occurrence.url %>
76
+ </div>
77
+ </div>
78
+ <div class="snitch-tr">
79
+ <div class="snitch-td">User Agent</div>
80
+ <div class="snitch-td">
81
+ <%= @occurrence.user_agent %>
82
+ </div>
83
+ </div>
84
+ <!-- <div class="snitch-tr">
85
+ <div class="snitch-td">Timeline</div>
86
+ <div class="snitch-td">
87
+ <todo>
88
+ X: Date
89
+ Y: Occurrence Count (Hover for date/count)
90
+ [Hour|Day|Week|Month]
91
+ </todo>
92
+ </div>
93
+ </div> -->
94
+ <!-- <div class="snitch-tr">
95
+ <div class="snitch-td">Tags <todo>(edit)</todo></div>
96
+ <div class="snitch-td"><%= @report.tags %></div>
97
+ </div> -->
98
+ </div>
99
+ </div>
100
+
101
+ <div class="btn-row">
102
+ <!-- <a class="snitch-btn danger" href="">Delete</a> -->
103
+ <% if @report.ignored? %>
104
+ <%= link_to "Mark as Unignored", snitch_report_path(@report, snitch_report: { ignored: false }), method: :patch, class: "snitch-btn" %>
105
+ <% else %>
106
+ <%= link_to "Mark as Ignored", snitch_report_path(@report, snitch_report: { ignored: true }), method: :patch, class: "snitch-btn" %>
107
+ <% end %>
108
+ <!-- <a class="snitch-btn" href="">Assign</a> -->
109
+ <!-- <a class="snitch-btn" href="">URL</a> -->
110
+ <!-- <a class="snitch-btn" href="">Search StackOverflow</a> -->
111
+ <!-- <a class="snitch-btn" href="">Create GH Issue</a> -->
112
+ </div>
113
+
114
+ <!-- <div id="comments" class="snitch-section">
115
+ <h3>Comments</h3>
116
+ <todo>
117
+ Text area - Markdown/editor
118
+ - list of comments
119
+ </todo>
120
+ </div> -->
121
+
122
+ <div id="params" class="snitch-section">
123
+ <h3>Params</h3>
124
+ <div class="line-trace">
125
+ <code><%= JSON.pretty_generate(@occurrence.params) %></code>
126
+ </div>
127
+ </div>
128
+
129
+ <div id="backtrace" class="snitch-section">
130
+ <h3>Application Backtrace</h3>
131
+ <% @occurrence.backtrace_data.each do |row_data| %>
132
+ <% row_data = row_data.with_indifferent_access %>
133
+ <% file_path = row_data[:file_path] %>
134
+ <% line_number = row_data[:line_number].to_i %>
135
+
136
+ <div class="line-trace">
137
+ <div class="trace-details">
138
+ <span class="trace-file"><%= file_path.split("/").last(2).join("/") %></span>
139
+ <span>&rarr;</span>
140
+ <span class="trace-line"><%= line_number %></span>
141
+ <div class="trace-full"><%= file_path %></div>
142
+ </div>
143
+
144
+ <code><% row_data[:snippet].each do |line_num, line_code| %><span class="<%= 'current-line' if line_num.to_s.to_i == line_number %>"><%= line_num %> <%= line_code %></span><br><% end %></code>
145
+ </div>
146
+ <% end %>
147
+ </div>
148
+
149
+ <div id="backtrace" class="snitch-section">
150
+ <h3>Full Backtrace</h3>
151
+ <div class="line-trace cap-height">
152
+ <code><%= @occurrence.backtrace.join("\n") %></code>
153
+ </div>
154
+ </div>
155
+
156
+ <div id="context" class="snitch-section">
157
+ <h3>Context</h3>
158
+ <div class="line-trace">
159
+ <code><%= JSON.pretty_generate @occurrence.context %></code>
160
+ </div>
161
+ </div>
162
+
163
+ <div id="environment" class="snitch-section">
164
+ <h3>Environment</h3>
165
+ <div class="line-trace">
166
+ <code><%= JSON.pretty_generate @occurrence.headers %></code>
167
+ </div>
168
+ </div>
169
+
170
+ <!-- <div id="history" class="snitch-section">
171
+ <h3>History</h3>
172
+ <div class="snitch-table">
173
+ <div class="snitch-thead">
174
+ <div class="snitch-tr">
175
+ <div class="snitch-th">Action</div>
176
+ <div class="snitch-th">User</div>
177
+ <div class="snitch-th">Timestamp</div>
178
+ </div>
179
+ </div>
180
+ <div class="snitch-tbody">
181
+ <% @report.histories.each do |history| %>
182
+ <div class="snitch-td"><%= history.text %></div>
183
+ <div class="snitch-td"><%= history.user %></div>
184
+ <div class="snitch-td"><%= history.created_at %></div>
185
+ <% end %>
186
+ </div>
187
+ </div>
188
+ </div> -->
189
+ </div>
data/config/routes.rb CHANGED
@@ -1,2 +1,5 @@
1
- Rails.application.routes.draw do
1
+ SnitchReporting::Engine.routes.draw do
2
+ root to: "snitch_reports#index"
3
+
4
+ resources :snitch_reports, path: "/", only: [:index, :show, :update, :edit]
2
5
  end
@@ -0,0 +1,24 @@
1
+ require "rails/generators/migration"
2
+
3
+ module SnitchReporting
4
+ module Generators
5
+ class InstallGenerator < ::Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+ source_root File.expand_path("../templates", __FILE__)
8
+ desc "add the migrations"
9
+
10
+ def self.next_migration_number(path)
11
+ unless @prev_migration_nr
12
+ @prev_migration_nr = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
13
+ else
14
+ @prev_migration_nr += 1
15
+ end
16
+ @prev_migration_nr.to_s
17
+ end
18
+
19
+ def copy_migrations
20
+ migration_template "install_snitch_reporting.rb", "db/migrate/install_snitch_reporting.rb"
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,62 @@
1
+ class InstallSnitchReporting < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :snitch_reporting_snitch_reports do |t|
4
+ t.text :error
5
+ t.text :message
6
+ t.integer :log_level
7
+ t.string :klass
8
+ t.string :action
9
+ t.text :tags
10
+
11
+ t.datetime :first_occurrence_at
12
+ t.datetime :last_occurrence_at
13
+ t.bigint :occurrence_count
14
+
15
+ t.belongs_to :assigned_to
16
+ t.datetime :resolved_at
17
+ t.belongs_to :resolved_by
18
+ t.datetime :ignored_at
19
+ t.belongs_to :ignored_by
20
+
21
+ t.timestamps
22
+ end
23
+
24
+ create_table :snitch_reporting_snitch_occurrences do |t|
25
+ t.belongs_to :report
26
+ t.string :http_method
27
+ t.string :url
28
+ t.text :user_agent
29
+ t.text :backtrace
30
+ t.text :backtrace_data
31
+ t.text :context
32
+ t.text :params
33
+ t.text :headers
34
+
35
+ t.timestamps
36
+ end
37
+
38
+ create_table :snitch_reporting_snitch_comments do |t|
39
+ t.belongs_to :report
40
+ t.belongs_to :author
41
+ t.text :body
42
+
43
+ t.timestamps
44
+ end
45
+
46
+ create_table :snitch_reporting_snitch_histories do |t|
47
+ t.belongs_to :report
48
+ t.belongs_to :user
49
+ t.text :text
50
+
51
+ t.timestamps
52
+ end
53
+
54
+ create_table :snitch_reporting_snitch_trackers do |t|
55
+ t.belongs_to :report
56
+ t.date :date
57
+ t.bigint :count
58
+
59
+ t.timestamps
60
+ end
61
+ end
62
+ end
@@ -1,4 +1,7 @@
1
1
  module SnitchReporting
2
2
  class Engine < ::Rails::Engine
3
+ isolate_namespace SnitchReporting
4
+
5
+ # config.app_middleware.use ::SnitchReporting::Rack
3
6
  end
4
7
  end
@@ -0,0 +1,29 @@
1
+ class SnitchReporting::Rack
2
+ class SnitchException < RuntimeError; end
3
+ attr_accessor :notify_callback
4
+
5
+ def initialize(app, notify_callback=nil)
6
+ @app = app
7
+ @notify_callback = notify_callback
8
+ end
9
+
10
+ def call(env)
11
+ response = @app.call(env)
12
+ _, headers, = response
13
+
14
+ if headers["X-Cascade"] == "pass"
15
+ msg = "This exception means that the preceding Rack middleware set the 'X-Cascade' header to 'pass' -- in " \
16
+ "Rails, this often means that the route was not found (404 error)."
17
+ raise SnitchException, msg
18
+ end
19
+
20
+ response
21
+ rescue Exception => exception
22
+ occurrence = ::SnitchReporting::SnitchReport.fatal(exception, env: env)
23
+ notify_callback.call(occurrence) if occurrence.notify?
24
+
25
+ raise exception unless exception.is_a?(SnitchException)
26
+
27
+ response
28
+ end
29
+ end