newshound 0.2.2 → 0.2.5
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 +17 -31
- data/README.md +5 -11
- data/Rakefile +1 -0
- data/checksums/newshound-0.2.3.gem.sha512 +1 -0
- data/checksums/newshound-0.2.5.gem.sha512 +1 -0
- data/lib/newshound/configuration.rb +4 -1
- data/lib/newshound/exceptions/exception_track.rb +23 -44
- data/lib/newshound/exceptions/solid_errors.rb +29 -48
- data/lib/newshound/middleware/banner_injector.rb +38 -7
- data/lib/newshound/version.rb +1 -1
- data/lib/newshound/warning_reporter.rb +80 -0
- data/lib/newshound/warnings/base.rb +42 -0
- data/lib/newshound/warnings.rb +56 -0
- data/lib/newshound.rb +2 -0
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3d9a10844e45f2216a4e2f9a3aa895d72a2cbffb09c157460ce989e7a390564b
|
|
4
|
+
data.tar.gz: e271a3cd7916ec4eee93274e7601f14e4c7dd55d6db92ae55dce2f74f9c31245
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a3c24e8dcf33ef8ea5c346e566f6a5441afa0657ab338b2599806133088f0f19ed36af407cf3d93da087bc76965dc35b3d135b222d6b0aa53f5caa610e015954
|
|
7
|
+
data.tar.gz: 3f2197e8a8bd9d99a2968df964bfd0c471d1c344a365c31a8e092ea9d5c09dfe9f4a87f97da14b3e92e32f9b9219327414998c3f36779b6780ef56b0f03cd9fd
|
data/CHANGELOG.md
CHANGED
|
@@ -5,45 +5,31 @@ 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
|
-
## [0.2.
|
|
8
|
+
## [0.2.5] - 2025-12-12
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
- Separate linter job for RuboCop (782cc57)
|
|
17
|
-
- Coverage reporting with SimpleCov (782cc57)
|
|
18
|
-
- .claude/settings.local.json to .gitignore (701d802)
|
|
19
|
-
- Reissue gem dependency for automated versioning (feac98a)
|
|
20
|
-
- Git trailer support for changelog management (feac98a)
|
|
21
|
-
- Initial CHANGELOG.md with Keep a Changelog format (feac98a)
|
|
22
|
-
- CODEOWNERS file for SOFware/engineers (1a0a1f5)
|
|
23
|
-
- Newshound::Exceptions::Base for standard API to interact with exception data (9dce846)
|
|
24
|
-
- Configuration exception_source to allow for future alternative exception backends (9dce846)
|
|
25
|
-
- SolidErrors support (8b5b48a)
|
|
12
|
+
- Add `Newshound::Warnings` module with registry-based adapter pattern for custom warning sources
|
|
13
|
+
- Add `Newshound::Warnings::Base` abstract class for warning adapters
|
|
14
|
+
- Add `Newshound::WarningReporter` for fetching and formatting warnings
|
|
15
|
+
- Display warnings in banner alongside exceptions and job stats
|
|
26
16
|
|
|
27
|
-
###
|
|
17
|
+
### Fixed
|
|
28
18
|
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
-
|
|
32
|
-
- Simplified test setup by removing unused mocks (701d802)
|
|
33
|
-
- Rakefile to use reissue tasks instead of manual versioning (feac98a)
|
|
34
|
-
- Removed Ruby 3.0 from test matrix (now testing 3.1-3.4) (4beafd2)
|
|
35
|
-
- Updated gemspec required_ruby_version to >= 3.1.0 (4beafd2)
|
|
19
|
+
- Fixed summary badge to show combined warnings and failed jobs count
|
|
20
|
+
|
|
21
|
+
## [0.2.4] - 2025-12-12
|
|
36
22
|
|
|
37
23
|
### Fixed
|
|
38
24
|
|
|
39
|
-
-
|
|
40
|
-
- Mock ActiveRecord::Base.connection and its methods (701d802)
|
|
41
|
-
- Mock connection.execute to return database-like results (701d802)
|
|
42
|
-
- Mock connection.quote and connection.select_value properly (701d802)
|
|
43
|
-
- Name and email in gemspec authors (ab44f30)
|
|
25
|
+
- Fixed release workflow to properly create version bump PRs using gh pr create instead of peter-evans/create-pull-request action, resolving compatibility issue with reissue gem
|
|
44
26
|
|
|
45
|
-
|
|
27
|
+
## [0.2.3] - 2025-10-29
|
|
46
28
|
|
|
47
|
-
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- Updated styling in banner injector to attempt to keep newshound banner above other application's menus, instead of hovering over them.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
48
34
|
|
|
49
|
-
|
|
35
|
+
- Consolidated exception data extraction in ExceptionTrack and SolidErrors (55cffd3)
|
data/README.md
CHANGED
|
@@ -203,18 +203,12 @@ bin/console
|
|
|
203
203
|
|
|
204
204
|
## Release Management
|
|
205
205
|
|
|
206
|
-
This gem uses [Reissue](https://github.com/rails/reissue) for release management.
|
|
207
|
-
the following steps as you would with any other ruby gem:
|
|
206
|
+
This gem uses [Reissue](https://github.com/rails/reissue) for release management. Releases are automated via GitHub Actions:
|
|
208
207
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
```bash
|
|
214
|
-
bundle exec rake release
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
The final step is to push your version bump branch, open a PR, and merge it.
|
|
208
|
+
1. Navigate to Actions → "Release gem to RubyGems.org"
|
|
209
|
+
2. Click "Run workflow"
|
|
210
|
+
3. Select version segment to bump (patch, minor, major)
|
|
211
|
+
4. Review and merge the auto-generated PR
|
|
218
212
|
|
|
219
213
|
## Dependencies
|
|
220
214
|
|
data/Rakefile
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
4b38b6628a214ff2cf66a400e184a5659a2ba82ccf46efde37293d93dd77534ffe87d3dd41220e0cac43aabaed66318cbdb70b8cbf66988f744f04ead452bbc5
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
e17f9781dfbe16702fdda1809ec0e69eacbb3ed762d8df8fb0c471b79860989a0dfedebbb22923bf699c4b243ad8a543bdab374b3ab6c296b67cae3c4b5c305a
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
module Newshound
|
|
4
4
|
class Configuration
|
|
5
5
|
attr_accessor :exception_limit, :enabled, :authorized_roles,
|
|
6
|
-
:current_user_method, :authorization_block, :exception_source
|
|
6
|
+
:current_user_method, :authorization_block, :exception_source,
|
|
7
|
+
:warning_source, :warning_limit
|
|
7
8
|
|
|
8
9
|
def initialize
|
|
9
10
|
@exception_limit = 10
|
|
@@ -12,6 +13,8 @@ module Newshound
|
|
|
12
13
|
@current_user_method = :current_user
|
|
13
14
|
@authorization_block = nil
|
|
14
15
|
@exception_source = :exception_track
|
|
16
|
+
@warning_source = nil
|
|
17
|
+
@warning_limit = 10
|
|
15
18
|
end
|
|
16
19
|
|
|
17
20
|
# Allow custom authorization logic
|
|
@@ -12,53 +12,41 @@ module Newshound
|
|
|
12
12
|
details = parse_exception_details(exception)
|
|
13
13
|
|
|
14
14
|
<<~TEXT
|
|
15
|
-
*#{number}. #{
|
|
15
|
+
*#{number}. #{details[:title]}*
|
|
16
16
|
• *Time:* #{exception.created_at.strftime("%I:%M %p")}
|
|
17
17
|
#{format_controller(details)}
|
|
18
|
-
#{format_message(
|
|
18
|
+
#{format_message(details)}
|
|
19
19
|
TEXT
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def format_for_banner(exception)
|
|
23
23
|
details = parse_exception_details(exception)
|
|
24
24
|
|
|
25
|
-
# Extract message
|
|
26
|
-
message = if details["message"].present?
|
|
27
|
-
details["message"].to_s
|
|
28
|
-
elsif exception.respond_to?(:message) && exception.message.present?
|
|
29
|
-
exception.message.to_s
|
|
30
|
-
else
|
|
31
|
-
+""
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Extract location
|
|
35
|
-
location = if details["controller_name"] && details["action_name"]
|
|
36
|
-
"#{details["controller_name"]}##{details["action_name"]}"
|
|
37
|
-
else
|
|
38
|
-
+""
|
|
39
|
-
end
|
|
40
|
-
|
|
41
25
|
{
|
|
42
|
-
title:
|
|
43
|
-
message: message.truncate(100),
|
|
44
|
-
location: location,
|
|
26
|
+
title: details[:title],
|
|
27
|
+
message: details[:message].truncate(100),
|
|
28
|
+
location: details[:location],
|
|
45
29
|
time: exception.created_at.strftime("%I:%M %p")
|
|
46
30
|
}
|
|
47
31
|
end
|
|
48
32
|
|
|
49
33
|
private
|
|
50
34
|
|
|
51
|
-
def
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"Unknown Exception"
|
|
58
|
-
|
|
35
|
+
def parse_exception_details(exception)
|
|
36
|
+
body_data = parse_body(exception)
|
|
37
|
+
controller_name = body_data["controller_name"]
|
|
38
|
+
action_name = body_data["action_name"]
|
|
39
|
+
|
|
40
|
+
{
|
|
41
|
+
title: exception.try(:title).presence || exception.try(:exception_class).presence || "Unknown Exception",
|
|
42
|
+
message: body_data["message"].presence&.to_s || exception.try(:message).presence&.to_s || +"",
|
|
43
|
+
location: (controller_name && action_name) ? "#{controller_name}##{action_name}" : +"",
|
|
44
|
+
controller_name: controller_name,
|
|
45
|
+
action_name: action_name
|
|
46
|
+
}
|
|
59
47
|
end
|
|
60
48
|
|
|
61
|
-
def
|
|
49
|
+
def parse_body(exception)
|
|
62
50
|
return {} unless exception.respond_to?(:body) && exception.body.present?
|
|
63
51
|
|
|
64
52
|
JSON.parse(exception.body)
|
|
@@ -67,24 +55,15 @@ module Newshound
|
|
|
67
55
|
end
|
|
68
56
|
|
|
69
57
|
def format_controller(details)
|
|
70
|
-
return +"" unless details
|
|
58
|
+
return +"" unless details in {controller_name: String, action_name: String}
|
|
71
59
|
|
|
72
|
-
"• *Controller:* #{details[
|
|
60
|
+
"• *Controller:* #{details[:location]}\n"
|
|
73
61
|
end
|
|
74
62
|
|
|
75
|
-
def format_message(
|
|
76
|
-
details
|
|
77
|
-
|
|
78
|
-
# Try to get message from different sources
|
|
79
|
-
message = if details["message"].present?
|
|
80
|
-
details["message"]
|
|
81
|
-
elsif exception.respond_to?(:message) && exception.message.present?
|
|
82
|
-
exception.message
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
return +"" unless message.present?
|
|
63
|
+
def format_message(details)
|
|
64
|
+
return +"" unless details in {message: String}
|
|
86
65
|
|
|
87
|
-
message = message.to_s.truncate(100)
|
|
66
|
+
message = details[:message].to_s.truncate(100)
|
|
88
67
|
"• *Message:* `#{message}`"
|
|
89
68
|
end
|
|
90
69
|
end
|
|
@@ -9,60 +9,50 @@ module Newshound
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def format_for_report(exception, number)
|
|
12
|
-
|
|
12
|
+
details = parse_exception_details(exception)
|
|
13
13
|
|
|
14
14
|
<<~TEXT
|
|
15
|
-
*#{number}. #{
|
|
15
|
+
*#{number}. #{details[:title]}*
|
|
16
16
|
• *Time:* #{exception.created_at.strftime("%I:%M %p")}
|
|
17
|
-
#{format_controller(
|
|
18
|
-
#{format_message(
|
|
17
|
+
#{format_controller(details)}
|
|
18
|
+
#{format_message(details)}
|
|
19
19
|
TEXT
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def format_for_banner(exception)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
# Extract message
|
|
26
|
-
message = if exception.respond_to?(:message) && exception.message.present?
|
|
27
|
-
exception.message.to_s
|
|
28
|
-
elsif context["message"].present?
|
|
29
|
-
context["message"].to_s
|
|
30
|
-
else
|
|
31
|
-
+""
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Extract location from context
|
|
35
|
-
location = if context["controller"] && context["action"]
|
|
36
|
-
"#{context["controller"]}##{context["action"]}"
|
|
37
|
-
else
|
|
38
|
-
+""
|
|
39
|
-
end
|
|
23
|
+
details = parse_exception_details(exception)
|
|
40
24
|
|
|
41
25
|
{
|
|
42
|
-
title:
|
|
43
|
-
message: message.truncate(100),
|
|
44
|
-
location: location,
|
|
26
|
+
title: details[:title],
|
|
27
|
+
message: details[:message].truncate(100),
|
|
28
|
+
location: details[:location],
|
|
45
29
|
time: exception.created_at.strftime("%I:%M %p")
|
|
46
30
|
}
|
|
47
31
|
end
|
|
48
32
|
|
|
49
33
|
private
|
|
50
34
|
|
|
51
|
-
def
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
35
|
+
def parse_exception_details(exception)
|
|
36
|
+
context_data = parse_context(exception)
|
|
37
|
+
controller = context_data["controller"]
|
|
38
|
+
action = context_data["action"]
|
|
39
|
+
|
|
40
|
+
{
|
|
41
|
+
title: exception.try(:error_class).presence || "Unknown Exception",
|
|
42
|
+
message: exception.try(:message).presence&.to_s || context_data["message"].presence&.to_s || +"",
|
|
43
|
+
location: (controller && action) ? "#{controller}##{action}" : +"",
|
|
44
|
+
controller: controller,
|
|
45
|
+
action: action
|
|
46
|
+
}
|
|
57
47
|
end
|
|
58
48
|
|
|
59
49
|
def parse_context(exception)
|
|
60
50
|
return {} unless exception.respond_to?(:context) && exception.context.present?
|
|
61
51
|
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
case exception.context
|
|
53
|
+
when Hash
|
|
64
54
|
exception.context
|
|
65
|
-
|
|
55
|
+
when String
|
|
66
56
|
JSON.parse(exception.context)
|
|
67
57
|
else
|
|
68
58
|
{}
|
|
@@ -71,25 +61,16 @@ module Newshound
|
|
|
71
61
|
{}
|
|
72
62
|
end
|
|
73
63
|
|
|
74
|
-
def format_controller(
|
|
75
|
-
return +"" unless
|
|
64
|
+
def format_controller(details)
|
|
65
|
+
return +"" unless details in {controller: String, action: String}
|
|
76
66
|
|
|
77
|
-
"• *Controller:* #{
|
|
67
|
+
"• *Controller:* #{details[:location]}\n"
|
|
78
68
|
end
|
|
79
69
|
|
|
80
|
-
def format_message(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
# Try to get message from different sources
|
|
84
|
-
message = if exception.respond_to?(:message) && exception.message.present?
|
|
85
|
-
exception.message
|
|
86
|
-
elsif context["message"].present?
|
|
87
|
-
context["message"]
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
return +"" unless message.present?
|
|
70
|
+
def format_message(details)
|
|
71
|
+
return +"" unless details in {message: String}
|
|
91
72
|
|
|
92
|
-
message = message.to_s.truncate(100)
|
|
73
|
+
message = details[:message].to_s.truncate(100)
|
|
93
74
|
"• *Message:* `#{message}`"
|
|
94
75
|
end
|
|
95
76
|
end
|
|
@@ -56,27 +56,30 @@ module Newshound
|
|
|
56
56
|
def generate_banner_html
|
|
57
57
|
exception_reporter = Newshound::ExceptionReporter.new
|
|
58
58
|
que_reporter = Newshound::QueReporter.new
|
|
59
|
+
warning_reporter = Newshound::WarningReporter.new
|
|
59
60
|
|
|
60
61
|
exception_data = exception_reporter.banner_data
|
|
61
62
|
job_data = que_reporter.banner_data
|
|
63
|
+
warning_data = warning_reporter.banner_data
|
|
62
64
|
|
|
63
65
|
# Generate HTML from template
|
|
64
|
-
render_banner(exception_data, job_data)
|
|
66
|
+
render_banner(exception_data, job_data, warning_data)
|
|
65
67
|
end
|
|
66
68
|
|
|
67
|
-
def render_banner(exception_data, job_data)
|
|
69
|
+
def render_banner(exception_data, job_data, warning_data = {})
|
|
68
70
|
<<~HTML
|
|
69
71
|
<div id="newshound-banner" class="newshound-banner newshound-collapsed">
|
|
70
72
|
#{render_styles}
|
|
71
73
|
<div class="newshound-header" onclick="document.getElementById('newshound-banner').classList.toggle('newshound-collapsed'); window.newshoundUpdatePadding();">
|
|
72
74
|
<span class="newshound-title">
|
|
73
75
|
🐕 Newshound
|
|
74
|
-
#{summary_badge(exception_data, job_data)}
|
|
76
|
+
#{summary_badge(exception_data, job_data, warning_data)}
|
|
75
77
|
</span>
|
|
76
78
|
<span class="newshound-toggle">▼</span>
|
|
77
79
|
</div>
|
|
78
80
|
<div class="newshound-content">
|
|
79
81
|
#{render_exceptions(exception_data)}
|
|
82
|
+
#{render_warnings(warning_data)}
|
|
80
83
|
#{render_jobs(job_data)}
|
|
81
84
|
</div>
|
|
82
85
|
</div>
|
|
@@ -117,7 +120,7 @@ module Newshound
|
|
|
117
120
|
<<~CSS
|
|
118
121
|
<style>
|
|
119
122
|
body {
|
|
120
|
-
padding-top:
|
|
123
|
+
padding-top: 50px;
|
|
121
124
|
transition: padding-top 0.3s ease-out;
|
|
122
125
|
}
|
|
123
126
|
.newshound-banner {
|
|
@@ -236,16 +239,20 @@ module Newshound
|
|
|
236
239
|
CSS
|
|
237
240
|
end
|
|
238
241
|
|
|
239
|
-
def summary_badge(exception_data, job_data)
|
|
242
|
+
def summary_badge(exception_data, job_data, warning_data = {})
|
|
240
243
|
exception_count = exception_data[:exceptions]&.length || 0
|
|
241
244
|
failed_jobs = job_data.dig(:queue_stats, :failed) || 0
|
|
245
|
+
warning_count = warning_data[:warnings]&.length || 0
|
|
242
246
|
|
|
243
247
|
if exception_count > 0 || failed_jobs > 10
|
|
244
248
|
badge_class = "error"
|
|
245
249
|
text = "#{exception_count} exceptions, #{failed_jobs} failed jobs"
|
|
246
|
-
elsif failed_jobs > 5
|
|
250
|
+
elsif warning_count > 0 || failed_jobs > 5
|
|
247
251
|
badge_class = "warning"
|
|
248
|
-
|
|
252
|
+
parts = []
|
|
253
|
+
parts << "#{warning_count} warnings" if warning_count > 0
|
|
254
|
+
parts << "#{failed_jobs} failed jobs" if failed_jobs > 5
|
|
255
|
+
text = parts.join(", ")
|
|
249
256
|
else
|
|
250
257
|
badge_class = "success"
|
|
251
258
|
text = "All clear"
|
|
@@ -280,6 +287,30 @@ module Newshound
|
|
|
280
287
|
HTML
|
|
281
288
|
end
|
|
282
289
|
|
|
290
|
+
def render_warnings(data)
|
|
291
|
+
warnings = data[:warnings] || []
|
|
292
|
+
|
|
293
|
+
return "" if warnings.empty?
|
|
294
|
+
|
|
295
|
+
items = warnings.take(5).map do |w|
|
|
296
|
+
<<~HTML
|
|
297
|
+
<div class="newshound-item">
|
|
298
|
+
<div class="newshound-item-title">#{escape_html(w[:title])}</div>
|
|
299
|
+
<div class="newshound-item-detail">
|
|
300
|
+
#{escape_html(w[:message])} • #{escape_html(w[:location])} • #{escape_html(w[:time])}
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
HTML
|
|
304
|
+
end.join
|
|
305
|
+
|
|
306
|
+
<<~HTML
|
|
307
|
+
<div class="newshound-section">
|
|
308
|
+
<div class="newshound-section-title">⚠️ Warnings (#{warnings.length})</div>
|
|
309
|
+
#{items}
|
|
310
|
+
</div>
|
|
311
|
+
HTML
|
|
312
|
+
end
|
|
313
|
+
|
|
283
314
|
def render_jobs(data)
|
|
284
315
|
stats = data[:queue_stats] || {}
|
|
285
316
|
|
data/lib/newshound/version.rb
CHANGED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Newshound
|
|
4
|
+
class WarningReporter
|
|
5
|
+
attr_reader :warning_source, :configuration, :time_range
|
|
6
|
+
|
|
7
|
+
def initialize(warning_source: nil, configuration: nil, time_range: 24.hours)
|
|
8
|
+
@configuration = configuration || Newshound.configuration
|
|
9
|
+
@warning_source = resolve_warning_source(warning_source)
|
|
10
|
+
@time_range = time_range
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def generate_report
|
|
14
|
+
return no_warnings_block if recent_warnings.empty?
|
|
15
|
+
|
|
16
|
+
[
|
|
17
|
+
{
|
|
18
|
+
type: "section",
|
|
19
|
+
text: {
|
|
20
|
+
type: "mrkdwn",
|
|
21
|
+
text: "*⚠️ Recent Warnings (Last 24 Hours)*"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
*format_warnings
|
|
25
|
+
]
|
|
26
|
+
end
|
|
27
|
+
alias_method :report, :generate_report
|
|
28
|
+
|
|
29
|
+
# Returns data formatted for the banner UI
|
|
30
|
+
def banner_data
|
|
31
|
+
return {warnings: []} unless warning_source
|
|
32
|
+
|
|
33
|
+
{
|
|
34
|
+
warnings: recent_warnings.map { |warning| warning_source.format_for_banner(warning) }
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def resolve_warning_source(source)
|
|
41
|
+
source ||= @configuration.warning_source
|
|
42
|
+
return nil if source.nil?
|
|
43
|
+
|
|
44
|
+
source.is_a?(Symbol) ? Warnings.source(source) : source
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def recent_warnings
|
|
48
|
+
return [] unless warning_source
|
|
49
|
+
|
|
50
|
+
@recent_warnings ||= warning_source.recent(
|
|
51
|
+
time_range: time_range,
|
|
52
|
+
limit: configuration.warning_limit
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def format_warnings
|
|
57
|
+
recent_warnings.map.with_index do |warning, index|
|
|
58
|
+
{
|
|
59
|
+
type: "section",
|
|
60
|
+
text: {
|
|
61
|
+
type: "mrkdwn",
|
|
62
|
+
text: warning_source.format_for_report(warning, index + 1)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def no_warnings_block
|
|
69
|
+
[
|
|
70
|
+
{
|
|
71
|
+
type: "section",
|
|
72
|
+
text: {
|
|
73
|
+
type: "mrkdwn",
|
|
74
|
+
text: "*✅ No Warnings in the Last 24 Hours*"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Newshound
|
|
4
|
+
module Warnings
|
|
5
|
+
# Base class for warning source adapters
|
|
6
|
+
# Each adapter is responsible for:
|
|
7
|
+
# 1. Fetching recent warnings from its specific data source
|
|
8
|
+
# 2. Formatting warning data for reports and banners
|
|
9
|
+
#
|
|
10
|
+
# Subclasses must implement:
|
|
11
|
+
# - #recent(time_range:, limit:) - Returns a collection of warning records
|
|
12
|
+
# - #format_for_report(warning, number) - Formats a single warning for Slack/report display
|
|
13
|
+
# - #format_for_banner(warning) - Formats a single warning for banner UI
|
|
14
|
+
class Base
|
|
15
|
+
# Fetches recent warnings from the data source
|
|
16
|
+
#
|
|
17
|
+
# @param time_range [ActiveSupport::Duration] Time duration to look back (e.g., 24.hours)
|
|
18
|
+
# @param limit [Integer] Maximum number of warnings to return
|
|
19
|
+
# @return [Array] Collection of warning records
|
|
20
|
+
def recent(time_range:, limit:)
|
|
21
|
+
raise NotImplementedError, "#{self.class} must implement #recent"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Formats a warning for report/Slack display
|
|
25
|
+
#
|
|
26
|
+
# @param warning [Object] Warning record from the data source
|
|
27
|
+
# @param number [Integer] Position number in the list
|
|
28
|
+
# @return [String] Formatted markdown text for display
|
|
29
|
+
def format_for_report(warning, number)
|
|
30
|
+
raise NotImplementedError, "#{self.class} must implement #format_for_report"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Formats a warning for banner UI display
|
|
34
|
+
#
|
|
35
|
+
# @param warning [Object] Warning record from the data source
|
|
36
|
+
# @return [Hash] Hash with keys: :title, :message, :location, :time
|
|
37
|
+
def format_for_banner(warning)
|
|
38
|
+
raise NotImplementedError, "#{self.class} must implement #format_for_banner"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Dir[File.join(__dir__, "warnings", "*.rb")].each { |file| require file }
|
|
4
|
+
|
|
5
|
+
module Newshound
|
|
6
|
+
module Warnings
|
|
7
|
+
# Registry for warning adapters
|
|
8
|
+
# Allows applications to register custom warning sources without
|
|
9
|
+
# needing to create classes within the Newshound namespace
|
|
10
|
+
@registry = {}
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
# Access the registry of registered adapters
|
|
14
|
+
# @return [Hash] The registry hash mapping names to adapter classes
|
|
15
|
+
attr_reader :registry
|
|
16
|
+
|
|
17
|
+
# Register a warning adapter class with a symbolic name
|
|
18
|
+
#
|
|
19
|
+
# @param name [Symbol, String] The name to register the adapter under
|
|
20
|
+
# @param adapter_class [Class] The adapter class (must inherit from Warnings::Base)
|
|
21
|
+
# @example
|
|
22
|
+
# Newshound::Warnings.register(:unprocessable_events, MyApp::UnprocessableEventsWarning)
|
|
23
|
+
def register(name, adapter_class)
|
|
24
|
+
@registry[name.to_sym] = adapter_class
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Get a warning source adapter instance
|
|
28
|
+
#
|
|
29
|
+
# @param source [Symbol, Object] Either a symbolic name to look up, or an adapter instance
|
|
30
|
+
# @return [Warnings::Base] An instance of the warning adapter
|
|
31
|
+
# @raise [RuntimeError] If the source symbol cannot be resolved
|
|
32
|
+
# @example
|
|
33
|
+
# Newshound::Warnings.source(:unprocessable_events)
|
|
34
|
+
# Newshound::Warnings.source(MyAdapter.new)
|
|
35
|
+
def source(source)
|
|
36
|
+
return source unless source.is_a?(Symbol)
|
|
37
|
+
|
|
38
|
+
# First check the registry
|
|
39
|
+
if @registry.key?(source)
|
|
40
|
+
return @registry[source].new
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Fall back to constant lookup (like Exceptions module)
|
|
44
|
+
constant = constants.find { |c| c.to_s.gsub(/(?<!^)([A-Z])/, "_\\1").downcase == source.to_s }
|
|
45
|
+
raise "Invalid warning source: #{source}" unless constant
|
|
46
|
+
|
|
47
|
+
const_get(constant).new
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Clear the registry (primarily for testing)
|
|
51
|
+
def clear_registry!
|
|
52
|
+
@registry = {}
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/newshound.rb
CHANGED
|
@@ -4,6 +4,8 @@ require_relative "newshound/version"
|
|
|
4
4
|
require_relative "newshound/configuration"
|
|
5
5
|
require_relative "newshound/exceptions"
|
|
6
6
|
require_relative "newshound/exception_reporter"
|
|
7
|
+
require_relative "newshound/warnings"
|
|
8
|
+
require_relative "newshound/warning_reporter"
|
|
7
9
|
require_relative "newshound/que_reporter"
|
|
8
10
|
require_relative "newshound/authorization"
|
|
9
11
|
require_relative "newshound/middleware/banner_injector"
|
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: 0.2.
|
|
4
|
+
version: 0.2.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Savannah Moore
|
|
@@ -52,6 +52,8 @@ files:
|
|
|
52
52
|
- README.md
|
|
53
53
|
- Rakefile
|
|
54
54
|
- TRANSPORT_USAGE.md
|
|
55
|
+
- checksums/newshound-0.2.3.gem.sha512
|
|
56
|
+
- checksums/newshound-0.2.5.gem.sha512
|
|
55
57
|
- lib/generators/newshound/install/install_generator.rb
|
|
56
58
|
- lib/generators/newshound/install/templates/newshound.rb
|
|
57
59
|
- lib/newshound.rb
|
|
@@ -66,6 +68,9 @@ files:
|
|
|
66
68
|
- lib/newshound/que_reporter.rb
|
|
67
69
|
- lib/newshound/railtie.rb
|
|
68
70
|
- lib/newshound/version.rb
|
|
71
|
+
- lib/newshound/warning_reporter.rb
|
|
72
|
+
- lib/newshound/warnings.rb
|
|
73
|
+
- lib/newshound/warnings/base.rb
|
|
69
74
|
- newshound.gemspec
|
|
70
75
|
homepage: https://github.com/SOFware/newshound
|
|
71
76
|
licenses:
|