newshound 0.2.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed2c9eae65ac8681fc1c72890cbd421ca9dd73130c37fa86c04643cb74c03856
4
- data.tar.gz: 7ccb875f08607e1478245384376a621243fdd44e40443fb191064210cff94ba6
3
+ metadata.gz: 3128baf64381d76f8ba53efd4887d333b875ba981d08f9ca6d6ebde9f5d23921
4
+ data.tar.gz: 35110f37798b54c435be13f5ebe0a1d1dfeb0e2629a676fa51c15b2ebafee9ef
5
5
  SHA512:
6
- metadata.gz: a78bd31f883c89116c83b7c77b680faba172b41e0bc3f375e4a81a075503d33860ebd4778e71f11d868a794fe04effd815e240ebd3d5c6135b0ed80599c87469
7
- data.tar.gz: 4985d3d128688284f4734391b12e7f6e34d1c69857d713d3995d2078cc4df210b64ca5f84d889841e52b1b3a9086bcf0f19e7d35bef646b60e08020379527ad6
6
+ metadata.gz: 9d99bc945f8fd1a1bb0550c94a215006833b962e815a4450fc84281e3ca6d0f37fa98fd6ecfedd5aee9e1f4dc4ad93da9e4cf30710906a0651d1e1959a554c47
7
+ data.tar.gz: b01b19aa141e0fe32db7dd188487fd6b8e7840b68194bf1b06b1f666c463240a353d3d8f0c1c408a2d60d0c58a13df517c83b78303d78ebd16caa504a0552180
@@ -24,6 +24,14 @@ module Newshound
24
24
  *format_exceptions
25
25
  ]
26
26
  end
27
+ alias_method :report, :generate_report
28
+
29
+ # Returns data formatted for the banner UI
30
+ def banner_data
31
+ {
32
+ exceptions: recent_exceptions.map { |exception| format_exception_for_banner(exception) }
33
+ }
34
+ end
27
35
 
28
36
  private
29
37
 
@@ -53,29 +61,100 @@ module Newshound
53
61
  end
54
62
 
55
63
  def format_exception_text(exception, number)
64
+ details = parse_exception_details(exception)
65
+
56
66
  <<~TEXT
57
- *#{number}. #{exception.title || exception.exception_class}*
67
+ *#{number}. #{exception_title(exception)}*
58
68
  • *Time:* #{exception.created_at.strftime('%I:%M %p')}
59
- • *Controller:* #{exception.controller_name}##{exception.action_name}
60
- • *Count:* #{exception_count(exception)}
61
- #{format_message(exception)}
69
+ #{format_controller(details)}
70
+ #{format_message(exception, details)}
62
71
  TEXT
63
72
  end
64
73
 
65
- def format_message(exception)
66
- return "" unless exception.message.present?
67
-
68
- message = exception.message.truncate(100)
74
+ def exception_title(exception)
75
+ if exception.respond_to?(:title) && exception.title.present?
76
+ exception.title
77
+ elsif exception.respond_to?(:exception_class) && exception.exception_class.present?
78
+ exception.exception_class
79
+ else
80
+ "Unknown Exception"
81
+ end
82
+ end
83
+
84
+ def parse_exception_details(exception)
85
+ return {} unless exception.respond_to?(:body) && exception.body.present?
86
+
87
+ JSON.parse(exception.body)
88
+ rescue JSON::ParserError
89
+ {}
90
+ end
91
+
92
+ def format_controller(details)
93
+ return String.new unless details["controller_name"] && details["action_name"]
94
+
95
+ "• *Controller:* #{details['controller_name']}##{details['action_name']}\n"
96
+ end
97
+
98
+ def format_message(exception, details = nil)
99
+ details ||= parse_exception_details(exception)
100
+
101
+ # Try to get message from different sources
102
+ message = if details["message"].present?
103
+ details["message"]
104
+ elsif exception.respond_to?(:message) && exception.message.present?
105
+ exception.message
106
+ end
107
+
108
+ return String.new unless message.present?
109
+
110
+ message = message.to_s.truncate(100)
69
111
  "• *Message:* `#{message}`"
70
112
  end
71
113
 
72
114
  def exception_count(exception)
73
115
  return 0 unless exception_source
74
116
 
75
- exception_source
76
- .where(exception_class: exception.exception_class)
77
- .where("created_at >= ?", time_range.ago)
78
- .count
117
+ # Use title for exception-track, exception_class for other systems
118
+ if exception.respond_to?(:title) && exception.title.present?
119
+ exception_source
120
+ .where(title: exception.title)
121
+ .where("created_at >= ?", time_range.ago)
122
+ .count
123
+ elsif exception.respond_to?(:exception_class)
124
+ exception_source
125
+ .where(exception_class: exception.exception_class)
126
+ .where("created_at >= ?", time_range.ago)
127
+ .count
128
+ else
129
+ 0
130
+ end
131
+ end
132
+
133
+ def format_exception_for_banner(exception)
134
+ details = parse_exception_details(exception)
135
+
136
+ # Extract message
137
+ message = if details["message"].present?
138
+ details["message"].to_s
139
+ elsif exception.respond_to?(:message) && exception.message.present?
140
+ exception.message.to_s
141
+ else
142
+ String.new
143
+ end
144
+
145
+ # Extract location
146
+ location = if details["controller_name"] && details["action_name"]
147
+ "#{details['controller_name']}##{details['action_name']}"
148
+ else
149
+ String.new
150
+ end
151
+
152
+ {
153
+ title: exception_title(exception),
154
+ message: message.truncate(100),
155
+ location: location,
156
+ time: exception.created_at.strftime('%I:%M %p')
157
+ }
79
158
  end
80
159
 
81
160
  def no_exceptions_block
@@ -48,7 +48,7 @@ module Newshound
48
48
  end
49
49
 
50
50
  def response_body(response)
51
- body = ""
51
+ body = String.new
52
52
  response.each { |part| body << part }
53
53
  body
54
54
  end
@@ -57,8 +57,8 @@ module Newshound
57
57
  exception_reporter = Newshound::ExceptionReporter.new
58
58
  que_reporter = Newshound::QueReporter.new
59
59
 
60
- exception_data = exception_reporter.report
61
- job_data = que_reporter.report
60
+ exception_data = exception_reporter.banner_data
61
+ job_data = que_reporter.banner_data
62
62
 
63
63
  # Generate HTML from template
64
64
  render_banner(exception_data, job_data)
@@ -275,7 +275,7 @@ module Newshound
275
275
  end
276
276
 
277
277
  def escape_html(text)
278
- return "" unless text
278
+ return String.new unless text.present?
279
279
  text.to_s
280
280
  .gsub('&', '&amp;')
281
281
  .gsub('<', '&lt;')
@@ -22,6 +22,21 @@ module Newshound
22
22
  queue_health_section
23
23
  ].compact
24
24
  end
25
+ alias_method :report, :generate_report
26
+
27
+ # Returns data formatted for the banner UI
28
+ def banner_data
29
+ stats = queue_statistics
30
+
31
+ {
32
+ queue_stats: {
33
+ ready_to_run: stats[:ready],
34
+ scheduled: stats[:scheduled],
35
+ failed: stats[:failed],
36
+ completed_today: stats[:finished_today]
37
+ }
38
+ }
39
+ end
25
40
 
26
41
  private
27
42
 
@@ -42,21 +57,33 @@ module Newshound
42
57
  def job_counts_by_type
43
58
  return {} unless job_source
44
59
 
45
- job_source
46
- .group(:job_class)
47
- .group(:error_count)
48
- .count
49
- .each_with_object({}) do |((job_class, error_count), count), hash|
50
- hash[job_class] ||= { success: 0, failed: 0, total: 0 }
51
-
52
- if error_count.zero?
53
- hash[job_class][:success] += count
54
- else
55
- hash[job_class][:failed] += count
56
- end
57
-
58
- hash[job_class][:total] += count
60
+ # Use raw SQL since Que::Job may not support ActiveRecord's .group method
61
+ results = ActiveRecord::Base.connection.execute(<<~SQL)
62
+ SELECT job_class, error_count, COUNT(*) as count
63
+ FROM que_jobs
64
+ WHERE finished_at IS NULL
65
+ GROUP BY job_class, error_count
66
+ ORDER BY job_class
67
+ SQL
68
+
69
+ results.each_with_object({}) do |row, hash|
70
+ job_class = row["job_class"]
71
+ error_count = row["error_count"].to_i
72
+ count = row["count"].to_i
73
+
74
+ hash[job_class] ||= {success: 0, failed: 0, total: 0}
75
+
76
+ if error_count.zero?
77
+ hash[job_class][:success] += count
78
+ else
79
+ hash[job_class][:failed] += count
59
80
  end
81
+
82
+ hash[job_class][:total] += count
83
+ end
84
+ rescue StandardError => e
85
+ logger.error "Failed to fetch job counts: #{e.message}"
86
+ {}
60
87
  end
61
88
 
62
89
  def format_job_counts(counts)
@@ -85,20 +112,27 @@ module Newshound
85
112
  def queue_statistics
86
113
  return default_stats unless job_source
87
114
 
88
- current_time = Time.now
89
- beginning_of_day = Date.today.to_time
115
+ conn = ActiveRecord::Base.connection
116
+ current_time = conn.quote(Time.now)
117
+ beginning_of_day = conn.quote(Date.today.to_time)
90
118
 
91
119
  {
92
- ready: job_source.where(finished_at: nil, expired_at: nil).where("run_at <= ?", current_time).count,
93
- scheduled: job_source.where(finished_at: nil, expired_at: nil).where("run_at > ?", current_time).count,
94
- failed: job_source.where.not(error_count: 0).where(finished_at: nil).count,
95
- finished_today: job_source.where("finished_at >= ?", beginning_of_day).count
120
+ ready: count_jobs("finished_at IS NULL AND expired_at IS NULL AND run_at <= #{current_time}"),
121
+ scheduled: count_jobs("finished_at IS NULL AND expired_at IS NULL AND run_at > #{current_time}"),
122
+ failed: count_jobs("error_count > 0 AND finished_at IS NULL"),
123
+ finished_today: count_jobs("finished_at >= #{beginning_of_day}")
96
124
  }
97
125
  rescue StandardError => e
98
126
  logger.error "Failed to fetch Que statistics: #{e.message}"
99
127
  default_stats
100
128
  end
101
129
 
130
+ def count_jobs(where_clause)
131
+ ActiveRecord::Base.connection.select_value(
132
+ "SELECT COUNT(*) FROM que_jobs WHERE #{where_clause}"
133
+ ).to_i
134
+ end
135
+
102
136
  def default_stats
103
137
  { ready: 0, scheduled: 0, failed: 0, finished_today: 0 }
104
138
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Newshound
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.1"
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: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - salbanez