newshound 1.0.1 → 1.0.3
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/CHANGELOG.md +7 -26
- data/lib/generators/newshound/install/templates/newshound.rb +5 -0
- data/lib/newshound/configuration.rb +4 -0
- data/lib/newshound/exception_reporter.rb +1 -1
- data/lib/newshound/exceptions/base.rb +4 -0
- data/lib/newshound/exceptions/bugsink.rb +77 -0
- data/lib/newshound/exceptions.rb +2 -2
- data/lib/newshound/middleware/banner_injector.rb +72 -10
- data/lib/newshound/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f63cb577cdf9cc38edce00142a140f07263b9a59ddad4308fa4956b796617e60
|
|
4
|
+
data.tar.gz: ba96c4b481cc3de1b462bba4b53a482e0ece9ac05ebaebc56f65e601b638a2d2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fb61b7b3f7bb65a5f0f888738ad78087cdc248e0dc512e82f467d4f85314428359443e6e3e5219e3d19c59e7cb63d9594b00e9d35505b4cda7e5e90ec25c9ad5
|
|
7
|
+
data.tar.gz: d86c02b4f21c5f2fc1780846848163a5d1205256c797f5c4138f1eed1ab1fb3c6003b10bdd60735232b375bb5cbf08196c1585bdd6f0d27edfa969896b07e6d4
|
data/CHANGELOG.md
CHANGED
|
@@ -5,39 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [1.0.
|
|
9
|
-
|
|
10
|
-
### Changed
|
|
11
|
-
|
|
12
|
-
- The authorize_with initializer will generate with the method inside the configure block. (2d6ef48)
|
|
8
|
+
## [1.0.3] - 2026-04-21
|
|
13
9
|
|
|
14
10
|
### Added
|
|
15
11
|
|
|
16
|
-
-
|
|
17
|
-
- Record ID in format_for_banner output for linking to individual records (e06d040)
|
|
18
|
-
|
|
19
|
-
## [0.2.8] - 2026-02-10
|
|
12
|
+
- failed_jobs_threshold configuration option (0caac13)
|
|
20
13
|
|
|
21
14
|
### Changed
|
|
22
15
|
|
|
23
|
-
-
|
|
24
|
-
- Job monitoring uses configurable adapter pattern via config.job_source (8d33d30)
|
|
25
|
-
|
|
26
|
-
### Removed
|
|
27
|
-
|
|
28
|
-
- Ruby 3.1 and 3.2 support (24e5b86)
|
|
29
|
-
- Hard dependency on que gem (8d33d30)
|
|
30
|
-
- QueReporter class (replaced by JobReporter + Jobs adapters) Version: major (8d33d30)
|
|
31
|
-
|
|
32
|
-
### Fixed
|
|
16
|
+
- Skip banner injection when there is nothing notable to display (0caac13)
|
|
33
17
|
|
|
34
|
-
|
|
35
|
-
- test_exceptions rake task calling report instead of banner_data (e440188)
|
|
36
|
-
- Banner overlaying content in apps with !important body padding-top rules (16f22f1)
|
|
18
|
+
## [1.0.2] - 2026-04-03
|
|
37
19
|
|
|
38
20
|
### Added
|
|
39
21
|
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
- ExceptionReporter#formatted_exception_count and #exception_summary helpers (e440188)
|
|
22
|
+
- Bugsink exception source adapter for HTTP-based error tracking (6d3e2a1)
|
|
23
|
+
- exception_source_config option for passing connection details to adapters (6d3e2a1)
|
|
24
|
+
- Minimize button for banner (c686227)
|
|
@@ -19,6 +19,11 @@ Newshound.configure do |config|
|
|
|
19
19
|
# config.job_source = :que # or a custom adapter instance
|
|
20
20
|
# See Newshound::Jobs::Base for the adapter interface
|
|
21
21
|
|
|
22
|
+
# Number of failed jobs at or below which the banner is suppressed
|
|
23
|
+
# Set higher if your app tolerates occasional background job failures
|
|
24
|
+
# Default is 0 (any failed job triggers the banner)
|
|
25
|
+
# config.failed_jobs_threshold = 0
|
|
26
|
+
|
|
22
27
|
# User roles that are authorized to view the Newshound banner
|
|
23
28
|
# These should match the role values in your User model
|
|
24
29
|
# Default is [:developer, :super_user]
|
|
@@ -4,7 +4,9 @@ module Newshound
|
|
|
4
4
|
class Configuration
|
|
5
5
|
attr_accessor :exception_limit, :enabled, :authorized_roles,
|
|
6
6
|
:current_user_method, :authorization_block, :exception_source,
|
|
7
|
+
:exception_source_config,
|
|
7
8
|
:warning_source, :warning_limit, :job_source,
|
|
9
|
+
:failed_jobs_threshold,
|
|
8
10
|
:exception_links, :job_links, :warning_links
|
|
9
11
|
|
|
10
12
|
def initialize
|
|
@@ -14,9 +16,11 @@ module Newshound
|
|
|
14
16
|
@current_user_method = :current_user
|
|
15
17
|
@authorization_block = nil
|
|
16
18
|
@exception_source = :exception_track
|
|
19
|
+
@exception_source_config = {}
|
|
17
20
|
@warning_source = nil
|
|
18
21
|
@warning_limit = 10
|
|
19
22
|
@job_source = nil
|
|
23
|
+
@failed_jobs_threshold = 0
|
|
20
24
|
@exception_links = {}
|
|
21
25
|
@job_links = {}
|
|
22
26
|
@warning_links = {}
|
|
@@ -51,7 +51,7 @@ module Newshound
|
|
|
51
51
|
|
|
52
52
|
def resolve_exception_source(source)
|
|
53
53
|
source ||= @configuration.exception_source
|
|
54
|
-
source.is_a?(Symbol) ? Exceptions.source(source) : source
|
|
54
|
+
source.is_a?(Symbol) ? Exceptions.source(source, @configuration.exception_source_config) : source
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def recent_exceptions
|
|
@@ -10,6 +10,10 @@ module Newshound
|
|
|
10
10
|
# - #format_for_report(exception) - Formats a single exception for Slack/report display
|
|
11
11
|
# - #format_for_banner(exception) - Formats a single exception for banner UI
|
|
12
12
|
class Base
|
|
13
|
+
def initialize(config = {})
|
|
14
|
+
@config = config
|
|
15
|
+
end
|
|
16
|
+
|
|
13
17
|
# Fetches recent exceptions from the exception tracking system
|
|
14
18
|
#
|
|
15
19
|
# @param time_range [ActiveSupport::Duration] Time duration to look back (e.g., 24.hours)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
require "uri"
|
|
6
|
+
require "time"
|
|
7
|
+
|
|
8
|
+
module Newshound
|
|
9
|
+
module Exceptions
|
|
10
|
+
class Bugsink < Base
|
|
11
|
+
def self.required_keys
|
|
12
|
+
%i[url token project_id].freeze
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(config = {})
|
|
16
|
+
super
|
|
17
|
+
config = config.transform_keys(&:to_sym)
|
|
18
|
+
|
|
19
|
+
missing = self.class.required_keys.select { |key| config[key].nil? }
|
|
20
|
+
if missing.any?
|
|
21
|
+
raise ArgumentError,
|
|
22
|
+
"Bugsink requires #{missing.map { |k| ":#{k}" }.join(", ")} in exception_source_config"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
@url = config[:url]
|
|
26
|
+
@token = config[:token]
|
|
27
|
+
@project_id = config[:project_id]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def recent(time_range:, limit:)
|
|
31
|
+
issues = fetch_issues
|
|
32
|
+
cutoff = Time.now.utc - time_range
|
|
33
|
+
|
|
34
|
+
issues
|
|
35
|
+
.select { |i| Time.parse(i["last_seen"]) >= cutoff }
|
|
36
|
+
.first(limit)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def format_for_report(issue, number)
|
|
40
|
+
<<~TEXT
|
|
41
|
+
*#{number}. #{issue["calculated_type"] || "Unknown"}*
|
|
42
|
+
• *Time:* #{Time.parse(issue["last_seen"]).strftime("%I:%M %p")}
|
|
43
|
+
• *Message:* `#{(issue["calculated_value"] || "").truncate(100)}`
|
|
44
|
+
TEXT
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def format_for_banner(issue)
|
|
48
|
+
{
|
|
49
|
+
id: issue["id"],
|
|
50
|
+
title: issue["calculated_type"] || "Unknown",
|
|
51
|
+
message: (issue["calculated_value"] || "").truncate(100),
|
|
52
|
+
location: issue["transaction"] || "",
|
|
53
|
+
time: Time.parse(issue["last_seen"]).strftime("%I:%M %p")
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def fetch_issues
|
|
60
|
+
uri = URI("#{@url.chomp("/")}/api/canonical/0/issues/")
|
|
61
|
+
uri.query = URI.encode_www_form(project: @project_id, sort: "last_seen", order: "desc")
|
|
62
|
+
|
|
63
|
+
request = Net::HTTP::Get.new(uri)
|
|
64
|
+
request["Authorization"] = "Bearer #{@token}"
|
|
65
|
+
request["Accept"] = "application/json"
|
|
66
|
+
|
|
67
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
|
|
68
|
+
http.request(request)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
raise "Bugsink API error: #{response.code}" unless response.is_a?(Net::HTTPSuccess)
|
|
72
|
+
|
|
73
|
+
JSON.parse(response.body)["results"] || []
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
data/lib/newshound/exceptions.rb
CHANGED
|
@@ -2,11 +2,11 @@ Dir[File.join(__dir__, "exceptions", "*.rb")].each { |file| require file }
|
|
|
2
2
|
|
|
3
3
|
module Newshound
|
|
4
4
|
module Exceptions
|
|
5
|
-
def self.source(source)
|
|
5
|
+
def self.source(source, config = {})
|
|
6
6
|
constant = constants.find { |c| c.to_s.gsub(/(?<!^)([A-Z])/, "_\\1").downcase == source.to_s }
|
|
7
7
|
raise "Invalid exception source: #{source}" unless constant
|
|
8
8
|
|
|
9
|
-
const_get(constant).new
|
|
9
|
+
const_get(constant).new(config)
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
end
|
|
@@ -19,8 +19,9 @@ module Newshound
|
|
|
19
19
|
return [status, headers, response] unless controller
|
|
20
20
|
return [status, headers, response] unless Newshound::Authorization.authorized?(controller)
|
|
21
21
|
|
|
22
|
-
# Get banner HTML
|
|
22
|
+
# Get banner HTML (nil when nothing notable to display)
|
|
23
23
|
banner_html = generate_banner_html
|
|
24
|
+
return [status, headers, response] unless banner_html
|
|
24
25
|
|
|
25
26
|
# Inject banner after <body> tag
|
|
26
27
|
new_response = inject_banner(response, banner_html)
|
|
@@ -62,10 +63,20 @@ module Newshound
|
|
|
62
63
|
job_data = job_reporter.banner_data
|
|
63
64
|
warning_data = warning_reporter.banner_data
|
|
64
65
|
|
|
65
|
-
|
|
66
|
+
return nil unless notable_data?(exception_data, job_data, warning_data)
|
|
67
|
+
|
|
66
68
|
render_banner(exception_data, job_data, warning_data)
|
|
67
69
|
end
|
|
68
70
|
|
|
71
|
+
def notable_data?(exception_data, job_data, warning_data)
|
|
72
|
+
exception_count = exception_data[:exceptions]&.length || 0
|
|
73
|
+
failed_jobs = job_data.dig(:queue_stats, :failed) || 0
|
|
74
|
+
warning_count = warning_data[:warnings]&.length || 0
|
|
75
|
+
threshold = Newshound.configuration.failed_jobs_threshold
|
|
76
|
+
|
|
77
|
+
exception_count > 0 || warning_count > 0 || failed_jobs > threshold
|
|
78
|
+
end
|
|
79
|
+
|
|
69
80
|
def render_banner(exception_data, job_data, warning_data = {})
|
|
70
81
|
<<~HTML
|
|
71
82
|
<div id="newshound-banner" class="newshound-banner newshound-collapsed">
|
|
@@ -75,13 +86,19 @@ module Newshound
|
|
|
75
86
|
🐕 Newshound
|
|
76
87
|
#{summary_badge(exception_data, job_data, warning_data)}
|
|
77
88
|
</span>
|
|
78
|
-
<span class="newshound-
|
|
89
|
+
<span class="newshound-header-controls">
|
|
90
|
+
<span class="newshound-minimize" onclick="event.stopPropagation(); document.getElementById('newshound-banner').classList.add('newshound-minimized'); localStorage.setItem('newshound-minimized','1'); window.newshoundUpdatePadding();" title="Minimize">−</span>
|
|
91
|
+
<span class="newshound-toggle">▼</span>
|
|
92
|
+
</span>
|
|
79
93
|
</div>
|
|
80
94
|
<div class="newshound-content">
|
|
81
95
|
#{render_exceptions(exception_data)}
|
|
82
96
|
#{render_warnings(warning_data)}
|
|
83
97
|
#{render_jobs(job_data)}
|
|
84
98
|
</div>
|
|
99
|
+
<div class="newshound-restore" onclick="document.getElementById('newshound-banner').classList.remove('newshound-minimized'); localStorage.removeItem('newshound-minimized'); window.newshoundUpdatePadding();" title="Restore Newshound">
|
|
100
|
+
🐕
|
|
101
|
+
</div>
|
|
85
102
|
</div>
|
|
86
103
|
#{render_script}
|
|
87
104
|
HTML
|
|
@@ -91,6 +108,11 @@ module Newshound
|
|
|
91
108
|
<<~JS
|
|
92
109
|
<script>
|
|
93
110
|
(function() {
|
|
111
|
+
// Restore minimized state from localStorage
|
|
112
|
+
if (localStorage.getItem('newshound-minimized')) {
|
|
113
|
+
document.getElementById('newshound-banner').classList.add('newshound-minimized');
|
|
114
|
+
}
|
|
115
|
+
|
|
94
116
|
var cachedPriority, cachedBodyRule;
|
|
95
117
|
|
|
96
118
|
// Detect once whether any non-newshound stylesheet uses !important on body padding-top
|
|
@@ -290,6 +312,48 @@ module Newshound
|
|
|
290
312
|
a.newshound-stat:hover {
|
|
291
313
|
background: rgba(255,255,255,0.2);
|
|
292
314
|
}
|
|
315
|
+
.newshound-header-controls {
|
|
316
|
+
display: flex;
|
|
317
|
+
align-items: center;
|
|
318
|
+
gap: 12px;
|
|
319
|
+
}
|
|
320
|
+
.newshound-minimize {
|
|
321
|
+
cursor: pointer;
|
|
322
|
+
font-size: 18px;
|
|
323
|
+
font-weight: 700;
|
|
324
|
+
line-height: 1;
|
|
325
|
+
opacity: 0.7;
|
|
326
|
+
padding: 0 4px;
|
|
327
|
+
}
|
|
328
|
+
.newshound-minimize:hover {
|
|
329
|
+
opacity: 1;
|
|
330
|
+
}
|
|
331
|
+
.newshound-restore {
|
|
332
|
+
display: none;
|
|
333
|
+
}
|
|
334
|
+
.newshound-banner.newshound-minimized .newshound-header {
|
|
335
|
+
display: none;
|
|
336
|
+
}
|
|
337
|
+
.newshound-banner.newshound-minimized .newshound-content {
|
|
338
|
+
max-height: 0;
|
|
339
|
+
overflow: hidden;
|
|
340
|
+
border-top: none;
|
|
341
|
+
}
|
|
342
|
+
.newshound-banner.newshound-minimized .newshound-restore {
|
|
343
|
+
display: flex;
|
|
344
|
+
align-items: center;
|
|
345
|
+
justify-content: center;
|
|
346
|
+
width: 36px;
|
|
347
|
+
height: 36px;
|
|
348
|
+
cursor: pointer;
|
|
349
|
+
font-size: 16px;
|
|
350
|
+
user-select: none;
|
|
351
|
+
}
|
|
352
|
+
.newshound-banner.newshound-minimized {
|
|
353
|
+
left: auto;
|
|
354
|
+
border-radius: 0 0 0 8px;
|
|
355
|
+
box-shadow: -2px 2px 6px rgba(0,0,0,0.2);
|
|
356
|
+
}
|
|
293
357
|
</style>
|
|
294
358
|
CSS
|
|
295
359
|
end
|
|
@@ -298,19 +362,17 @@ module Newshound
|
|
|
298
362
|
exception_count = exception_data[:exceptions]&.length || 0
|
|
299
363
|
failed_jobs = job_data.dig(:queue_stats, :failed) || 0
|
|
300
364
|
warning_count = warning_data[:warnings]&.length || 0
|
|
365
|
+
threshold = Newshound.configuration.failed_jobs_threshold
|
|
301
366
|
|
|
302
|
-
if exception_count > 0
|
|
367
|
+
if exception_count > 0
|
|
303
368
|
badge_class = "newshound-error"
|
|
304
|
-
text = "#{exception_count} exceptions
|
|
305
|
-
|
|
369
|
+
text = "#{exception_count} exceptions"
|
|
370
|
+
else
|
|
306
371
|
badge_class = "newshound-warning"
|
|
307
372
|
parts = []
|
|
308
373
|
parts << "#{warning_count} warnings" if warning_count > 0
|
|
309
|
-
parts << "#{failed_jobs} failed jobs" if failed_jobs >
|
|
374
|
+
parts << "#{failed_jobs} failed jobs" if failed_jobs > threshold
|
|
310
375
|
text = parts.join(", ")
|
|
311
|
-
else
|
|
312
|
-
badge_class = "newshound-success"
|
|
313
|
-
text = "All clear"
|
|
314
376
|
end
|
|
315
377
|
|
|
316
378
|
%(<span class="newshound-badge #{badge_class}">#{text}</span>)
|
data/lib/newshound/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: newshound
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Savannah Moore
|
|
@@ -49,6 +49,7 @@ files:
|
|
|
49
49
|
- lib/newshound/exception_reporter.rb
|
|
50
50
|
- lib/newshound/exceptions.rb
|
|
51
51
|
- lib/newshound/exceptions/base.rb
|
|
52
|
+
- lib/newshound/exceptions/bugsink.rb
|
|
52
53
|
- lib/newshound/exceptions/exception_track.rb
|
|
53
54
|
- lib/newshound/exceptions/solid_errors.rb
|
|
54
55
|
- lib/newshound/job_reporter.rb
|