snitch_reporting 0.1.0 → 1.0.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.
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