snitch_reporting 1.0.7 → 1.1.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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/snitch_reporting/search_bar.js +74 -0
- data/app/assets/javascripts/snitch_reporting/snitch_report.js +2 -0
- data/app/assets/stylesheets/snitch_reporting/components.scss +1 -0
- data/app/assets/stylesheets/snitch_reporting/navigation.scss +49 -0
- data/app/assets/stylesheets/snitch_reporting/snitch_report.scss +1 -1
- data/app/controllers/snitch_reporting/snitch_reports_controller.rb +100 -21
- data/app/models/snitch_reporting/snitch_occurrence.rb +2 -2
- data/app/models/snitch_reporting/snitch_report.rb +2 -2
- data/app/views/snitch_reporting/snitch_reports/index.html.erb +18 -20
- data/app/views/snitch_reporting/snitch_reports/show.html.erb +1 -1
- data/config/routes.rb +1 -0
- data/lib/snitch_reporting/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea0cf12cf265e5dbdaeb502ce609cda532d5905041ec0f2ec96cde59cedeedcb
|
4
|
+
data.tar.gz: e22d3a0e011b516fd2f69ca487a674bcefbfbb5722940920c8f1e2cceb1108fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ed3dfb3a37fea761142366b66b1e3f2afa3e5bcb8fffb0227991fb0c2f4da3cdc3145b5cfa7e63d0ce42f13ee1e8c14c468b385ad8c856fa833e4e027345ac93
|
7
|
+
data.tar.gz: 909ba5d52e8585fd1e46944bf3a4d390d1e8d81035587d8194a6ba92dde0c2fb9b82bb13cb4565354aaff1259482a0f159e6370519c3e472a6f55511f5acdc70
|
@@ -0,0 +1,74 @@
|
|
1
|
+
document.addEventListener("DOMContentLoaded", function(){
|
2
|
+
document.querySelector("[name=filter_string]").addEventListener("focus", function(event) {
|
3
|
+
document.querySelector(".filters").classList.add("open")
|
4
|
+
})
|
5
|
+
|
6
|
+
setNewFilters(document.querySelector("[name=filter_string]").value)
|
7
|
+
});
|
8
|
+
|
9
|
+
document.addEventListener("click", function(event) {
|
10
|
+
if (event.target.matches(".filters") || event.target.matches("[name=filter_string]")) {
|
11
|
+
return event.stopPropagation()
|
12
|
+
}
|
13
|
+
|
14
|
+
if (event.target.matches(".filter-cell")) {
|
15
|
+
event.preventDefault()
|
16
|
+
return applyFilter(event.target)
|
17
|
+
}
|
18
|
+
|
19
|
+
document.querySelector(".filters").classList.remove("open")
|
20
|
+
})
|
21
|
+
|
22
|
+
function applyFilter(selected_cell) {
|
23
|
+
var filter_field = document.querySelector("[name=filter_string]")
|
24
|
+
var filter_string = filter_field.value
|
25
|
+
|
26
|
+
var matched_filters = filter_string.match(new RegExp(selected_cell.dataset.filterName + ":" + "\\w+"))
|
27
|
+
var current_filter = (matched_filters || [])[0]
|
28
|
+
|
29
|
+
if (current_filter == undefined) {
|
30
|
+
current_filter = selected_cell.dataset.filterName + ":" + selected_cell.dataset.filterValue
|
31
|
+
|
32
|
+
if (!filter_string.slice(-1) == " ") { filter_string = filter_string + " " }
|
33
|
+
filter_string = filter_string + current_filter
|
34
|
+
} else {
|
35
|
+
var filter_values = current_filter.split(":")
|
36
|
+
var filter_name = filter_values[0]
|
37
|
+
var filter_value = filter_values[1]
|
38
|
+
var filter_sort = filter_values[2]
|
39
|
+
|
40
|
+
var new_filter = current_filter.replace(filter_value, selected_cell.dataset.filterValue)
|
41
|
+
|
42
|
+
filter_string = filter_string.replace(current_filter, new_filter)
|
43
|
+
}
|
44
|
+
|
45
|
+
setNewFilters(filter_string)
|
46
|
+
}
|
47
|
+
|
48
|
+
function setNewFilters(filter_string) {
|
49
|
+
var filter_field = document.querySelector("[name=filter_string]")
|
50
|
+
filter_field.value = filter_string
|
51
|
+
|
52
|
+
var filters = {}, filter_strings = filter_string.match(/\w+\:\w+/g) || []
|
53
|
+
filter_strings.forEach(function(filter) {
|
54
|
+
var filter_values = filter.split(":")
|
55
|
+
var filter_name = filter_values[0]
|
56
|
+
var filter_value = filter_values[1]
|
57
|
+
var filter_sort = filter_values[2]
|
58
|
+
|
59
|
+
filters[filter_name] = filter_value
|
60
|
+
})
|
61
|
+
|
62
|
+
document.querySelectorAll(".filters .snitch-table").forEach(function(filter_table) {
|
63
|
+
var current_filter = filters[filter_table.dataset.filterName]
|
64
|
+
current_filter = current_filter || filter_table.dataset.default
|
65
|
+
|
66
|
+
filter_table.querySelectorAll(".filter-cell").forEach(function(filter_cell) {
|
67
|
+
if (filter_cell.dataset.filterValue == current_filter) {
|
68
|
+
filter_cell.classList.add("selected")
|
69
|
+
} else {
|
70
|
+
filter_cell.classList.remove("selected")
|
71
|
+
}
|
72
|
+
})
|
73
|
+
})
|
74
|
+
}
|
@@ -33,5 +33,54 @@
|
|
33
33
|
}
|
34
34
|
}
|
35
35
|
|
36
|
+
.snitch-search-area {
|
37
|
+
display: flex;
|
38
|
+
position: relative;
|
39
|
+
flex-direction: row-reverse;
|
40
|
+
|
41
|
+
.filters {
|
42
|
+
display: none;
|
43
|
+
position: absolute;
|
44
|
+
top: 60px;
|
45
|
+
right: 0;
|
46
|
+
left: 0;
|
47
|
+
z-index: 100;
|
48
|
+
transition: all 0.3s;
|
49
|
+
box-shadow: 2px 2px 4px $border-grey;
|
50
|
+
border: 3px solid $border-grey;
|
51
|
+
border-radius: 4px;
|
52
|
+
background: white;
|
53
|
+
padding: 20px;
|
54
|
+
}
|
55
|
+
|
56
|
+
input[name=filter_string] {
|
57
|
+
box-shadow: 2px 2px 4px $border-grey;
|
58
|
+
border: 2px solid $border-grey;
|
59
|
+
border-radius: 4px;
|
60
|
+
padding: 10px;
|
61
|
+
width: 100%;
|
62
|
+
font-size: 32px;
|
63
|
+
}
|
64
|
+
|
65
|
+
button[type=submit] {
|
66
|
+
cursor: pointer;
|
67
|
+
box-shadow: 2px 2px 4px $border-grey;
|
68
|
+
border: 1px solid $border-grey;
|
69
|
+
border-radius: 6px;
|
70
|
+
background: linear-gradient($snitch-blue, darken($snitch-blue, 15%));
|
71
|
+
padding: 0 12px;
|
72
|
+
color: white;
|
73
|
+
font-size: 18px;
|
74
|
+
|
75
|
+
&:hover {
|
76
|
+
background: linear-gradient(darken($snitch-blue, 15%), $snitch-blue, $snitch-blue);
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
.filters.open {
|
81
|
+
display: block;
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
36
85
|
.snitch-breadcrumbs {}
|
37
86
|
}
|
@@ -1 +1 @@
|
|
1
|
-
@import "*";
|
1
|
+
@import "*";
|
@@ -14,6 +14,10 @@ class ::SnitchReporting::SnitchReportsController < ApplicationController
|
|
14
14
|
# sort_reports
|
15
15
|
end
|
16
16
|
|
17
|
+
def interpret_search
|
18
|
+
redirect_to snitch_reports_path(filter_string: params[:filter_string])
|
19
|
+
end
|
20
|
+
|
17
21
|
def show
|
18
22
|
@report = ::SnitchReporting::SnitchReport.find(params[:id])
|
19
23
|
occurrences = @report.occurrences.order(created_at: :asc)
|
@@ -93,8 +97,8 @@ class ::SnitchReporting::SnitchReportsController < ApplicationController
|
|
93
97
|
# end
|
94
98
|
#
|
95
99
|
# def set_report_preferences
|
96
|
-
# @
|
97
|
-
# preferences = JSON.parse(session[:
|
100
|
+
# @filters = begin
|
101
|
+
# preferences = JSON.parse(session[:filters].presence || "{}").symbolize_keys
|
98
102
|
#
|
99
103
|
# available_preferences = [:level_tags, :severity_tags, :source_tags, :resolved, :ignored]
|
100
104
|
# available_preferences.each do |pref_key|
|
@@ -103,7 +107,7 @@ class ::SnitchReporting::SnitchReportsController < ApplicationController
|
|
103
107
|
# preferences.delete(pref_key) if pref_val == "all"
|
104
108
|
# end
|
105
109
|
#
|
106
|
-
# session[:
|
110
|
+
# session[:filters] = preferences.to_json
|
107
111
|
# preferences
|
108
112
|
# end
|
109
113
|
# end
|
@@ -112,28 +116,30 @@ class ::SnitchReporting::SnitchReportsController < ApplicationController
|
|
112
116
|
status: {
|
113
117
|
default: :unresolved,
|
114
118
|
values: [:all, :resolved, :unresolved]
|
115
|
-
}
|
119
|
+
},
|
116
120
|
# assignee: {
|
117
121
|
# default: :any,
|
118
122
|
# values: [:any, :me, :not_me, :not_assigned]
|
119
123
|
# },
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
+
log_level: {
|
125
|
+
default: :any,
|
126
|
+
values: [:any] + ::SnitchReporting::SnitchReport.log_levels.keys.map(&:to_sym)
|
127
|
+
},
|
124
128
|
# ignored: {
|
125
129
|
# default: :not_ignored,
|
126
130
|
# values: [:all, :ignored, :not_ignored]
|
127
131
|
# }
|
128
132
|
}
|
129
133
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
134
|
+
set_filter_string
|
135
|
+
|
136
|
+
# @filters = @filter_sets.each_with_object({set_filters: {}}) do |(filter_name, filter_set), filters|
|
137
|
+
# filters[filter_name] = filter_set[:default]
|
138
|
+
# filter_in_param = params[filter_name].try(:to_sym)
|
139
|
+
# next unless filter_in_param && filter_set[:values].include?(filter_in_param)
|
140
|
+
# filters[filter_name] = filter_in_param
|
141
|
+
# filters[:set_filters][filter_name] = filter_in_param
|
142
|
+
# end
|
137
143
|
end
|
138
144
|
|
139
145
|
def filter_reports
|
@@ -141,14 +147,87 @@ class ::SnitchReporting::SnitchReportsController < ApplicationController
|
|
141
147
|
|
142
148
|
@reports = @reports.resolved if @filters[:status] == :resolved
|
143
149
|
@reports = @reports.unresolved if @filters[:status] == :unresolved
|
144
|
-
|
150
|
+
@reports = @reports.search(@filters[:search]) if @filters[:search].present?
|
145
151
|
#
|
146
|
-
|
147
|
-
# @reports = @reports.by_severity(@
|
148
|
-
# @reports = @reports.by_source(@
|
152
|
+
@reports = @reports.by_level(@filters[:log_level]) if @filters[:log_level].present?
|
153
|
+
# @reports = @reports.by_severity(@filters[:severity_tags]) if @filters[:severity_tags].present?
|
154
|
+
# @reports = @reports.by_source(@filters[:source_tags]) if @filters[:source_tags].present?
|
149
155
|
#
|
150
|
-
# @reports = @
|
151
|
-
# @reports = @
|
156
|
+
# @reports = @filters[:resolved].present? && truthy?(@filters[:resolved]) ? @reports.resolved : @reports.unresolved
|
157
|
+
# @reports = @filters[:ignored].present? && truthy?(@filters[:ignored]) ? @reports.ignored : @reports.unignored
|
158
|
+
end
|
159
|
+
|
160
|
+
def encode_string(token, str)
|
161
|
+
@interpolated_strings ||= []
|
162
|
+
|
163
|
+
@interpolated_strings << str
|
164
|
+
"#{token}(#{@interpolated_strings.length})"
|
165
|
+
end
|
166
|
+
|
167
|
+
def decode_string(token, encoded_str)
|
168
|
+
@interpolated_strings ||= []
|
169
|
+
|
170
|
+
encoded_str.gsub(/(\w+)\((\d+)\)/) do |found|
|
171
|
+
token = Regexp.last_match(1)
|
172
|
+
idx = Regexp.last_match(2)
|
173
|
+
|
174
|
+
if idx.to_i.to_s == idx
|
175
|
+
@interpolated_strings[idx.to_i - 1]
|
176
|
+
else
|
177
|
+
found
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def param_safe_value(str)
|
183
|
+
return str unless str.include?(" ")
|
184
|
+
|
185
|
+
"\"#{str}\""
|
186
|
+
end
|
187
|
+
|
188
|
+
def set_filter_string
|
189
|
+
@filter_string = params[:filter_string] || session[:snitch_filter_string] || "status:unresolved"
|
190
|
+
@unknown_strings = []
|
191
|
+
@filters = {}
|
192
|
+
|
193
|
+
secret_key = loop do
|
194
|
+
token = SecureRandom.hex(10)
|
195
|
+
break token unless @filter_string.include?(token)
|
196
|
+
end
|
197
|
+
|
198
|
+
temp_filter_string = @filter_string.gsub(/\"(.*?)\"/) do |found|
|
199
|
+
encode_string(secret_key, Regexp.last_match(1))
|
200
|
+
end
|
201
|
+
|
202
|
+
search_strings = []
|
203
|
+
filter_strings = temp_filter_string.split(" ").select do |filter_string|
|
204
|
+
search_strings << filter_string unless filter_string.include?(":")
|
205
|
+
filter_string.include?(":")
|
206
|
+
end
|
207
|
+
|
208
|
+
filter_strings.each do |filter_string|
|
209
|
+
filter, value, * = filter_string.split(":")
|
210
|
+
if value.blank?
|
211
|
+
# Search pure text by itself
|
212
|
+
search_strings << value
|
213
|
+
elsif @filter_sets.keys.include?(filter.to_s.to_sym)
|
214
|
+
value = decode_string(secret_key, value)
|
215
|
+
@filters[filter.to_sym] = value.to_sym
|
216
|
+
elsif filter == "search"
|
217
|
+
search_strings << decode_string(secret_key, value)
|
218
|
+
else
|
219
|
+
@unknown_strings << filter_string
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
@filters[:search] = "\"#{search_strings.join(' ')}\"" if search_strings.any?
|
224
|
+
|
225
|
+
new_filter_strings = @filters.each_with_object([]) do |(filter_string, filter_value), filter_array|
|
226
|
+
filter_array << "#{filter_string}:#{filter_value}" if filter_value.present?
|
227
|
+
end
|
228
|
+
|
229
|
+
@filter_string = new_filter_strings.join(" ")
|
230
|
+
session[:snitch_filter_string] = @filter_string.dup
|
152
231
|
end
|
153
232
|
#
|
154
233
|
# def sort_reports
|
@@ -55,10 +55,10 @@ class SnitchReporting::SnitchOccurrence < ApplicationRecord
|
|
55
55
|
def backtrace=(trace_lines)
|
56
56
|
already_traced = []
|
57
57
|
self.backtrace_data = trace_lines.map do |trace_line|
|
58
|
-
next unless trace_line.include?("/app/")
|
58
|
+
next unless trace_line.include?("/app/")
|
59
59
|
|
60
60
|
joined_path = file_lines_from_backtrace(trace_line)
|
61
|
-
next if
|
61
|
+
next if already_traced.include?(joined_path)
|
62
62
|
already_traced << joined_path
|
63
63
|
|
64
64
|
file_path, line_number = joined_path.split(":", 2)
|
@@ -20,7 +20,6 @@ class SnitchReporting::SnitchReport < ApplicationRecord
|
|
20
20
|
has_many :histories, class_name: "SnitchReporting::SnitchHistory", foreign_key: :report_id
|
21
21
|
|
22
22
|
# belongs_to :assigned_to
|
23
|
-
def assigned_to; end
|
24
23
|
# belongs_to :resolved_by
|
25
24
|
# belongs_to :ignored_by
|
26
25
|
|
@@ -31,6 +30,7 @@ class SnitchReporting::SnitchReport < ApplicationRecord
|
|
31
30
|
scope :ignored, -> { where.not(ignored_at: nil) }
|
32
31
|
scope :unignored, -> { where(ignored_at: nil) }
|
33
32
|
scope :by_level, ->(*level_tags) { where(log_level: level_tags) }
|
33
|
+
scope :search, ->(text) { where("CONCAT(error, ' ', message) ILIKE :text", text: "%#{text}%") }
|
34
34
|
|
35
35
|
enum log_level: {
|
36
36
|
debug: 1,
|
@@ -271,7 +271,7 @@ class SnitchReporting::SnitchReport < ApplicationRecord
|
|
271
271
|
end
|
272
272
|
|
273
273
|
def tags=(new_tags)
|
274
|
-
super((new_tags.try(:to_a) || [new_tags]).compact.flatten)
|
274
|
+
super((new_tags.try(:to_a) || [new_tags]).compact.flatten.uniq)
|
275
275
|
end
|
276
276
|
|
277
277
|
def add_tags(new_tags)
|
@@ -1,27 +1,26 @@
|
|
1
1
|
<div class="snitch-reporting">
|
2
|
-
<!--
|
3
|
-
<div class="nav-tab">
|
4
|
-
<input type="search" name="search" value="<%= params[:search] %>" placeholder="Search">
|
5
|
-
</div>
|
6
|
-
-->
|
7
|
-
<div class="filters">
|
8
|
-
<% @filter_sets.each do |filter_name, filter_set| %>
|
9
|
-
<div class="snitch-table filter-table">
|
10
|
-
<div class="snitch-tr">
|
11
|
-
<div class="snitch-th"><%= filter_name.to_s.titleize %></div>
|
12
|
-
</div>
|
13
2
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
3
|
+
<%= form_tag interpret_search_path, class: "snitch-search-area" do %>
|
4
|
+
<button type="submit" name="button">Filter</button>
|
5
|
+
<input type="text" name="filter_string" value="<%= @filter_string %>">
|
6
|
+
<label for="search">
|
7
|
+
<div class="filters">
|
8
|
+
<% @filter_sets.each do |filter_name, filter_set| %>
|
9
|
+
<div class="snitch-table filter-table" data-filter-name="<%= filter_name %>" data-default="<%= filter_set[:default] %>">
|
10
|
+
<div class="snitch-tr">
|
11
|
+
<div class="snitch-th"><%= filter_name.to_s.titleize %></div>
|
12
|
+
</div>
|
13
|
+
|
14
|
+
<% filter_set[:values].each do |filter_value| %>
|
15
|
+
<div class="snitch-tr">
|
16
|
+
<%= link_to filter_value.to_s.titleize, {}, class: "snitch-td link-cell filter-cell", data: { filter_name: filter_name, filter_value: filter_value } %>
|
17
|
+
</div>
|
18
|
+
<% end %>
|
20
19
|
</div>
|
21
20
|
<% end %>
|
22
21
|
</div>
|
23
|
-
|
24
|
-
|
22
|
+
</label>
|
23
|
+
<% end %>
|
25
24
|
|
26
25
|
<div class="snitch-index">
|
27
26
|
<div class="snitch-title-section">
|
@@ -63,7 +62,6 @@
|
|
63
62
|
<span class="snitch-tag"><%= tag %></span><%= ', ' if idx < report.tags.length - 1 %>
|
64
63
|
<% end %>
|
65
64
|
</div>
|
66
|
-
<!-- <div class="snitch-td"><%= report.assigned_to.try(:name).presence || "-" %></div> -->
|
67
65
|
<div class="snitch-td">
|
68
66
|
<%= content_tag :input, "", type: :checkbox, name: :resolved, class: "snitch-resolution-switch", checked: report.resolved?, data: { mark_resolution_url: snitch_report_url(report) } %>
|
69
67
|
</div>
|
@@ -126,7 +126,7 @@
|
|
126
126
|
<div id="params" class="snitch-section">
|
127
127
|
<h3>Params</h3>
|
128
128
|
<div class="line-trace">
|
129
|
-
<span class="snitch-code"><%= JSON.pretty_generate(@occurrence.params) %></span>
|
129
|
+
<span class="snitch-code"><%= JSON.pretty_generate(@occurrence.params.presence || {}) %></span>
|
130
130
|
</div>
|
131
131
|
</div>
|
132
132
|
|
data/config/routes.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: snitch_reporting
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rocco Nicholls
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-02-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -78,6 +78,7 @@ files:
|
|
78
78
|
- README.md
|
79
79
|
- Rakefile
|
80
80
|
- app/assets/config/snitch_reporting_manifest.js
|
81
|
+
- app/assets/javascripts/snitch_reporting/search_bar.js
|
81
82
|
- app/assets/javascripts/snitch_reporting/snitch_report.js
|
82
83
|
- app/assets/stylesheets/snitch_reporting/_variables.scss
|
83
84
|
- app/assets/stylesheets/snitch_reporting/components.scss
|