snitch_reporting 1.0.7 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|