rails_error_dashboard 0.8.3 → 0.9.0
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/README.md +35 -0
- data/app/controllers/rails_error_dashboard/application_controller.rb +156 -0
- data/app/controllers/rails_error_dashboard/errors_controller.rb +95 -73
- data/app/controllers/rails_error_dashboard/locales_controller.rb +87 -0
- data/app/helpers/rails_error_dashboard/application_helper.rb +20 -14
- data/app/helpers/rails_error_dashboard/backtrace_helper.rb +15 -6
- data/app/helpers/rails_error_dashboard/i18n_helper.rb +154 -0
- data/app/helpers/rails_error_dashboard/mailer_i18n_helper.rb +69 -0
- data/app/helpers/rails_error_dashboard/overview_helper.rb +18 -6
- data/app/helpers/rails_error_dashboard/user_agent_helper.rb +25 -11
- data/app/jobs/rails_error_dashboard/application_job.rb +5 -0
- data/app/jobs/rails_error_dashboard/baseline_alert_job.rb +18 -16
- data/app/jobs/rails_error_dashboard/concerns/localized_job.rb +83 -0
- data/app/jobs/rails_error_dashboard/discord_error_notification_job.rb +4 -2
- data/app/jobs/rails_error_dashboard/email_error_notification_job.rb +4 -2
- data/app/jobs/rails_error_dashboard/pagerduty_error_notification_job.rb +6 -2
- data/app/jobs/rails_error_dashboard/scheduled_digest_job.rb +8 -3
- data/app/jobs/rails_error_dashboard/slack_error_notification_job.rb +6 -4
- data/app/jobs/rails_error_dashboard/storm_notification_job.rb +26 -8
- data/app/jobs/rails_error_dashboard/webhook_error_notification_job.rb +4 -2
- data/app/mailers/rails_error_dashboard/application_mailer.rb +14 -0
- data/app/mailers/rails_error_dashboard/digest_mailer.rb +18 -2
- data/app/mailers/rails_error_dashboard/error_notification_mailer.rb +19 -2
- data/app/views/layouts/rails_error_dashboard.html.erb +255 -92
- data/app/views/rails_error_dashboard/digest_mailer/digest_summary.html.erb +23 -22
- data/app/views/rails_error_dashboard/digest_mailer/digest_summary.text.erb +30 -19
- data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.html.erb +18 -16
- data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.text.erb +21 -14
- data/app/views/rails_error_dashboard/errors/_ai_help_panel.html.erb +11 -7
- data/app/views/rails_error_dashboard/errors/_breadcrumbs_group.html.erb +45 -38
- data/app/views/rails_error_dashboard/errors/_co_occurring_errors.html.erb +17 -14
- data/app/views/rails_error_dashboard/errors/_discussion.html.erb +27 -9
- data/app/views/rails_error_dashboard/errors/_error_cascades.html.erb +25 -21
- data/app/views/rails_error_dashboard/errors/_error_info.html.erb +25 -22
- data/app/views/rails_error_dashboard/errors/_error_row.html.erb +24 -12
- data/app/views/rails_error_dashboard/errors/_instance_variables.html.erb +12 -11
- data/app/views/rails_error_dashboard/errors/_issue_section.html.erb +19 -13
- data/app/views/rails_error_dashboard/errors/_llm_summary.html.erb +14 -14
- data/app/views/rails_error_dashboard/errors/_local_variables.html.erb +8 -8
- data/app/views/rails_error_dashboard/errors/_modals.html.erb +60 -52
- data/app/views/rails_error_dashboard/errors/_pattern_insights.html.erb +59 -49
- data/app/views/rails_error_dashboard/errors/_request_context.html.erb +23 -20
- data/app/views/rails_error_dashboard/errors/_show_scripts.html.erb +14 -14
- data/app/views/rails_error_dashboard/errors/_sidebar_metadata.html.erb +118 -93
- data/app/views/rails_error_dashboard/errors/_similar_errors.html.erb +18 -12
- data/app/views/rails_error_dashboard/errors/_source_code.html.erb +20 -5
- data/app/views/rails_error_dashboard/errors/_stats.html.erb +5 -5
- data/app/views/rails_error_dashboard/errors/_timeline.html.erb +7 -7
- data/app/views/rails_error_dashboard/errors/_user_errors_table.html.erb +15 -12
- data/app/views/rails_error_dashboard/errors/actioncable_health_summary.html.erb +25 -25
- data/app/views/rails_error_dashboard/errors/activestorage_health_summary.html.erb +28 -28
- data/app/views/rails_error_dashboard/errors/analytics.html.erb +82 -82
- data/app/views/rails_error_dashboard/errors/cache_health_summary.html.erb +27 -27
- data/app/views/rails_error_dashboard/errors/correlation.html.erb +61 -57
- data/app/views/rails_error_dashboard/errors/database_health_summary.html.erb +64 -65
- data/app/views/rails_error_dashboard/errors/deprecations.html.erb +23 -23
- data/app/views/rails_error_dashboard/errors/diagnostic_dumps.html.erb +48 -36
- data/app/views/rails_error_dashboard/errors/index.html.erb +121 -70
- data/app/views/rails_error_dashboard/errors/job_health_summary.html.erb +30 -30
- data/app/views/rails_error_dashboard/errors/llm_health_summary.html.erb +38 -39
- data/app/views/rails_error_dashboard/errors/n_plus_one_summary.html.erb +28 -25
- data/app/views/rails_error_dashboard/errors/overview.html.erb +45 -39
- data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +45 -44
- data/app/views/rails_error_dashboard/errors/rack_attack_summary.html.erb +39 -28
- data/app/views/rails_error_dashboard/errors/releases.html.erb +46 -47
- data/app/views/rails_error_dashboard/errors/settings/_value_badge.html.erb +48 -44
- data/app/views/rails_error_dashboard/errors/settings.html.erb +122 -112
- data/app/views/rails_error_dashboard/errors/show.html.erb +44 -37
- data/app/views/rails_error_dashboard/errors/storms.html.erb +30 -24
- data/app/views/rails_error_dashboard/errors/swallowed_exceptions.html.erb +26 -26
- data/app/views/rails_error_dashboard/errors/user_impact.html.erb +31 -30
- data/config/locales/de.yml +2007 -0
- data/config/locales/en.yml +2719 -0
- data/config/locales/es.yml +2079 -0
- data/config/locales/fr.yml +2082 -0
- data/config/locales/it.yml +2060 -0
- data/config/locales/ja.yml +1802 -0
- data/config/locales/pl.yml +2108 -0
- data/config/locales/pt-BR.yml +2064 -0
- data/config/locales/ru.yml +2109 -0
- data/config/locales/uk.yml +2106 -0
- data/config/locales/zh-CN.yml +1791 -0
- data/config/routes.rb +5 -0
- data/db/migrate/20260325000001_fix_swallowed_exceptions_index_for_mysql.rb +9 -3
- data/lib/rails_error_dashboard/commands/batch_delete_errors.rb +3 -1
- data/lib/rails_error_dashboard/commands/batch_mute_errors.rb +4 -2
- data/lib/rails_error_dashboard/commands/batch_resolve_errors.rb +4 -2
- data/lib/rails_error_dashboard/commands/batch_unmute_errors.rb +4 -2
- data/lib/rails_error_dashboard/commands/create_issue.rb +5 -3
- data/lib/rails_error_dashboard/commands/link_existing_issue.rb +4 -2
- data/lib/rails_error_dashboard/commands/log_error.rb +1 -1
- data/lib/rails_error_dashboard/configuration.rb +23 -0
- data/lib/rails_error_dashboard/current.rb +56 -0
- data/lib/rails_error_dashboard/i18n_store.rb +293 -0
- data/lib/rails_error_dashboard/private_backend.rb +213 -0
- data/lib/rails_error_dashboard/services/baseline_alert_payload_builder.rb +89 -22
- data/lib/rails_error_dashboard/services/digest_builder.rb +21 -7
- data/lib/rails_error_dashboard/services/discord_payload_builder.rb +27 -15
- data/lib/rails_error_dashboard/services/error_notification_dispatcher.rb +10 -5
- data/lib/rails_error_dashboard/services/localized_time_formatter.rb +117 -0
- data/lib/rails_error_dashboard/services/notification_helpers.rb +87 -4
- data/lib/rails_error_dashboard/services/pagerduty_payload_builder.rb +22 -5
- data/lib/rails_error_dashboard/services/slack_payload_builder.rb +48 -28
- data/lib/rails_error_dashboard/services/storm_protection/gate.rb +2 -1
- data/lib/rails_error_dashboard/services/webhook_payload_builder.rb +15 -1
- data/lib/rails_error_dashboard/translation.rb +39 -0
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/rails_error_dashboard.rb +4 -0
- data/lib/tasks/error_dashboard.rake +5 -1
- metadata +24 -4
|
@@ -5,32 +5,32 @@
|
|
|
5
5
|
<%= form_with url: resolve_error_path(error), method: :post do |f| %>
|
|
6
6
|
<div class="modal-header">
|
|
7
7
|
<h5 class="modal-title" id="resolveModalLabel">
|
|
8
|
-
<i class="bi bi-check-circle"></i>
|
|
8
|
+
<i class="bi bi-check-circle"></i> <%= red_t("red.errors.modals.resolve.title") %>
|
|
9
9
|
</h5>
|
|
10
|
-
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="
|
|
10
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="<%= red_t("red.common.close") %>"></button>
|
|
11
11
|
</div>
|
|
12
12
|
<div class="modal-body">
|
|
13
13
|
<div class="mb-3">
|
|
14
|
-
<label for="resolved_by_name" class="form-label"
|
|
15
|
-
<%= text_field_tag :resolved_by_name, nil, class: "form-control", placeholder: "
|
|
16
|
-
<small class="text-muted"
|
|
14
|
+
<label for="resolved_by_name" class="form-label"><%= red_t("red.errors.modals.resolve.name_label") %> <span class="text-danger">*</span></label>
|
|
15
|
+
<%= text_field_tag :resolved_by_name, nil, class: "form-control", placeholder: red_t("red.errors.modals.name_placeholder"), required: true %>
|
|
16
|
+
<small class="text-muted"><%= red_t("red.errors.modals.resolve.name_hint") %></small>
|
|
17
17
|
</div>
|
|
18
18
|
|
|
19
19
|
<div class="mb-3">
|
|
20
|
-
<label for="resolution_reference" class="form-label"
|
|
21
|
-
<%= text_field_tag :resolution_reference, nil, class: "form-control", placeholder: "
|
|
22
|
-
<small class="text-muted"
|
|
20
|
+
<label for="resolution_reference" class="form-label"><%= red_t("red.errors.modals.resolve.reference_label") %></label>
|
|
21
|
+
<%= text_field_tag :resolution_reference, nil, class: "form-control", placeholder: red_t("red.errors.modals.resolve.reference_placeholder") %>
|
|
22
|
+
<small class="text-muted"><%= red_t("red.errors.modals.resolve.reference_hint") %></small>
|
|
23
23
|
</div>
|
|
24
24
|
|
|
25
25
|
<div class="mb-3">
|
|
26
|
-
<label for="resolution_comment" class="form-label"
|
|
27
|
-
<%= text_area_tag :resolution_comment, nil, class: "form-control", rows: 4, placeholder: "
|
|
28
|
-
<small class="text-muted"
|
|
26
|
+
<label for="resolution_comment" class="form-label"><%= red_t("red.errors.modals.resolve.notes_label") %></label>
|
|
27
|
+
<%= text_area_tag :resolution_comment, nil, class: "form-control", rows: 4, placeholder: red_t("red.errors.modals.resolve.notes_placeholder") %>
|
|
28
|
+
<small class="text-muted"><%= red_t("red.errors.modals.resolve.notes_hint") %></small>
|
|
29
29
|
</div>
|
|
30
30
|
</div>
|
|
31
31
|
<div class="modal-footer">
|
|
32
|
-
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"
|
|
33
|
-
<%= submit_tag "
|
|
32
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><%= red_t("red.errors.modals.cancel") %></button>
|
|
33
|
+
<%= submit_tag red_t("red.errors.modals.resolve.submit"), class: "btn btn-success", data: { action: "click->loading#click" } %>
|
|
34
34
|
</div>
|
|
35
35
|
<% end %>
|
|
36
36
|
</div>
|
|
@@ -44,20 +44,20 @@
|
|
|
44
44
|
<%= form_with url: assign_error_path(error), method: :post do |f| %>
|
|
45
45
|
<div class="modal-header">
|
|
46
46
|
<h5 class="modal-title" id="assignModalLabel">
|
|
47
|
-
<i class="bi bi-person-plus"></i>
|
|
47
|
+
<i class="bi bi-person-plus"></i> <%= red_t("red.errors.modals.assign.title") %>
|
|
48
48
|
</h5>
|
|
49
|
-
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="
|
|
49
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="<%= red_t("red.common.close") %>"></button>
|
|
50
50
|
</div>
|
|
51
51
|
<div class="modal-body">
|
|
52
52
|
<div class="mb-3">
|
|
53
|
-
<label for="assigned_to" class="form-label"
|
|
54
|
-
<%= text_field_tag :assigned_to, nil, class: "form-control", placeholder: "
|
|
55
|
-
<small class="text-muted"
|
|
53
|
+
<label for="assigned_to" class="form-label"><%= red_t("red.errors.modals.assign.label") %> <span class="text-danger">*</span></label>
|
|
54
|
+
<%= text_field_tag :assigned_to, nil, class: "form-control", placeholder: red_t("red.errors.modals.name_placeholder"), required: true, autofocus: true %>
|
|
55
|
+
<small class="text-muted"><%= red_t("red.errors.modals.assign.hint") %></small>
|
|
56
56
|
</div>
|
|
57
57
|
</div>
|
|
58
58
|
<div class="modal-footer">
|
|
59
|
-
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"
|
|
60
|
-
<%= submit_tag "
|
|
59
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><%= red_t("red.errors.modals.cancel") %></button>
|
|
60
|
+
<%= submit_tag red_t("red.errors.modals.assign.submit"), class: "btn btn-primary", data: { action: "click->loading#click" } %>
|
|
61
61
|
</div>
|
|
62
62
|
<% end %>
|
|
63
63
|
</div>
|
|
@@ -71,25 +71,25 @@
|
|
|
71
71
|
<%= form_with url: update_priority_error_path(error), method: :post do |f| %>
|
|
72
72
|
<div class="modal-header">
|
|
73
73
|
<h5 class="modal-title" id="priorityModalLabel">
|
|
74
|
-
<i class="bi bi-flag"></i>
|
|
74
|
+
<i class="bi bi-flag"></i> <%= red_t("red.errors.modals.priority.title") %>
|
|
75
75
|
</h5>
|
|
76
|
-
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="
|
|
76
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="<%= red_t("red.common.close") %>"></button>
|
|
77
77
|
</div>
|
|
78
78
|
<div class="modal-body">
|
|
79
79
|
<div class="mb-3">
|
|
80
|
-
<label for="priority_level" class="form-label"
|
|
80
|
+
<label for="priority_level" class="form-label"><%= red_t("red.errors.modals.priority.label") %> <span class="text-danger">*</span></label>
|
|
81
81
|
<%= select_tag :priority_level,
|
|
82
82
|
options_for_select(
|
|
83
83
|
RailsErrorDashboard::ErrorLog.priority_options,
|
|
84
84
|
error.priority_level
|
|
85
85
|
),
|
|
86
86
|
class: "form-select", required: true %>
|
|
87
|
-
<small class="text-muted"
|
|
87
|
+
<small class="text-muted"><%= red_t("red.errors.modals.priority.current", value: error.priority_label) %></small>
|
|
88
88
|
</div>
|
|
89
89
|
</div>
|
|
90
90
|
<div class="modal-footer">
|
|
91
|
-
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"
|
|
92
|
-
<%= submit_tag "
|
|
91
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><%= red_t("red.errors.modals.cancel") %></button>
|
|
92
|
+
<%= submit_tag red_t("red.errors.modals.priority.submit"), class: "btn btn-warning", data: { action: "click->loading#click" } %>
|
|
93
93
|
</div>
|
|
94
94
|
<% end %>
|
|
95
95
|
</div>
|
|
@@ -103,35 +103,44 @@
|
|
|
103
103
|
<%= form_with url: snooze_error_path(error), method: :post do |f| %>
|
|
104
104
|
<div class="modal-header">
|
|
105
105
|
<h5 class="modal-title" id="snoozeModalLabel">
|
|
106
|
-
<i class="bi bi-alarm"></i>
|
|
106
|
+
<i class="bi bi-alarm"></i> <%= red_t("red.errors.modals.snooze.title") %>
|
|
107
107
|
</h5>
|
|
108
|
-
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="
|
|
108
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="<%= red_t("red.common.close") %>"></button>
|
|
109
109
|
</div>
|
|
110
110
|
<div class="modal-body">
|
|
111
111
|
<div class="mb-3">
|
|
112
|
-
<label for="hours" class="form-label"
|
|
112
|
+
<label for="hours" class="form-label"><%= red_t("red.errors.modals.snooze.label") %> <span class="text-danger">*</span></label>
|
|
113
|
+
<%# The longer spans are written twice in English ("24 hours (1 day)").
|
|
114
|
+
The halves pluralize on different counts, so each is its own
|
|
115
|
+
plural key and hours_with_equivalent composes the pair. %>
|
|
116
|
+
<% snooze_hours = ->(count) { red_tp("red.errors.modals.snooze.hours", count: count) } %>
|
|
117
|
+
<% snooze_pair = lambda do |hours, unit_key, unit_count|
|
|
118
|
+
red_t("red.errors.modals.snooze.hours_with_equivalent",
|
|
119
|
+
hours: snooze_hours.call(hours),
|
|
120
|
+
equivalent: red_tp("red.errors.modals.snooze.#{unit_key}", count: unit_count))
|
|
121
|
+
end %>
|
|
113
122
|
<%= select_tag :hours,
|
|
114
123
|
options_for_select([
|
|
115
|
-
[
|
|
116
|
-
[
|
|
117
|
-
[
|
|
118
|
-
[
|
|
119
|
-
[
|
|
120
|
-
[
|
|
124
|
+
[ snooze_hours.call(1), 1 ],
|
|
125
|
+
[ snooze_hours.call(4), 4 ],
|
|
126
|
+
[ snooze_hours.call(8), 8 ],
|
|
127
|
+
[ snooze_pair.call(24, "days", 1), 24 ],
|
|
128
|
+
[ snooze_pair.call(48, "days", 2), 48 ],
|
|
129
|
+
[ snooze_pair.call(168, "weeks", 1), 168 ]
|
|
121
130
|
], 24),
|
|
122
131
|
class: "form-select", required: true %>
|
|
123
|
-
<small class="text-muted"
|
|
132
|
+
<small class="text-muted"><%= red_t("red.errors.modals.snooze.hint") %></small>
|
|
124
133
|
</div>
|
|
125
134
|
|
|
126
135
|
<div class="mb-3">
|
|
127
|
-
<label for="reason" class="form-label"
|
|
128
|
-
<%= text_area_tag :reason, nil, class: "form-control", rows: 3, placeholder: "
|
|
129
|
-
<small class="text-muted"
|
|
136
|
+
<label for="reason" class="form-label"><%= red_t("red.errors.modals.snooze.reason_label") %></label>
|
|
137
|
+
<%= text_area_tag :reason, nil, class: "form-control", rows: 3, placeholder: red_t("red.errors.modals.snooze.reason_placeholder") %>
|
|
138
|
+
<small class="text-muted"><%= red_t("red.errors.modals.snooze.reason_hint") %></small>
|
|
130
139
|
</div>
|
|
131
140
|
</div>
|
|
132
141
|
<div class="modal-footer">
|
|
133
|
-
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"
|
|
134
|
-
<%= submit_tag "
|
|
142
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><%= red_t("red.errors.modals.cancel") %></button>
|
|
143
|
+
<%= submit_tag red_t("red.errors.modals.snooze.submit"), class: "btn btn-warning", data: { action: "click->loading#click" } %>
|
|
135
144
|
</div>
|
|
136
145
|
<% end %>
|
|
137
146
|
</div>
|
|
@@ -145,28 +154,27 @@
|
|
|
145
154
|
<%= form_with url: mute_error_path(error), method: :post do |f| %>
|
|
146
155
|
<div class="modal-header">
|
|
147
156
|
<h5 class="modal-title" id="muteModalLabel">
|
|
148
|
-
<i class="bi bi-bell-slash"></i>
|
|
157
|
+
<i class="bi bi-bell-slash"></i> <%= red_t("red.errors.modals.mute.title") %>
|
|
149
158
|
</h5>
|
|
150
|
-
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="
|
|
159
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="<%= red_t("red.common.close") %>"></button>
|
|
151
160
|
</div>
|
|
152
161
|
<div class="modal-body">
|
|
153
162
|
<p class="text-muted">
|
|
154
|
-
|
|
155
|
-
(Slack, email, Discord, PagerDuty, webhooks).
|
|
163
|
+
<%= red_t("red.errors.modals.mute.description") %>
|
|
156
164
|
</p>
|
|
157
165
|
<div class="mb-3">
|
|
158
|
-
<label for="muted_by" class="form-label"
|
|
159
|
-
<%= text_field_tag :muted_by, nil, class: "form-control", placeholder: "
|
|
166
|
+
<label for="muted_by" class="form-label"><%= red_t("red.errors.modals.mute.name_label") %></label>
|
|
167
|
+
<%= text_field_tag :muted_by, nil, class: "form-control", placeholder: red_t("red.errors.modals.name_placeholder") %>
|
|
160
168
|
</div>
|
|
161
169
|
<div class="mb-3">
|
|
162
|
-
<label for="reason" class="form-label"
|
|
163
|
-
<%= text_area_tag :reason, nil, class: "form-control", rows: 3, placeholder: "
|
|
164
|
-
<small class="text-muted"
|
|
170
|
+
<label for="reason" class="form-label"><%= red_t("red.errors.modals.mute.reason_label") %></label>
|
|
171
|
+
<%= text_area_tag :reason, nil, class: "form-control", rows: 3, placeholder: red_t("red.errors.modals.mute.reason_placeholder") %>
|
|
172
|
+
<small class="text-muted"><%= red_t("red.errors.modals.mute.reason_hint") %></small>
|
|
165
173
|
</div>
|
|
166
174
|
</div>
|
|
167
175
|
<div class="modal-footer">
|
|
168
|
-
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"
|
|
169
|
-
<%= submit_tag "
|
|
176
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><%= red_t("red.errors.modals.cancel") %></button>
|
|
177
|
+
<%= submit_tag red_t("red.errors.modals.mute.submit"), class: "btn btn-secondary", data: { action: "click->loading#click" } %>
|
|
170
178
|
</div>
|
|
171
179
|
<% end %>
|
|
172
180
|
</div>
|
|
@@ -1,64 +1,73 @@
|
|
|
1
1
|
<% if RailsErrorDashboard.configuration.enable_occurrence_patterns %>
|
|
2
2
|
<%
|
|
3
|
-
# Display occurrence patterns and bursts for an error
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
# Display occurrence patterns and bursts for an error.
|
|
4
|
+
#
|
|
5
|
+
# The windows and the peak threshold are named because each is cited in
|
|
6
|
+
# prose as well as passed to a query. Written twice they drift, and the
|
|
7
|
+
# copy that drifts is the one the user reads.
|
|
8
|
+
pattern_window_days = 30
|
|
9
|
+
burst_window_days = 7
|
|
10
|
+
peak_hour_multiplier = 2
|
|
11
|
+
shown_bursts = 10
|
|
12
|
+
|
|
13
|
+
pattern_data = @error.occurrence_pattern(days: pattern_window_days)
|
|
14
|
+
bursts = @error.error_bursts(days: burst_window_days)
|
|
6
15
|
%>
|
|
7
16
|
|
|
8
17
|
<% if pattern_data.present? && pattern_data[:total_errors] > 0 %>
|
|
9
18
|
<div class="card mb-4" id="section-occurrence-patterns">
|
|
10
19
|
<div class="card-header">
|
|
11
20
|
<h5 class="mb-0">
|
|
12
|
-
<i class="bi bi-graph-up"></i>
|
|
13
|
-
<small class="text-muted"
|
|
21
|
+
<i class="bi bi-graph-up"></i> <%= red_t("red.errors.patterns.title") %>
|
|
22
|
+
<small class="text-muted"><%= red_t("red.errors.patterns.window", days: pattern_window_days) %></small>
|
|
14
23
|
</h5>
|
|
15
24
|
</div>
|
|
16
25
|
<div class="card-body">
|
|
17
26
|
<!-- Pattern Type Summary -->
|
|
18
27
|
<div class="row mb-4">
|
|
19
28
|
<div class="col-md-6">
|
|
20
|
-
<h6 class="text-muted mb-2"
|
|
29
|
+
<h6 class="text-muted mb-2"><%= red_t("red.errors.patterns.pattern_type") %></h6>
|
|
21
30
|
<% case pattern_data[:pattern_type] %>
|
|
22
31
|
<% when :business_hours %>
|
|
23
32
|
<span class="badge bg-info fs-6">
|
|
24
|
-
<i class="bi bi-clock"></i>
|
|
33
|
+
<i class="bi bi-clock"></i> <%= red_t("red.errors.patterns.types.business_hours.label") %>
|
|
25
34
|
</span>
|
|
26
35
|
<p class="small text-muted mt-2">
|
|
27
|
-
|
|
36
|
+
<%= red_t("red.errors.patterns.types.business_hours.description") %>
|
|
28
37
|
</p>
|
|
29
38
|
<% when :night %>
|
|
30
39
|
<span class="badge bg-dark fs-6">
|
|
31
|
-
<i class="bi bi-moon-fill"></i>
|
|
40
|
+
<i class="bi bi-moon-fill"></i> <%= red_t("red.errors.patterns.types.night.label") %>
|
|
32
41
|
</span>
|
|
33
42
|
<p class="small text-muted mt-2">
|
|
34
|
-
|
|
43
|
+
<%= red_t("red.errors.patterns.types.night.description") %>
|
|
35
44
|
</p>
|
|
36
45
|
<% when :weekend %>
|
|
37
46
|
<span class="badge bg-primary fs-6">
|
|
38
|
-
<i class="bi bi-calendar-week"></i>
|
|
47
|
+
<i class="bi bi-calendar-week"></i> <%= red_t("red.errors.patterns.types.weekend.label") %>
|
|
39
48
|
</span>
|
|
40
49
|
<p class="small text-muted mt-2">
|
|
41
|
-
|
|
50
|
+
<%= red_t("red.errors.patterns.types.weekend.description") %>
|
|
42
51
|
</p>
|
|
43
52
|
<% when :uniform %>
|
|
44
53
|
<span class="badge bg-secondary fs-6">
|
|
45
|
-
<i class="bi bi-distribute-horizontal"></i>
|
|
54
|
+
<i class="bi bi-distribute-horizontal"></i> <%= red_t("red.errors.patterns.types.uniform.label") %>
|
|
46
55
|
</span>
|
|
47
56
|
<p class="small text-muted mt-2">
|
|
48
|
-
|
|
57
|
+
<%= red_t("red.errors.patterns.types.uniform.description") %>
|
|
49
58
|
</p>
|
|
50
59
|
<% else %>
|
|
51
60
|
<span class="badge bg-light text-dark fs-6">
|
|
52
|
-
<i class="bi bi-question-circle"></i>
|
|
61
|
+
<i class="bi bi-question-circle"></i> <%= red_t("red.errors.patterns.types.none.label") %>
|
|
53
62
|
</span>
|
|
54
63
|
<p class="small text-muted mt-2">
|
|
55
|
-
|
|
64
|
+
<%= red_t("red.errors.patterns.types.none.description") %>
|
|
56
65
|
</p>
|
|
57
66
|
<% end %>
|
|
58
67
|
</div>
|
|
59
68
|
|
|
60
69
|
<div class="col-md-6">
|
|
61
|
-
<h6 class="text-muted mb-2"
|
|
70
|
+
<h6 class="text-muted mb-2"><%= red_t("red.errors.patterns.strength") %></h6>
|
|
62
71
|
<div class="d-flex align-items-center">
|
|
63
72
|
<div class="progress flex-grow-1" style="height: 25px;">
|
|
64
73
|
<div class="progress-bar <%= pattern_data[:pattern_strength] >= 0.7 ? 'bg-success' : pattern_data[:pattern_strength] >= 0.4 ? 'bg-warning' : 'bg-secondary' %>"
|
|
@@ -71,11 +80,11 @@
|
|
|
71
80
|
</div>
|
|
72
81
|
<p class="small text-muted mt-2">
|
|
73
82
|
<% if pattern_data[:pattern_strength] >= 0.7 %>
|
|
74
|
-
<strong
|
|
83
|
+
<strong><%= red_t("red.errors.patterns.strengths.strong.label") %></strong> - <%= red_t("red.errors.patterns.strengths.strong.description") %>
|
|
75
84
|
<% elsif pattern_data[:pattern_strength] >= 0.4 %>
|
|
76
|
-
<strong
|
|
85
|
+
<strong><%= red_t("red.errors.patterns.strengths.moderate.label") %></strong> - <%= red_t("red.errors.patterns.strengths.moderate.description") %>
|
|
77
86
|
<% else %>
|
|
78
|
-
<strong
|
|
87
|
+
<strong><%= red_t("red.errors.patterns.strengths.weak.label") %></strong> - <%= red_t("red.errors.patterns.strengths.weak.description") %>
|
|
79
88
|
<% end %>
|
|
80
89
|
</p>
|
|
81
90
|
</div>
|
|
@@ -83,7 +92,7 @@
|
|
|
83
92
|
|
|
84
93
|
<!-- Hourly Distribution Heatmap -->
|
|
85
94
|
<% if pattern_data[:hourly_distribution].present? %>
|
|
86
|
-
<h6 class="text-muted mb-3"
|
|
95
|
+
<h6 class="text-muted mb-3"><%= red_t("red.errors.patterns.heatmap_title") %></h6>
|
|
87
96
|
<div class="mb-4">
|
|
88
97
|
<div class="d-flex flex-wrap">
|
|
89
98
|
<% (0..23).each do |hour| %>
|
|
@@ -94,7 +103,7 @@
|
|
|
94
103
|
<div class="text-center m-1 heatmap-cell-wrapper" style="width: 45px;">
|
|
95
104
|
<div class="rounded p-2 heatmap-cell <%= is_peak ? 'border border-danger border-2' : '' %>"
|
|
96
105
|
style="background-color: rgba(220, 53, 69, <%= intensity / 100.0 %>); min-height: 50px; display: flex; align-items: center; justify-content: center;"
|
|
97
|
-
title="<%=
|
|
106
|
+
title="<%= red_tp("red.errors.patterns.heatmap_cell", count: count, hour: "#{hour}:00") %>">
|
|
98
107
|
<small class="fw-bold heatmap-count <%= intensity > 50 ? 'text-white' : 'text-dark' %>">
|
|
99
108
|
<%= count %>
|
|
100
109
|
</small>
|
|
@@ -105,7 +114,7 @@
|
|
|
105
114
|
</div>
|
|
106
115
|
<div class="mt-2 small text-muted">
|
|
107
116
|
<i class="bi bi-info-circle"></i>
|
|
108
|
-
|
|
117
|
+
<%= red_t("red.errors.patterns.heatmap_legend", multiplier: peak_hour_multiplier) %>
|
|
109
118
|
</div>
|
|
110
119
|
</div>
|
|
111
120
|
<% end %>
|
|
@@ -113,11 +122,11 @@
|
|
|
113
122
|
<!-- Peak Hours -->
|
|
114
123
|
<% if pattern_data[:peak_hours].present? %>
|
|
115
124
|
<div class="alert alert-info">
|
|
116
|
-
<strong><i class="bi bi-clock-fill"></i>
|
|
125
|
+
<strong><i class="bi bi-clock-fill"></i> <%= red_t("red.errors.patterns.peak_hours") %></strong>
|
|
117
126
|
<%= pattern_data[:peak_hours].map { |h| "#{h}:00" }.join(", ") %>
|
|
118
127
|
<br>
|
|
119
128
|
<small>
|
|
120
|
-
|
|
129
|
+
<%= red_t("red.errors.patterns.peak_hours_hint") %>
|
|
121
130
|
</small>
|
|
122
131
|
</div>
|
|
123
132
|
<% end %>
|
|
@@ -130,48 +139,49 @@
|
|
|
130
139
|
<div class="card mb-4">
|
|
131
140
|
<div class="card-header">
|
|
132
141
|
<h5 class="mb-0">
|
|
133
|
-
<i class="bi bi-lightning-charge-fill text-warning"></i>
|
|
142
|
+
<i class="bi bi-lightning-charge-fill text-warning"></i> <%= red_t("red.errors.patterns.bursts_title") %>
|
|
134
143
|
<span class="badge bg-warning text-dark"><%= bursts.count %></span>
|
|
135
|
-
<small class="text-muted"
|
|
144
|
+
<small class="text-muted"><%= red_t("red.errors.patterns.bursts_window", days: burst_window_days) %></small>
|
|
136
145
|
</h5>
|
|
137
146
|
</div>
|
|
138
147
|
<div class="card-body">
|
|
139
148
|
<p class="text-muted">
|
|
140
|
-
|
|
149
|
+
<%= red_t("red.errors.patterns.bursts_hint") %>
|
|
141
150
|
</p>
|
|
142
151
|
|
|
143
152
|
<div class="table-responsive">
|
|
144
153
|
<table class="table table-hover">
|
|
145
154
|
<thead>
|
|
146
155
|
<tr>
|
|
147
|
-
<th
|
|
148
|
-
<th
|
|
149
|
-
<th
|
|
150
|
-
<th
|
|
156
|
+
<th><%= red_t("red.errors.patterns.column_start_time") %></th>
|
|
157
|
+
<th><%= red_t("red.errors.patterns.column_duration") %></th>
|
|
158
|
+
<th><%= red_t("red.errors.patterns.column_error_count") %></th>
|
|
159
|
+
<th><%= red_t("red.errors.patterns.column_intensity") %></th>
|
|
151
160
|
</tr>
|
|
152
161
|
</thead>
|
|
153
162
|
<tbody>
|
|
154
|
-
<% bursts.first(
|
|
163
|
+
<% bursts.first(shown_bursts).each do |burst| %>
|
|
155
164
|
<tr>
|
|
156
165
|
<td>
|
|
157
|
-
<%= burst[:start_time].strftime(
|
|
166
|
+
<%= burst[:start_time].strftime(red_time_format(:full)) %>
|
|
158
167
|
</td>
|
|
159
168
|
<td>
|
|
160
169
|
<% duration_minutes = (burst[:duration_seconds] / 60.0).round(1) %>
|
|
161
|
-
<%= duration_minutes %>
|
|
162
|
-
<small class="text-muted"
|
|
170
|
+
<%= red_t("red.errors.patterns.burst_duration", minutes: duration_minutes) %>
|
|
171
|
+
<small class="text-muted"><%= red_t("red.errors.patterns.burst_duration_seconds", seconds: burst[:duration_seconds].round) %></small>
|
|
163
172
|
</td>
|
|
164
173
|
<td>
|
|
165
|
-
|
|
174
|
+
<%= red_tp("red.errors.patterns.burst_errors_html", count: burst[:error_count],
|
|
175
|
+
value: tag.strong(burst[:error_count])) %>
|
|
166
176
|
</td>
|
|
167
177
|
<td>
|
|
168
178
|
<% case burst[:burst_intensity] %>
|
|
169
179
|
<% when :high %>
|
|
170
|
-
<span class="badge bg-danger"
|
|
180
|
+
<span class="badge bg-danger"><%= red_t("red.errors.patterns.intensity_high") %></span>
|
|
171
181
|
<% when :medium %>
|
|
172
|
-
<span class="badge bg-warning text-dark"
|
|
182
|
+
<span class="badge bg-warning text-dark"><%= red_t("red.errors.patterns.intensity_medium") %></span>
|
|
173
183
|
<% when :low %>
|
|
174
|
-
<span class="badge bg-info"
|
|
184
|
+
<span class="badge bg-info"><%= red_t("red.errors.patterns.intensity_low") %></span>
|
|
175
185
|
<% end %>
|
|
176
186
|
</td>
|
|
177
187
|
</tr>
|
|
@@ -180,20 +190,20 @@
|
|
|
180
190
|
</table>
|
|
181
191
|
</div>
|
|
182
192
|
|
|
183
|
-
<% if bursts.count >
|
|
193
|
+
<% if bursts.count > shown_bursts %>
|
|
184
194
|
<p class="text-muted small mb-0">
|
|
185
|
-
|
|
195
|
+
<%= red_t("red.errors.patterns.bursts_truncated", shown: shown_bursts, total: bursts.count) %>
|
|
186
196
|
</p>
|
|
187
197
|
<% end %>
|
|
188
198
|
|
|
189
199
|
<div class="alert alert-warning mt-3">
|
|
190
|
-
<strong><i class="bi bi-exclamation-triangle"></i>
|
|
191
|
-
|
|
200
|
+
<strong><i class="bi bi-exclamation-triangle"></i> <%= red_t("red.errors.patterns.recommendation_label") %></strong>
|
|
201
|
+
<%= red_t("red.errors.patterns.recommendation_intro") %>
|
|
192
202
|
<ul class="mb-0 mt-2">
|
|
193
|
-
<li
|
|
194
|
-
<li
|
|
195
|
-
<li
|
|
196
|
-
<li
|
|
203
|
+
<li><%= red_t("red.errors.patterns.recommendation_deploys") %></li>
|
|
204
|
+
<li><%= red_t("red.errors.patterns.recommendation_load") %></li>
|
|
205
|
+
<li><%= red_t("red.errors.patterns.recommendation_cascades") %></li>
|
|
206
|
+
<li><%= red_t("red.errors.patterns.recommendation_jobs") %></li>
|
|
197
207
|
</ul>
|
|
198
208
|
</div>
|
|
199
209
|
</div>
|
|
@@ -203,7 +213,7 @@
|
|
|
203
213
|
<% if (pattern_data.blank? || pattern_data[:total_errors] == 0) && bursts.blank? %>
|
|
204
214
|
<div class="alert alert-info">
|
|
205
215
|
<i class="bi bi-info-circle"></i>
|
|
206
|
-
|
|
216
|
+
<%= red_t("red.errors.patterns.empty") %>
|
|
207
217
|
</div>
|
|
208
218
|
<% end %>
|
|
209
219
|
<% end %>
|
|
@@ -1,55 +1,58 @@
|
|
|
1
1
|
<!-- Request Context -->
|
|
2
|
-
|
|
2
|
+
<%# The locale is part of the cache key: this fragment contains translated
|
|
3
|
+
text, and without it the first locale to render an error would be
|
|
4
|
+
served to every other one. %>
|
|
5
|
+
<% cache [error, 'request_context_v5', red_locale] do %>
|
|
3
6
|
<div class="card mb-4" id="section-request-context">
|
|
4
7
|
<div class="card-header">
|
|
5
|
-
<h5 class="mb-0"><i class="bi bi-globe"></i>
|
|
8
|
+
<h5 class="mb-0"><i class="bi bi-globe"></i> <%= red_t("red.errors.request_context.title") %></h5>
|
|
6
9
|
</div>
|
|
7
10
|
<div class="card-body">
|
|
8
11
|
<table class="table table-sm">
|
|
9
12
|
<tr>
|
|
10
|
-
<th width="200"
|
|
13
|
+
<th width="200"><%= red_t("red.errors.request_context.url") %></th>
|
|
11
14
|
<td>
|
|
12
15
|
<% if error.respond_to?(:http_method) && error.http_method.present? %>
|
|
13
16
|
<span class="badge bg-primary me-1"><%= error.http_method %></span>
|
|
14
17
|
<% end %>
|
|
15
|
-
<code><%= error.request_url ||
|
|
18
|
+
<code><%= error.request_url || red_t("red.common.not_available") %></code>
|
|
16
19
|
</td>
|
|
17
20
|
</tr>
|
|
18
21
|
<% if error.respond_to?(:hostname) && error.hostname.present? %>
|
|
19
22
|
<tr>
|
|
20
|
-
<th
|
|
23
|
+
<th><%= red_t("red.errors.request_context.hostname") %></th>
|
|
21
24
|
<td><code><%= error.hostname %></code></td>
|
|
22
25
|
</tr>
|
|
23
26
|
<% end %>
|
|
24
27
|
<% if error.respond_to?(:content_type) && error.content_type.present? %>
|
|
25
28
|
<tr>
|
|
26
|
-
<th
|
|
29
|
+
<th><%= red_t("red.errors.request_context.content_type") %></th>
|
|
27
30
|
<td><code><%= error.content_type %></code></td>
|
|
28
31
|
</tr>
|
|
29
32
|
<% end %>
|
|
30
33
|
<% if error.respond_to?(:request_duration_ms) && error.request_duration_ms.present? %>
|
|
31
34
|
<tr>
|
|
32
|
-
<th
|
|
35
|
+
<th><%= red_t("red.errors.request_context.duration") %></th>
|
|
33
36
|
<td>
|
|
34
37
|
<% duration = error.request_duration_ms %>
|
|
35
38
|
<span class="badge bg-<%= duration > 5000 ? 'danger' : duration > 1000 ? 'warning' : 'success' %>">
|
|
36
|
-
<%= duration > 1000 ? "
|
|
39
|
+
<%= duration > 1000 ? red_t("red.errors.request_context.duration_seconds", value: (duration / 1000.0).round(1)) : red_t("red.errors.request_context.duration_ms", value: duration) %>
|
|
37
40
|
</span>
|
|
38
41
|
</td>
|
|
39
42
|
</tr>
|
|
40
43
|
<% end %>
|
|
41
44
|
<tr>
|
|
42
|
-
<th
|
|
45
|
+
<th><%= red_t("red.errors.request_context.params") %></th>
|
|
43
46
|
<td>
|
|
44
47
|
<% if error.request_params.present? %>
|
|
45
48
|
<pre class="mb-0"><code><%= JSON.pretty_generate(JSON.parse(error.request_params)) rescue error.request_params %></code></pre>
|
|
46
49
|
<% else %>
|
|
47
|
-
<span class="text-muted"
|
|
50
|
+
<span class="text-muted"><%= red_t("red.common.not_available") %></span>
|
|
48
51
|
<% end %>
|
|
49
52
|
</td>
|
|
50
53
|
</tr>
|
|
51
54
|
<tr>
|
|
52
|
-
<th
|
|
55
|
+
<th><%= red_t("red.errors.request_context.user_agent") %></th>
|
|
53
56
|
<td>
|
|
54
57
|
<% if error.user_agent.present? %>
|
|
55
58
|
<% ua_info = parse_user_agent(error.user_agent) %>
|
|
@@ -64,39 +67,39 @@
|
|
|
64
67
|
</div>
|
|
65
68
|
<details>
|
|
66
69
|
<summary class="text-muted" style="cursor: pointer;">
|
|
67
|
-
<small
|
|
70
|
+
<small><%= red_t("red.errors.request_context.raw_user_agent") %></small>
|
|
68
71
|
</summary>
|
|
69
72
|
<small class="text-muted"><%= error.user_agent %></small>
|
|
70
73
|
</details>
|
|
71
74
|
<% else %>
|
|
72
|
-
<span class="text-muted"
|
|
75
|
+
<span class="text-muted"><%= red_t("red.common.not_available") %></span>
|
|
73
76
|
<% end %>
|
|
74
77
|
</td>
|
|
75
78
|
</tr>
|
|
76
79
|
<tr>
|
|
77
|
-
<th
|
|
78
|
-
<td><code><%= error.ip_address ||
|
|
80
|
+
<th><%= red_t("red.errors.request_context.ip_address") %></th>
|
|
81
|
+
<td><code><%= error.ip_address || red_t("red.common.not_available") %></code></td>
|
|
79
82
|
</tr>
|
|
80
83
|
<% curl_cmd = RailsErrorDashboard::Services::CurlGenerator.call(error) %>
|
|
81
84
|
<% rspec_cmd = RailsErrorDashboard::Services::RspecGenerator.call(error) %>
|
|
82
85
|
<% if curl_cmd.present? || rspec_cmd.present? %>
|
|
83
86
|
<tr>
|
|
84
|
-
<th
|
|
87
|
+
<th><%= red_t("red.errors.request_context.replay") %></th>
|
|
85
88
|
<td>
|
|
86
89
|
<% if curl_cmd.present? %>
|
|
87
90
|
<button type="button" class="btn btn-sm btn-outline-primary me-2"
|
|
88
91
|
data-red-action="copy"
|
|
89
92
|
data-red-copy-text="<%= h curl_cmd %>"
|
|
90
|
-
title="
|
|
91
|
-
<i class="bi bi-terminal"></i>
|
|
93
|
+
title="<%= red_t("red.errors.request_context.copy_curl_title") %>">
|
|
94
|
+
<i class="bi bi-terminal"></i> <%= red_t("red.errors.request_context.copy_curl") %>
|
|
92
95
|
</button>
|
|
93
96
|
<% end %>
|
|
94
97
|
<% if rspec_cmd.present? %>
|
|
95
98
|
<button type="button" class="btn btn-sm btn-outline-success"
|
|
96
99
|
data-red-action="copy"
|
|
97
100
|
data-red-copy-text="<%= h rspec_cmd %>"
|
|
98
|
-
title="
|
|
99
|
-
<i class="bi bi-code-slash"></i>
|
|
101
|
+
title="<%= red_t("red.errors.request_context.copy_rspec_title") %>">
|
|
102
|
+
<i class="bi bi-code-slash"></i> <%= red_t("red.errors.request_context.copy_rspec") %>
|
|
100
103
|
</button>
|
|
101
104
|
<% end %>
|
|
102
105
|
</td>
|