rails_error_dashboard 0.12.0 → 0.12.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: 2c23f8c75c71f71913d2f5964f25a38642373f2f720e8996377a5877e54a1e6a
4
- data.tar.gz: 95e1db7a2687ed2900e3a02a2f33d1a1c21fc70c4597f9d9bf5d5855356d2f98
3
+ metadata.gz: d504b1e375f3af3cb25e5fd636730f1f6e71cc74f98f9c2608bbd7ac042e1575
4
+ data.tar.gz: e6e8e5a078803978e9a93d5f0b2c71e6ec3a1b2ae7ee9c97fb4693a8f339ea98
5
5
  SHA512:
6
- metadata.gz: acb081a03269304c9c70b63ee996bd8d446a525bd506f401851d8249ad9bffe2ead0bf63798be64c231604176e03c614c761e4f551b9827802b7881b99f3b1b0
7
- data.tar.gz: d28393ca63c1b9639daeb2409cdc3f20074f905e1cbc9e3a89ae7b881dbe2666bfbaf53052d1172803de0ca46d58ff7ae8b4bd6f4d9b24be3ffa61ae1283a20e
6
+ metadata.gz: 3f64b74befa82818242fe0b38e1c399b2a683e0f464b6533fe62c9eac4de7503d9c18db124b05ddadedbfc3347c1edaddd49edfd845ff2a9eff8f5e2ddc68b45
7
+ data.tar.gz: 42e42075a4b4375fb71e8efe6eb6d85585a6c176c160ede254d1e4a937d8501fc102f40cacf74c9737745a6ca1700c6568d8986d7c78ed327a31008f0ce6522e
@@ -295,6 +295,12 @@
295
295
  <div class="card">
296
296
  <div class="card-header">
297
297
  <h5 class="mb-0"><i class="bi bi-people"></i> <%= red_t("red.analytics.overview.top_users.title") %></h5>
298
+ <%# Storm protection sheds per-event rows, and this table is built
299
+ from them, so the counts are a floor when that happened. The
300
+ Overview labels the same condition with the same key. %>
301
+ <% if @error_stats[:affected_users_incomplete] %>
302
+ <small style="display: block; color: var(--text-tertiary);"><%= red_t("red.errors.overview_page.affected_users_incomplete") %></small>
303
+ <% end %>
298
304
  </div>
299
305
  <div class="card-body p-0">
300
306
  <%= render partial: 'user_errors_table',
@@ -64,29 +64,45 @@ module RailsErrorDashboard
64
64
  base_scope.where("occurred_at >= ?", @start_date)
65
65
  end
66
66
 
67
+ # Two counting units, kept distinct on purpose.
68
+ #
69
+ # An ErrorLog row is a GROUP; its occurrence_count says how many times
70
+ # that error actually happened. Volume figures are EVENTS (the sum), so
71
+ # this page agrees with the Overview, which counts the same way. Resolved
72
+ # and unresolved stay GROUP counts -- a group is the thing that gets
73
+ # resolved, and an event cannot be.
67
74
  def error_statistics
68
75
  {
69
- total: base_query.count,
76
+ total: event_count,
77
+ total_groups: base_query.count,
70
78
  unresolved: base_query.unresolved.count,
71
- by_type: base_query.group(:error_type).count.sort_by { |_, count| -count }.to_h,
72
- by_day: base_query.group("DATE(occurred_at)").count
79
+ resolved: base_query.resolved.count,
80
+ by_type: base_query.group(:error_type).sum(:occurrence_count).sort_by { |_, count| -count }.to_h,
81
+ by_day: base_query.group("DATE(occurred_at)").sum(:occurrence_count),
82
+ affected_users_incomplete: affected_users_incomplete?
73
83
  }
74
84
  end
75
85
 
86
+ # Total EVENTS in the window. dashboard_stats.rb and
87
+ # platform_comparison.rb:165 count the same way.
88
+ def event_count
89
+ base_query.sum(:occurrence_count)
90
+ end
91
+
76
92
  def errors_over_time
77
- base_query.group_by_day(:occurred_at).count
93
+ base_query.group_by_day(:occurred_at).sum(:occurrence_count)
78
94
  end
79
95
 
80
96
  def errors_by_type
81
97
  base_query.group(:error_type)
82
- .count
98
+ .sum(:occurrence_count)
83
99
  .sort_by { |_, count| -count }
84
100
  .first(10)
85
101
  .to_h
86
102
  end
87
103
 
88
104
  def errors_by_platform
89
- base_query.group(:platform).count
105
+ base_query.group(:platform).sum(:occurrence_count)
90
106
  end
91
107
 
92
108
  # NULL (captured before the column existed) is reported under :unknown
@@ -94,25 +110,81 @@ module RailsErrorDashboard
94
110
  def errors_by_environment
95
111
  return {} unless ErrorLog.column_names.include?("environment")
96
112
 
97
- base_query.group(:environment).count.transform_keys { |env| env.nil? ? :unknown : env }
113
+ base_query.group(:environment).sum(:occurrence_count).transform_keys { |env| env.nil? ? :unknown : env }
98
114
  end
99
115
 
100
116
  def errors_by_hour
101
117
  # group_by_hour_of_day buckets into 0..23 to show diurnal patterns
102
118
  # (when in the day errors peak). The chart title says "Errors by Hour
103
119
  # of Day" — group_by_hour produced a chronological time series instead.
104
- base_query.group_by_hour_of_day(:occurred_at).count
120
+ base_query.group_by_hour_of_day(:occurred_at).sum(:occurrence_count)
105
121
  end
106
122
 
123
+ # Events per user, counted from OCCURRENCE rows.
124
+ #
125
+ # The group's user_id is overwritten by each new occurrence, so grouping
126
+ # ErrorLog by it attributed a whole group to whoever happened to hit it
127
+ # last -- at most one user per group, and their "count" was a number of
128
+ # groups. Occurrence rows carry the user of each individual event.
129
+ #
130
+ # Storm count-only events create no occurrence row, so during a storm
131
+ # this is a floor; affected_users_incomplete? says when.
107
132
  def top_affected_users
108
133
  user_model = RailsErrorDashboard.configuration.user_model
109
134
 
110
- base_query.where.not(user_id: nil)
111
- .group(:user_id)
112
- .count
113
- .sort_by { |_, count| -count }
114
- .first(10)
115
- .map { |user_id, count| { user_id: user_id, email: find_user_email(user_id, user_model), count: count } }
135
+ counts = user_event_counts
136
+ counts.sort_by { |_, count| -count }
137
+ .first(10)
138
+ .map { |user_id, count| { user_id: user_id, email: find_user_email(user_id, user_model), count: count } }
139
+ end
140
+
141
+ def user_event_counts
142
+ return group_user_counts unless occurrences_available?
143
+
144
+ occurrences = ErrorOccurrence.table_name
145
+ counts = occurrence_scope.where("#{occurrences}.occurred_at >= ?", @start_date)
146
+ .where.not(occurrences => { user_id: nil })
147
+ .group("#{occurrences}.user_id")
148
+ .count
149
+
150
+ # A user whose events predate occurrence tracking -- or whose events
151
+ # were shed by storm protection, which writes no occurrence row -- still
152
+ # belongs in the table. Fall back to the group's own user for those,
153
+ # taking whichever count is larger. UserImpactSummary merges the same way.
154
+ group_user_counts.merge(counts) { |_user, group_count, occurrence_count| [ group_count, occurrence_count ].max }
155
+ rescue StandardError
156
+ group_user_counts
157
+ end
158
+
159
+ def group_user_counts
160
+ base_query.where.not(user_id: nil).group(:user_id).sum(:occurrence_count)
161
+ end
162
+
163
+ # Occurrence rows joined back to error_logs, so the application filter
164
+ # (which the occurrence table has no column for) still applies.
165
+ def occurrence_scope
166
+ scope = ErrorOccurrence.joins(:error_log)
167
+ scope = scope.where(ErrorLog.table_name => { application_id: @application_id }) if @application_id.present?
168
+ scope
169
+ end
170
+
171
+ def occurrences_available?
172
+ defined?(ErrorOccurrence) && ErrorOccurrence.table_exists?
173
+ rescue StandardError
174
+ false
175
+ end
176
+
177
+ # True when the window holds more events than recorded occurrences --
178
+ # storm shedding dropped per-event rows, so any occurrence-derived
179
+ # figure (the affected-user table) is a floor, not a total.
180
+ def affected_users_incomplete?
181
+ return false unless occurrences_available?
182
+
183
+ occurrences = ErrorOccurrence.table_name
184
+ recorded = occurrence_scope.where("#{occurrences}.occurred_at >= ?", @start_date).count
185
+ recorded < event_count
186
+ rescue StandardError
187
+ false
116
188
  end
117
189
 
118
190
  def find_user_email(user_id, user_model)
@@ -122,23 +194,31 @@ module RailsErrorDashboard
122
194
  "User ##{user_id}"
123
195
  end
124
196
 
197
+ # Resolution rate is GROUPS over GROUPS.
198
+ #
199
+ # Volume is now measured in events, but resolving is something that
200
+ # happens to a group, so dividing resolved groups by total events would
201
+ # collapse the rate toward zero the moment any error recurred. The
202
+ # Overview computes this the same way (resolved / resolved + unresolved),
203
+ # so both pages report one rate.
204
+ #
205
+ # Same scoped relation on both sides: an unscoped numerator counted every
206
+ # application's resolved errors against one application's total, and the
207
+ # "rate" went past 100%.
125
208
  def resolution_rate
126
- total = error_statistics[:total]
127
- return 0 if total.zero?
128
-
129
- # Same scoped relation as the denominator: an unscoped numerator
130
- # counted every application's resolved errors against one
131
- # application's total, and the "rate" went past 100%.
132
209
  resolved_count = base_query.resolved.count
133
- ((resolved_count.to_f / total) * 100).round(1)
210
+ total_groups = resolved_count + base_query.unresolved.count
211
+ return 0 if total_groups.zero?
212
+
213
+ ((resolved_count.to_f / total_groups) * 100).round(1)
134
214
  end
135
215
 
136
216
  def mobile_errors_count
137
- base_query.where(platform: [ "iOS", "Android" ]).count
217
+ base_query.where(platform: [ "iOS", "Android" ]).sum(:occurrence_count)
138
218
  end
139
219
 
140
220
  def api_errors_count
141
- base_query.where("platform IS NULL OR platform = ?", "API").count
221
+ base_query.where("platform IS NULL OR platform = ?", "API").sum(:occurrence_count)
142
222
  end
143
223
 
144
224
  # Pattern insights for top error types
@@ -1,3 +1,3 @@
1
1
  module RailsErrorDashboard
2
- VERSION = "0.12.0"
2
+ VERSION = "0.12.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_error_dashboard
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.12.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anjan Jagirdar
@@ -585,7 +585,7 @@ metadata:
585
585
  funding_uri: https://github.com/sponsors/AnjanJ
586
586
  post_install_message: |
587
587
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
588
- RED (Rails Error Dashboard) v0.12.0
588
+ RED (Rails Error Dashboard) v0.12.1
589
589
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
590
590
 
591
591
  First install: