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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 79ace59ee869878db3221ca5d851c9bf10ae50354877d6b4a2b57ccc9489319e
4
- data.tar.gz: 9a0e6d98ce5ac41b433c19d66ae920b207c090d7550df505645068c46f627feb
3
+ metadata.gz: f63cb577cdf9cc38edce00142a140f07263b9a59ddad4308fa4956b796617e60
4
+ data.tar.gz: ba96c4b481cc3de1b462bba4b53a482e0ece9ac05ebaebc56f65e601b638a2d2
5
5
  SHA512:
6
- metadata.gz: 7124a65e125a08eea643486de24db7e84436c981ccd6e9c6fa12b205b33f1e2e30b062d38c766ab0f9df18bda79fd3d093b5bfccf23b5d9938b0c45e45d6a23e
7
- data.tar.gz: d414aa404515671d71525214ebe0f601e456c6704e5bfccf29a49246454ea9f9d83e899576db2903cfe4d0491b8255a915d6b17a4c5cdefb879d5318b0476f66
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.1] - 2026-03-17
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
- - Configurable banner links for exceptions, jobs, and warnings via string path patterns (1df1636)
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
- - Ruby support to 3.3+ and added Ruby 4.0 to CI matrix (24e5b86)
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
- - Set the correct location for the repository on the web. (cb7bb88)
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
- - Jobs adapter pattern with Base class and registry (c50ba9c)
41
- - Jobs::Que adapter for Que job backend (c50ba9c)
42
- - JobReporter that delegates to a configurable job source adapter (c50ba9c)
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
@@ -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
- # Generate HTML from template
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-toggle">▼</span>
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 || failed_jobs > 10
367
+ if exception_count > 0
303
368
  badge_class = "newshound-error"
304
- text = "#{exception_count} exceptions, #{failed_jobs} failed jobs"
305
- elsif warning_count > 0 || failed_jobs > 5
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 > 5
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>)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Newshound
4
- VERSION = "1.0.1"
4
+ VERSION = "1.0.3"
5
5
  end
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.1
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