newshound 0.1.1 → 0.2.1

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.
data/newshound.gemspec CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["salbanez"]
9
9
  spec.email = ["salbanez@example.com"]
10
10
 
11
- spec.summary = "Daily Slack reporter for Que jobs status and exception tracking"
12
- spec.description = "Newshound sniffs out exceptions and job statuses in your Rails app and reports them daily to Slack"
11
+ spec.summary = "Real-time web UI banner for monitoring Que jobs and exception tracking"
12
+ spec.description = "Newshound displays exceptions and job statuses in a collapsible banner for authorized users in your Rails app"
13
13
  spec.homepage = "https://github.com/salbanez/newshound"
14
14
  spec.license = "MIT"
15
15
  spec.required_ruby_version = ">= 2.7.0"
@@ -29,11 +29,6 @@ Gem::Specification.new do |spec|
29
29
 
30
30
  # Runtime dependencies
31
31
  spec.add_dependency "rails", ">= 6.0"
32
- spec.add_dependency "slack-ruby-client", "~> 2.0"
33
32
  spec.add_dependency "que", ">= 1.0"
34
- spec.add_dependency "que-scheduler", ">= 4.0"
35
33
  spec.add_dependency "exception-track", ">= 0.1"
36
-
37
- # Optional dependency for SNS transport
38
- spec.add_development_dependency "aws-sdk-sns", "~> 1.0"
39
34
  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: 0.1.1
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - salbanez
@@ -23,20 +23,6 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '6.0'
26
- - !ruby/object:Gem::Dependency
27
- name: slack-ruby-client
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - "~>"
31
- - !ruby/object:Gem::Version
32
- version: '2.0'
33
- type: :runtime
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '2.0'
40
26
  - !ruby/object:Gem::Dependency
41
27
  name: que
42
28
  requirement: !ruby/object:Gem::Requirement
@@ -51,20 +37,6 @@ dependencies:
51
37
  - - ">="
52
38
  - !ruby/object:Gem::Version
53
39
  version: '1.0'
54
- - !ruby/object:Gem::Dependency
55
- name: que-scheduler
56
- requirement: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - ">="
59
- - !ruby/object:Gem::Version
60
- version: '4.0'
61
- type: :runtime
62
- prerelease: false
63
- version_requirements: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - ">="
66
- - !ruby/object:Gem::Version
67
- version: '4.0'
68
40
  - !ruby/object:Gem::Dependency
69
41
  name: exception-track
70
42
  requirement: !ruby/object:Gem::Requirement
@@ -79,22 +51,8 @@ dependencies:
79
51
  - - ">="
80
52
  - !ruby/object:Gem::Version
81
53
  version: '0.1'
82
- - !ruby/object:Gem::Dependency
83
- name: aws-sdk-sns
84
- requirement: !ruby/object:Gem::Requirement
85
- requirements:
86
- - - "~>"
87
- - !ruby/object:Gem::Version
88
- version: '1.0'
89
- type: :development
90
- prerelease: false
91
- version_requirements: !ruby/object:Gem::Requirement
92
- requirements:
93
- - - "~>"
94
- - !ruby/object:Gem::Version
95
- version: '1.0'
96
- description: Newshound sniffs out exceptions and job statuses in your Rails app and
97
- reports them daily to Slack
54
+ description: Newshound displays exceptions and job statuses in a collapsible banner
55
+ for authorized users in your Rails app
98
56
  email:
99
57
  - salbanez@example.com
100
58
  executables: []
@@ -110,16 +68,12 @@ files:
110
68
  - lib/generators/newshound/install/install_generator.rb
111
69
  - lib/generators/newshound/install/templates/newshound.rb
112
70
  - lib/newshound.rb
71
+ - lib/newshound/authorization.rb
113
72
  - lib/newshound/configuration.rb
114
- - lib/newshound/daily_report_job.rb
115
73
  - lib/newshound/exception_reporter.rb
74
+ - lib/newshound/middleware/banner_injector.rb
116
75
  - lib/newshound/que_reporter.rb
117
76
  - lib/newshound/railtie.rb
118
- - lib/newshound/scheduler.rb
119
- - lib/newshound/slack_notifier.rb
120
- - lib/newshound/transport/base.rb
121
- - lib/newshound/transport/slack.rb
122
- - lib/newshound/transport/sns.rb
123
77
  - lib/newshound/version.rb
124
78
  - newshound.gemspec
125
79
  homepage: https://github.com/salbanez/newshound
@@ -145,5 +99,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
99
  requirements: []
146
100
  rubygems_version: 3.6.9
147
101
  specification_version: 4
148
- summary: Daily Slack reporter for Que jobs status and exception tracking
102
+ summary: Real-time web UI banner for monitoring Que jobs and exception tracking
149
103
  test_files: []
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Newshound
4
- if defined?(::Que::Job)
5
- class DailyReportJob < ::Que::Job
6
- def run
7
- return unless Newshound.configuration.valid?
8
-
9
- Newshound.report!
10
-
11
- destroy
12
- rescue StandardError => e
13
- Rails.logger.error "Newshound::DailyReportJob failed: #{e.message}"
14
- Rails.logger.error e.backtrace.join("\n")
15
-
16
- # Re-raise to let Que handle retry logic
17
- raise
18
- end
19
- end
20
- else
21
- class DailyReportJob
22
- def self.enqueue(*args)
23
- Rails.logger.warn "Que is not available. DailyReportJob cannot be enqueued."
24
- end
25
-
26
- def run
27
- Rails.logger.warn "Que is not available. DailyReportJob cannot be run."
28
- end
29
- end
30
- end
31
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Newshound
4
- class Scheduler
5
- def self.schedule_daily_report
6
- return unless defined?(::Que::Scheduler)
7
-
8
- config = Newshound.configuration
9
- return unless config.valid?
10
-
11
- # Note: Que-scheduler uses a YAML config file (config/que_schedule.yml)
12
- # This method returns the configuration that should be added to that file
13
- # or can be used to manually schedule the job
14
- schedule_config = {
15
- "newshound_daily_report" => {
16
- "class" => "Newshound::DailyReportJob",
17
- "cron" => build_cron_expression(config.report_time),
18
- "queue" => "default",
19
- "args" => []
20
- }
21
- }
22
-
23
- # Log the configuration for visibility
24
- if defined?(Rails) && Rails.logger
25
- Rails.logger.info "Newshound daily report scheduled for #{config.report_time} (cron: #{schedule_config['newshound_daily_report']['cron']})"
26
- end
27
-
28
- schedule_config
29
- end
30
-
31
- def self.build_cron_expression(time_string)
32
- # Convert "09:00" format to cron expression
33
- # Format: minute hour * * *
34
- hour, minute = time_string.split(":").map(&:to_i)
35
- "#{minute} #{hour} * * *"
36
- end
37
-
38
- def self.run_now!
39
- Newshound::DailyReportJob.enqueue
40
- end
41
- end
42
- end
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "slack-ruby-client"
4
-
5
- module Newshound
6
- class SlackNotifier
7
- attr_reader :configuration, :logger, :transport
8
-
9
- def initialize(configuration: nil, logger: nil, transport: nil)
10
- @configuration = configuration || Newshound.configuration
11
- @logger = logger || (defined?(Rails) ? Rails.logger : Logger.new(STDOUT))
12
- @transport = transport || build_transport
13
- end
14
-
15
- def post(message)
16
- return unless configuration.valid?
17
-
18
- transport.deliver(message)
19
- rescue StandardError => e
20
- logger.error "Newshound: Failed to send notification: #{e.message}"
21
- end
22
-
23
- private
24
-
25
- def build_transport
26
- case configuration.transport_adapter
27
- when :sns, "sns"
28
- require_relative "transport/sns"
29
- Transport::Sns.new(configuration: configuration, logger: logger)
30
- when :slack, "slack", nil
31
- require_relative "transport/slack"
32
- Transport::Slack.new(configuration: configuration, logger: logger)
33
- else
34
- if configuration.transport_adapter.is_a?(Class)
35
- configuration.transport_adapter.new(configuration: configuration, logger: logger)
36
- elsif configuration.transport_adapter.respond_to?(:new)
37
- configuration.transport_adapter.new(configuration: configuration, logger: logger)
38
- else
39
- raise ArgumentError, "Invalid transport adapter: #{configuration.transport_adapter}"
40
- end
41
- end
42
- end
43
- end
44
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Newshound
4
- module Transport
5
- class Base
6
- attr_reader :configuration, :logger
7
-
8
- def initialize(configuration: nil, logger: nil)
9
- @configuration = configuration || Newshound.configuration
10
- @logger = logger || default_logger
11
- end
12
-
13
- def deliver(message)
14
- raise NotImplementedError, "Subclasses must implement the #deliver method"
15
- end
16
-
17
- protected
18
-
19
- def default_logger
20
- if defined?(Rails)
21
- Rails.logger
22
- else
23
- Logger.new(STDOUT)
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "slack-ruby-client"
4
- require_relative "base"
5
-
6
- module Newshound
7
- module Transport
8
- class Slack < Base
9
- attr_reader :webhook_client, :web_api_client
10
-
11
- def initialize(configuration: nil, logger: nil, webhook_client: nil, web_api_client: nil)
12
- super(configuration: configuration, logger: logger)
13
- @webhook_client = webhook_client
14
- @web_api_client = web_api_client
15
- configure_slack_client
16
- end
17
-
18
- def deliver(message)
19
- return unless configuration.valid?
20
-
21
- if webhook_configured?
22
- deliver_via_webhook(message)
23
- elsif web_api_configured?
24
- deliver_via_web_api(message)
25
- else
26
- logger.error "Newshound: No valid Slack configuration found"
27
- false
28
- end
29
- rescue StandardError => e
30
- logger.error "Newshound: Failed to send Slack notification: #{e.message}"
31
- false
32
- end
33
-
34
- private
35
-
36
- def configure_slack_client
37
- ::Slack.configure do |config|
38
- config.token = ENV["SLACK_API_TOKEN"] if ENV["SLACK_API_TOKEN"]
39
- end
40
- end
41
-
42
- def webhook_configured?
43
- !configuration.slack_webhook_url.nil? && !configuration.slack_webhook_url.empty?
44
- end
45
-
46
- def web_api_configured?
47
- !ENV["SLACK_API_TOKEN"].nil? && !ENV["SLACK_API_TOKEN"].empty?
48
- end
49
-
50
- def deliver_via_webhook(message)
51
- client = webhook_client || ::Slack::Incoming::Webhook.new(configuration.slack_webhook_url)
52
- client.post(message)
53
- true
54
- end
55
-
56
- def deliver_via_web_api(message)
57
- client = web_api_client || ::Slack::Web::Client.new
58
- client.chat_postMessage(
59
- channel: configuration.slack_channel,
60
- blocks: message[:blocks],
61
- text: "Daily Newshound Report"
62
- )
63
- true
64
- end
65
- end
66
- end
67
- end
@@ -1,115 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "base"
4
-
5
- module Newshound
6
- module Transport
7
- class Sns < Base
8
- attr_reader :sns_client
9
-
10
- def initialize(configuration: nil, logger: nil, sns_client: nil)
11
- super(configuration: configuration, logger: logger)
12
- @sns_client = sns_client || build_sns_client
13
- end
14
-
15
- def deliver(message)
16
- return false unless valid_sns_configuration?
17
-
18
- formatted_message = format_message(message)
19
-
20
- response = sns_client.publish(
21
- topic_arn: configuration.sns_topic_arn,
22
- message: formatted_message,
23
- subject: extract_subject(message)
24
- )
25
-
26
- logger.info "Newshound: Message sent to SNS, MessageId: #{response.message_id}"
27
- true
28
- rescue StandardError => e
29
- logger.error "Newshound: Failed to send SNS notification: #{e.message}"
30
- false
31
- end
32
-
33
- private
34
-
35
- def build_sns_client
36
- require "aws-sdk-sns"
37
-
38
- options = {
39
- region: configuration.aws_region || ENV["AWS_REGION"] || "us-east-1"
40
- }
41
-
42
- if configuration.aws_access_key_id && configuration.aws_secret_access_key
43
- options[:credentials] = Aws::Credentials.new(
44
- configuration.aws_access_key_id,
45
- configuration.aws_secret_access_key
46
- )
47
- end
48
-
49
- Aws::SNS::Client.new(options)
50
- end
51
-
52
- def valid_sns_configuration?
53
- if configuration.sns_topic_arn.nil? || configuration.sns_topic_arn.empty?
54
- logger.error "Newshound: SNS topic ARN not configured"
55
- false
56
- else
57
- true
58
- end
59
- end
60
-
61
- def format_message(message)
62
- case message
63
- when Hash
64
- if message[:blocks]
65
- format_slack_blocks_for_sns(message[:blocks])
66
- else
67
- JSON.pretty_generate(message)
68
- end
69
- when String
70
- message
71
- else
72
- message.to_s
73
- end
74
- end
75
-
76
- def format_slack_blocks_for_sns(blocks)
77
- lines = []
78
-
79
- blocks.each do |block|
80
- case block[:type]
81
- when "section"
82
- if block[:text]
83
- lines << format_text_element(block[:text])
84
- end
85
- when "header"
86
- if block[:text]
87
- lines << "=== #{format_text_element(block[:text])} ==="
88
- end
89
- when "divider"
90
- lines << "---"
91
- end
92
- end
93
-
94
- lines.join("\n\n")
95
- end
96
-
97
- def format_text_element(text_element)
98
- return "" unless text_element
99
-
100
- text = text_element[:text] || ""
101
- text.gsub(/:([a-z_]+):/, '')
102
- .gsub(/\*(.+?)\*/, '\1')
103
- .gsub(/_(.+?)_/, '\1')
104
- end
105
-
106
- def extract_subject(message)
107
- if message.is_a?(Hash)
108
- message[:subject] || "Newshound Notification"
109
- else
110
- "Newshound Notification"
111
- end
112
- end
113
- end
114
- end
115
- end