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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c842988f84c5f628e15f57cc36835b85fed9cec880b7d2fb2e6b137d9a74cbd
4
- data.tar.gz: 77290e2cf520feb8d711cf9f1d45df0a9f43d4b5ae93a071d1aba56e61c2fd42
3
+ metadata.gz: ea0cf12cf265e5dbdaeb502ce609cda532d5905041ec0f2ec96cde59cedeedcb
4
+ data.tar.gz: e22d3a0e011b516fd2f69ca487a674bcefbfbb5722940920c8f1e2cceb1108fa
5
5
  SHA512:
6
- metadata.gz: e21a8a1e211bdba81d0af8bdb98948d396013337a98c6c1a624318444634c0be43e767b4a0b7dbdc08892af50580662be3c5ed9c12a9bc68aa53da3dfafb2a6f
7
- data.tar.gz: 26b836607918d65d06c165422dded336808a8cf09bc154feded8a5985b64d844ed2878519ca5ffe8d1257a5c79c3d927be15b7c112539fdfa6a92eb883220dcb
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
+ }
@@ -1,3 +1,5 @@
1
+ //= require_tree .
2
+
1
3
  document.addEventListener("change", function(evt) {
2
4
  if (evt.target && evt.target.hasAttribute("data-mark-resolution-url")) {
3
5
  var report_url = evt.target.getAttribute("data-mark-resolution-url")
@@ -78,6 +78,7 @@ todo {
78
78
  border-radius: 4px;
79
79
  background: $border-grey;
80
80
  padding: 3px 5px;
81
+ line-height: 2;
81
82
  white-space: nowrap;
82
83
  }
83
84
 
@@ -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
- # @report_preferences = begin
97
- # preferences = JSON.parse(session[:report_preferences].presence || "{}").symbolize_keys
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[:report_preferences] = preferences.to_json
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
- # log_level: {
121
- # default: :any,
122
- # values: [:any] + ::SnitchReporting::SnitchReport.log_levels.keys.map(&:to_sym)
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
- @filters = @filter_sets.each_with_object({set_filters: {}}) do |(filter_name, filter_set), filters|
131
- filters[filter_name] = filter_set[:default]
132
- filter_in_param = params[filter_name].try(:to_sym)
133
- next unless filter_in_param && filter_set[:values].include?(filter_in_param)
134
- filters[filter_name] = filter_in_param
135
- filters[:set_filters][filter_name] = filter_in_param
136
- end
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
- # @reports = @reports.search(@report_preferences[:by_fuzzy_text]) if @report_preferences[:by_fuzzy_text].present?
150
+ @reports = @reports.search(@filters[:search]) if @filters[:search].present?
145
151
  #
146
- # @reports = @reports.by_level(@report_preferences[:level_tags]) if @report_preferences[:level_tags].present?
147
- # @reports = @reports.by_severity(@report_preferences[:severity_tags]) if @report_preferences[:severity_tags].present?
148
- # @reports = @reports.by_source(@report_preferences[:source_tags]) if @report_preferences[:source_tags].present?
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 = @report_preferences[:resolved].present? && truthy?(@report_preferences[:resolved]) ? @reports.resolved : @reports.unresolved
151
- # @reports = @report_preferences[:ignored].present? && truthy?(@report_preferences[:ignored]) ? @reports.ignored : @reports.unignored
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/") # && trace_line.exclude?("app/models/snitch_reporting")
58
+ next unless trace_line.include?("/app/")
59
59
 
60
60
  joined_path = file_lines_from_backtrace(trace_line)
61
- next if joined_path.include?(joined_path)
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
- <% selected_filter_value = @filters[filter_name] %>
15
- <% default_filter_value = filter_set[:default] %>
16
- <% filter_set[:values].each do |filter_value| %>
17
- <div class="snitch-tr">
18
- <% url_value = filter_value == default_filter_value ? @filters[:set_filters].except(filter_name) : @filters[:set_filters].merge(filter_name => filter_value) %>
19
- <%= link_to filter_value.to_s.titleize, url_value, class: "snitch-td link-cell #{'selected' if selected_filter_value == filter_value}" %>
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
- <% end %>
24
- </div>
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
@@ -2,4 +2,5 @@ SnitchReporting::Engine.routes.draw do
2
2
  root to: "snitch_reports#index"
3
3
 
4
4
  resources :snitch_reports, path: "/", only: [:index, :show, :update, :edit]
5
+ post :interpret_search, controller: :snitch_reports
5
6
  end
@@ -1,3 +1,3 @@
1
1
  module SnitchReporting
2
- VERSION = '1.0.7'
2
+ VERSION = '1.1.0'
3
3
  end
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.7
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-01-27 00:00:00.000000000 Z
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