rails_error_dashboard 0.1.27 → 0.1.29
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 +210 -11
- data/app/views/layouts/rails_error_dashboard.html.erb +5 -0
- data/app/views/rails_error_dashboard/errors/_user_errors_table.html.erb +70 -0
- data/app/views/rails_error_dashboard/errors/analytics.html.erb +9 -37
- data/app/views/rails_error_dashboard/errors/correlation.html.erb +11 -37
- data/app/views/rails_error_dashboard/errors/show.html.erb +58 -56
- data/lib/rails_error_dashboard/queries/analytics_stats.rb +1 -2
- data/lib/rails_error_dashboard/queries/errors_list.rb +7 -0
- data/lib/rails_error_dashboard/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3c2f62f1af545cfebb6eb7356950d1ef5044ca767f7f0d0f8d048b9f8cedc16f
|
|
4
|
+
data.tar.gz: 95239d1ecadcf74c334b6af8269b3e2fbf2fc27f8b679a91f537f9c81d2ab363
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 867adf79f0eebb0dd5ab6f4ecc417546af088a5ececc76595326a8d1970b1593e2eaca52023c48f5441ebd788daad167d577223500b0f5adbb07b24ef6d0e581
|
|
7
|
+
data.tar.gz: f626ae8fb589b8ddf4a48385c910e52de87d17218f18b62fdf4f29837a37651b8eb103839a8e4a548b6ed1df0ea773e0870ede59550f8ee1b9cf0dfdce958559
|
data/README.md
CHANGED
|
@@ -29,9 +29,9 @@ Experience the full dashboard with 250+ realistic Rails errors, LOTR-themed demo
|
|
|
29
29
|
---
|
|
30
30
|
|
|
31
31
|
### ⚠️ BETA SOFTWARE
|
|
32
|
-
This Rails Engine is in beta and under active development. While functional and tested (
|
|
32
|
+
This Rails Engine is in beta and under active development. While functional and tested (935+ tests passing), the API may change before v1.0.0. Use in production at your own discretion.
|
|
33
33
|
|
|
34
|
-
**Supports**: Rails 7.0 - 8.1 | Ruby 3.2
|
|
34
|
+
**Supports**: Rails 7.0 - 8.0 (+ edge 8.1) | Ruby 3.2 - 3.4
|
|
35
35
|
|
|
36
36
|
---
|
|
37
37
|
|
|
@@ -80,10 +80,6 @@ This Rails Engine is in beta and under active development. While functional and
|
|
|
80
80
|
|
|
81
81
|
---
|
|
82
82
|
|
|
83
|
-

|
|
84
|
-
|
|
85
|
-
---
|
|
86
|
-
|
|
87
83
|
## ✨ Features
|
|
88
84
|
|
|
89
85
|
### Core Features (Always Enabled)
|
|
@@ -121,7 +117,7 @@ HTTP Basic Auth, environment-based settings, optional separate database for isol
|
|
|
121
117
|
- **Separate Database** - Isolate error data for better performance
|
|
122
118
|
- **Database Indexes** - Composite indexes and PostgreSQL GIN full-text search
|
|
123
119
|
|
|
124
|
-
#### 🧠 Advanced Analytics (
|
|
120
|
+
#### 🧠 Advanced Analytics (7 Powerful Features)
|
|
125
121
|
|
|
126
122
|
**1. Baseline Anomaly Alerts** 🔔
|
|
127
123
|
Automatically detect unusual error rate spikes using statistical analysis (mean + std dev). Get proactive notifications when errors exceed expected baselines with intelligent cooldown to avoid alert fatigue.
|
|
@@ -144,8 +140,8 @@ Compare iOS vs Android vs Web health metrics side-by-side. Platform-specific err
|
|
|
144
140
|
**7. Occurrence Pattern Detection** 📈
|
|
145
141
|
Detect cyclical patterns (business hours, nighttime, weekend rhythms) and error bursts (many errors in short time). Understand when and how your errors happen.
|
|
146
142
|
|
|
147
|
-
**
|
|
148
|
-
|
|
143
|
+
**Plus: Developer Insights Dashboard** 💡
|
|
144
|
+
Built-in analytics dashboard with severity detection, platform stability scoring, actionable recommendations, and recent error activity summaries (always available, no configuration needed).
|
|
149
145
|
|
|
150
146
|
#### 🔌 Plugin System
|
|
151
147
|
Extensible architecture with event hooks (`on_error_logged`, `on_error_resolved`, `on_threshold_exceeded`). Built-in examples for Jira integration, metrics tracking, audit logging. Easy to create custom plugins - just drop a file in `config/initializers/error_dashboard_plugins/`.
|
|
@@ -173,7 +169,7 @@ rails db:migrate
|
|
|
173
169
|
The installer will guide you through optional feature selection:
|
|
174
170
|
- **Notifications** (Slack, Email, Discord, PagerDuty, Webhooks)
|
|
175
171
|
- **Performance** (Async Logging, Error Sampling, Separate Database)
|
|
176
|
-
- **Advanced Analytics** (
|
|
172
|
+
- **Advanced Analytics** (7 powerful features including baseline alerts, fuzzy matching, platform comparison)
|
|
177
173
|
|
|
178
174
|
**All features are opt-in** - choose what you need during installation, or enable/disable them later in the initializer.
|
|
179
175
|
|
|
@@ -665,6 +661,193 @@ Rails Error Dashboard is available as open source under the terms of the [MIT Li
|
|
|
665
661
|
|
|
666
662
|
---
|
|
667
663
|
|
|
664
|
+
## ❓ Frequently Asked Questions
|
|
665
|
+
|
|
666
|
+
<details>
|
|
667
|
+
<summary><strong>Is this production-ready?</strong></summary>
|
|
668
|
+
|
|
669
|
+
This is currently in **beta** but actively tested with 935+ passing tests across Rails 7.0-8.0 and Ruby 3.2-3.4. Many users are running it in production. See [production requirements](docs/FEATURES.md#production-readiness).
|
|
670
|
+
</details>
|
|
671
|
+
|
|
672
|
+
<details>
|
|
673
|
+
<summary><strong>How does this compare to Sentry/Rollbar/Honeybadger?</strong></summary>
|
|
674
|
+
|
|
675
|
+
**Similar**: Error tracking, grouping, notifications, dashboards
|
|
676
|
+
**Better**: 100% free, self-hosted (your data stays with you), no usage limits, Rails-optimized
|
|
677
|
+
**Trade-offs**: You manage hosting/backups, fewer integrations than commercial services
|
|
678
|
+
|
|
679
|
+
See [full comparison](docs/features/PLATFORM_COMPARISON.md).
|
|
680
|
+
</details>
|
|
681
|
+
|
|
682
|
+
<details>
|
|
683
|
+
<summary><strong>What's the performance impact?</strong></summary>
|
|
684
|
+
|
|
685
|
+
Minimal with async logging enabled:
|
|
686
|
+
- **Synchronous**: ~10-50ms per error (blocks request)
|
|
687
|
+
- **Async (recommended)**: ~1-2ms (queues to background job)
|
|
688
|
+
- **Sampling**: Log only 10% of non-critical errors for high-traffic apps
|
|
689
|
+
|
|
690
|
+
See [Performance Guide](docs/guides/ERROR_SAMPLING_AND_FILTERING.md).
|
|
691
|
+
</details>
|
|
692
|
+
|
|
693
|
+
<details>
|
|
694
|
+
<summary><strong>Can I use a separate database?</strong></summary>
|
|
695
|
+
|
|
696
|
+
Yes! Configure in your initializer:
|
|
697
|
+
|
|
698
|
+
```ruby
|
|
699
|
+
RailsErrorDashboard.configure do |config|
|
|
700
|
+
config.database = :errors # Use separate database
|
|
701
|
+
end
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
See [Database Options Guide](docs/guides/DATABASE_OPTIONS.md).
|
|
705
|
+
</details>
|
|
706
|
+
|
|
707
|
+
<details>
|
|
708
|
+
<summary><strong>How do I migrate from Sentry/Rollbar?</strong></summary>
|
|
709
|
+
|
|
710
|
+
1. Install Rails Error Dashboard
|
|
711
|
+
2. Run both systems in parallel (1-2 weeks)
|
|
712
|
+
3. Verify all errors are captured
|
|
713
|
+
4. Remove old error tracking gem
|
|
714
|
+
5. Update team documentation
|
|
715
|
+
|
|
716
|
+
Historical data cannot be imported (different formats).
|
|
717
|
+
</details>
|
|
718
|
+
|
|
719
|
+
<details>
|
|
720
|
+
<summary><strong>Does it work with API-only Rails apps?</strong></summary>
|
|
721
|
+
|
|
722
|
+
Yes! The error logging works in API-only mode. The dashboard UI requires a browser but can be:
|
|
723
|
+
- Mounted in a separate admin app
|
|
724
|
+
- Run in a separate Rails instance pointing to the same database
|
|
725
|
+
- Accessed via SSH tunnel
|
|
726
|
+
|
|
727
|
+
See [API-only setup](docs/guides/MOBILE_APP_INTEGRATION.md#backend-setup-rails-api).
|
|
728
|
+
</details>
|
|
729
|
+
|
|
730
|
+
<details>
|
|
731
|
+
<summary><strong>How do I track multiple Rails apps?</strong></summary>
|
|
732
|
+
|
|
733
|
+
Automatic! Just set `APP_NAME` environment variable:
|
|
734
|
+
|
|
735
|
+
```bash
|
|
736
|
+
# App 1
|
|
737
|
+
APP_NAME=my-api rails server
|
|
738
|
+
|
|
739
|
+
# App 2
|
|
740
|
+
APP_NAME=my-admin rails server
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
All apps share the same dashboard. See [Multi-App Guide](docs/MULTI_APP_PERFORMANCE.md).
|
|
744
|
+
</details>
|
|
745
|
+
|
|
746
|
+
<details>
|
|
747
|
+
<summary><strong>Can I customize error severity levels?</strong></summary>
|
|
748
|
+
|
|
749
|
+
Yes! Configure custom rules in your initializer:
|
|
750
|
+
|
|
751
|
+
```ruby
|
|
752
|
+
RailsErrorDashboard.configure do |config|
|
|
753
|
+
config.custom_severity_rules = {
|
|
754
|
+
/ActiveRecord::RecordNotFound/ => :low,
|
|
755
|
+
/Stripe::/ => :critical
|
|
756
|
+
}
|
|
757
|
+
end
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
See [Customization Guide](docs/CUSTOMIZATION.md).
|
|
761
|
+
</details>
|
|
762
|
+
|
|
763
|
+
<details>
|
|
764
|
+
<summary><strong>How long are errors stored?</strong></summary>
|
|
765
|
+
|
|
766
|
+
Forever by default. Configure retention policy:
|
|
767
|
+
|
|
768
|
+
```ruby
|
|
769
|
+
RailsErrorDashboard.configure do |config|
|
|
770
|
+
config.retention_days = 90 # Auto-delete after 90 days
|
|
771
|
+
end
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
Or clean up manually with rake tasks. See [Database Optimization](docs/guides/DATABASE_OPTIMIZATION.md).
|
|
775
|
+
</details>
|
|
776
|
+
|
|
777
|
+
<details>
|
|
778
|
+
<summary><strong>Can I get Slack/Discord notifications?</strong></summary>
|
|
779
|
+
|
|
780
|
+
Yes! Enable during installation or configure manually:
|
|
781
|
+
|
|
782
|
+
```ruby
|
|
783
|
+
RailsErrorDashboard.configure do |config|
|
|
784
|
+
config.enable_slack_notifications = true
|
|
785
|
+
config.slack_webhook_url = ENV['SLACK_WEBHOOK_URL']
|
|
786
|
+
end
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
Supports Slack, Discord, Email, PagerDuty, and custom webhooks. See [Notifications Guide](docs/guides/NOTIFICATIONS.md).
|
|
790
|
+
</details>
|
|
791
|
+
|
|
792
|
+
<details>
|
|
793
|
+
<summary><strong>Does it work with Turbo/Hotwire?</strong></summary>
|
|
794
|
+
|
|
795
|
+
Yes! Includes Turbo Streams support for real-time updates. Errors appear in the dashboard instantly without page refresh.
|
|
796
|
+
</details>
|
|
797
|
+
|
|
798
|
+
<details>
|
|
799
|
+
<summary><strong>How do I report errors from mobile apps (React Native/Flutter)?</strong></summary>
|
|
800
|
+
|
|
801
|
+
Make HTTP POST requests to your Rails API:
|
|
802
|
+
|
|
803
|
+
```javascript
|
|
804
|
+
// React Native example
|
|
805
|
+
fetch('https://api.example.com/error_dashboard/api/v1/errors', {
|
|
806
|
+
method: 'POST',
|
|
807
|
+
headers: {
|
|
808
|
+
'Content-Type': 'application/json',
|
|
809
|
+
'Authorization': 'Basic ' + btoa('admin:password')
|
|
810
|
+
},
|
|
811
|
+
body: JSON.stringify({
|
|
812
|
+
error_class: 'TypeError',
|
|
813
|
+
message: 'Cannot read property...',
|
|
814
|
+
platform: 'iOS'
|
|
815
|
+
})
|
|
816
|
+
});
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
See [Mobile App Integration](docs/guides/MOBILE_APP_INTEGRATION.md).
|
|
820
|
+
</details>
|
|
821
|
+
|
|
822
|
+
<details>
|
|
823
|
+
<summary><strong>Can I build custom integrations?</strong></summary>
|
|
824
|
+
|
|
825
|
+
Yes! Use the plugin system:
|
|
826
|
+
|
|
827
|
+
```ruby
|
|
828
|
+
class MyCustomPlugin < RailsErrorDashboard::Plugin
|
|
829
|
+
def on_error_logged(error_log)
|
|
830
|
+
# Your custom logic
|
|
831
|
+
end
|
|
832
|
+
end
|
|
833
|
+
|
|
834
|
+
RailsErrorDashboard::PluginRegistry.register(MyCustomPlugin.new)
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
See [Plugin System Guide](docs/PLUGIN_SYSTEM.md).
|
|
838
|
+
</details>
|
|
839
|
+
|
|
840
|
+
<details>
|
|
841
|
+
<summary><strong>What if I need help?</strong></summary>
|
|
842
|
+
|
|
843
|
+
- **📖 Read the docs**: [docs/README.md](docs/README.md)
|
|
844
|
+
- **🐛 Report bugs**: [GitHub Issues](https://github.com/AnjanJ/rails_error_dashboard/issues)
|
|
845
|
+
- **💡 Ask questions**: [GitHub Discussions](https://github.com/AnjanJ/rails_error_dashboard/discussions)
|
|
846
|
+
- **🔒 Security issues**: See [SECURITY.md](SECURITY.md)
|
|
847
|
+
</details>
|
|
848
|
+
|
|
849
|
+
---
|
|
850
|
+
|
|
668
851
|
## 💬 Support
|
|
669
852
|
|
|
670
853
|
- **📖 Documentation**: [docs/](docs/README.md)
|
|
@@ -673,6 +856,22 @@ Rails Error Dashboard is available as open source under the terms of the [MIT Li
|
|
|
673
856
|
|
|
674
857
|
---
|
|
675
858
|
|
|
676
|
-
|
|
859
|
+
## 🙏 Contributors
|
|
860
|
+
|
|
861
|
+
Thank you to everyone who has contributed to Rails Error Dashboard!
|
|
862
|
+
|
|
863
|
+
[](https://github.com/AnjanJ/rails_error_dashboard/graphs/contributors)
|
|
864
|
+
|
|
865
|
+
Special thanks to:
|
|
866
|
+
- [@bonniesimon](https://github.com/bonniesimon) - Turbo helpers production fix
|
|
867
|
+
- [@gundestrup](https://github.com/gundestrup) - Security fixes, dependency updates, CI/CD improvements
|
|
868
|
+
|
|
869
|
+
See [CONTRIBUTORS.md](CONTRIBUTORS.md) for the complete list of contributors and their contributions.
|
|
870
|
+
|
|
871
|
+
Want to contribute? Check out our [Contributing Guide](CONTRIBUTING.md)!
|
|
872
|
+
|
|
873
|
+
---
|
|
874
|
+
|
|
875
|
+
**Made with ❤️ by [Anjan](https://www.anjan.dev) for the Rails community**
|
|
677
876
|
|
|
678
877
|
*One Gem to rule them all, One Gem to find them, One Gem to bring them all, and in the dashboard bind them.* 🧙♂️
|
|
@@ -922,6 +922,11 @@
|
|
|
922
922
|
<i class="bi bi-graph-up"></i> Analytics
|
|
923
923
|
<% end %>
|
|
924
924
|
</li>
|
|
925
|
+
<li class="nav-item">
|
|
926
|
+
<%= link_to correlation_errors_path(nav_params), class: "nav-link #{request.path == correlation_errors_path ? 'active' : ''}" do %>
|
|
927
|
+
<i class="bi bi-diagram-3"></i> Correlation
|
|
928
|
+
<% end %>
|
|
929
|
+
</li>
|
|
925
930
|
<li class="nav-item">
|
|
926
931
|
<%= link_to settings_path(nav_params), class: "nav-link #{request.path == settings_path ? 'active' : ''}" do %>
|
|
927
932
|
<i class="bi bi-gear"></i> Settings
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<div class="table-responsive">
|
|
2
|
+
<table class="table table-hover mb-0">
|
|
3
|
+
<thead class="table-light">
|
|
4
|
+
<tr>
|
|
5
|
+
<% if show_rank %>
|
|
6
|
+
<th>Rank</th>
|
|
7
|
+
<% end %>
|
|
8
|
+
<th>User</th>
|
|
9
|
+
<% if show_error_type_count %>
|
|
10
|
+
<th>Different Error Types</th>
|
|
11
|
+
<% end %>
|
|
12
|
+
<th>Error Count</th>
|
|
13
|
+
<% if show_percentage %>
|
|
14
|
+
<th>Percentage</th>
|
|
15
|
+
<% end %>
|
|
16
|
+
<% if show_error_types %>
|
|
17
|
+
<th>Error Types</th>
|
|
18
|
+
<% end %>
|
|
19
|
+
<th>Actions</th>
|
|
20
|
+
</tr>
|
|
21
|
+
</thead>
|
|
22
|
+
<tbody>
|
|
23
|
+
<% users.each_with_index do |user_data, index| %>
|
|
24
|
+
<tr>
|
|
25
|
+
<% if show_rank %>
|
|
26
|
+
<td><strong>#<%= index + 1 %></strong></td>
|
|
27
|
+
<% end %>
|
|
28
|
+
<td><%= user_data[:email] || user_data[:user_email] %></td>
|
|
29
|
+
<% if show_error_type_count %>
|
|
30
|
+
<td>
|
|
31
|
+
<span class="badge bg-warning text-dark">
|
|
32
|
+
<%= user_data[:error_type_count] %> types
|
|
33
|
+
</span>
|
|
34
|
+
</td>
|
|
35
|
+
<% end %>
|
|
36
|
+
<td>
|
|
37
|
+
<span class="badge bg-danger"><%= user_data[:count] || user_data[:total_errors] %></span>
|
|
38
|
+
</td>
|
|
39
|
+
<% if show_percentage && total_errors.present? %>
|
|
40
|
+
<td>
|
|
41
|
+
<div class="progress" style="height: 20px;">
|
|
42
|
+
<div class="progress-bar bg-danger"
|
|
43
|
+
role="progressbar"
|
|
44
|
+
style="width: <%= ((user_data[:count] || user_data[:total_errors]).to_f / total_errors * 100).round(1) %>%"
|
|
45
|
+
aria-valuenow="<%= user_data[:count] || user_data[:total_errors] %>"
|
|
46
|
+
aria-valuemin="0"
|
|
47
|
+
aria-valuemax="100">
|
|
48
|
+
<%= ((user_data[:count] || user_data[:total_errors]).to_f / total_errors * 100).round(1) %>%
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</td>
|
|
52
|
+
<% end %>
|
|
53
|
+
<% if show_error_types && user_data[:error_types].present? %>
|
|
54
|
+
<td>
|
|
55
|
+
<% user_data[:error_types].first(3).each do |error_type| %>
|
|
56
|
+
<code class="small me-1"><%= error_type %></code>
|
|
57
|
+
<% end %>
|
|
58
|
+
<% if user_data[:error_types].count > 3 %>
|
|
59
|
+
<span class="text-muted small">+<%= user_data[:error_types].count - 3 %> more</span>
|
|
60
|
+
<% end %>
|
|
61
|
+
</td>
|
|
62
|
+
<% end %>
|
|
63
|
+
<td>
|
|
64
|
+
<%= link_to "View Errors", errors_path(user_id: user_data[:user_id]), class: "btn btn-sm btn-outline-primary" %>
|
|
65
|
+
</td>
|
|
66
|
+
</tr>
|
|
67
|
+
<% end %>
|
|
68
|
+
</tbody>
|
|
69
|
+
</table>
|
|
70
|
+
</div>
|
|
@@ -266,43 +266,15 @@
|
|
|
266
266
|
<h5 class="mb-0"><i class="bi bi-people"></i> Top 10 Affected Users</h5>
|
|
267
267
|
</div>
|
|
268
268
|
<div class="card-body p-0">
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
</tr>
|
|
279
|
-
</thead>
|
|
280
|
-
<tbody>
|
|
281
|
-
<% @top_users.each_with_index do |(email, count), index| %>
|
|
282
|
-
<tr>
|
|
283
|
-
<td><strong>#<%= index + 1 %></strong></td>
|
|
284
|
-
<td><%= email %></td>
|
|
285
|
-
<td><span class="badge bg-danger"><%= count %></span></td>
|
|
286
|
-
<td>
|
|
287
|
-
<div class="progress" style="height: 20px;">
|
|
288
|
-
<div class="progress-bar bg-danger"
|
|
289
|
-
role="progressbar"
|
|
290
|
-
style="width: <%= (count.to_f / @error_stats[:total] * 100).round(1) %>%"
|
|
291
|
-
aria-valuenow="<%= count %>"
|
|
292
|
-
aria-valuemin="0"
|
|
293
|
-
aria-valuemax="<%= @error_stats[:total] %>">
|
|
294
|
-
<%= (count.to_f / @error_stats[:total] * 100).round(1) %>%
|
|
295
|
-
</div>
|
|
296
|
-
</div>
|
|
297
|
-
</td>
|
|
298
|
-
<td>
|
|
299
|
-
<%= link_to "View Errors", errors_path(search: email), class: "btn btn-sm btn-outline-primary" %>
|
|
300
|
-
</td>
|
|
301
|
-
</tr>
|
|
302
|
-
<% end %>
|
|
303
|
-
</tbody>
|
|
304
|
-
</table>
|
|
305
|
-
</div>
|
|
269
|
+
<%= render partial: 'user_errors_table',
|
|
270
|
+
locals: {
|
|
271
|
+
users: @top_users,
|
|
272
|
+
show_rank: true,
|
|
273
|
+
show_error_type_count: false,
|
|
274
|
+
show_percentage: true,
|
|
275
|
+
show_error_types: false,
|
|
276
|
+
total_errors: @error_stats[:total]
|
|
277
|
+
} %>
|
|
306
278
|
</div>
|
|
307
279
|
</div>
|
|
308
280
|
</div>
|
|
@@ -216,43 +216,17 @@
|
|
|
216
216
|
<small class="text-muted">Users experiencing 2+ different error types</small>
|
|
217
217
|
</div>
|
|
218
218
|
<div class="card-body">
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
<tbody>
|
|
231
|
-
<% @multi_error_users.first(20).each do |user_data| %>
|
|
232
|
-
<tr>
|
|
233
|
-
<td><%= user_data[:user_email] %></td>
|
|
234
|
-
<td>
|
|
235
|
-
<span class="badge bg-warning text-dark">
|
|
236
|
-
<%= user_data[:error_type_count] %> types
|
|
237
|
-
</span>
|
|
238
|
-
</td>
|
|
239
|
-
<td><%= user_data[:total_errors] %></td>
|
|
240
|
-
<td>
|
|
241
|
-
<% user_data[:error_types].first(3).each do |error_type| %>
|
|
242
|
-
<code class="small me-1"><%= error_type %></code>
|
|
243
|
-
<% end %>
|
|
244
|
-
<% if user_data[:error_types].count > 3 %>
|
|
245
|
-
<span class="text-muted small">+<%= user_data[:error_types].count - 3 %> more</span>
|
|
246
|
-
<% end %>
|
|
247
|
-
</td>
|
|
248
|
-
<td>
|
|
249
|
-
<%= link_to "View", errors_path(search: user_data[:user_email]), class: "btn btn-sm btn-outline-primary" %>
|
|
250
|
-
</td>
|
|
251
|
-
</tr>
|
|
252
|
-
<% end %>
|
|
253
|
-
</tbody>
|
|
254
|
-
</table>
|
|
255
|
-
</div>
|
|
219
|
+
<%= render partial: 'user_errors_table',
|
|
220
|
+
locals: {
|
|
221
|
+
users: @multi_error_users.first(20),
|
|
222
|
+
show_rank: false,
|
|
223
|
+
show_error_type_count: true,
|
|
224
|
+
show_percentage: false,
|
|
225
|
+
show_error_types: true,
|
|
226
|
+
total_errors: nil
|
|
227
|
+
} %>
|
|
228
|
+
</div>
|
|
229
|
+
<div class="card-body pt-0 border-top">
|
|
256
230
|
<% if @multi_error_users.count > 20 %>
|
|
257
231
|
<p class="text-muted small mb-0">
|
|
258
232
|
Showing 20 of <%= @multi_error_users.count %> users
|
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
<% content_for :page_title, "Error ##{@error.id}" %>
|
|
2
2
|
|
|
3
|
+
<script>
|
|
4
|
+
function downloadErrorJSON(event) {
|
|
5
|
+
const errorData = {
|
|
6
|
+
id: <%= raw @error.id.to_json %>,
|
|
7
|
+
error_type: <%= raw @error.error_type.to_json %>,
|
|
8
|
+
message: <%= raw @error.message.to_json %>,
|
|
9
|
+
backtrace: <%= raw @error.backtrace.to_json %>,
|
|
10
|
+
occurred_at: <%= raw @error.occurred_at.to_json %>,
|
|
11
|
+
first_seen_at: <%= raw @error.first_seen_at.to_json %>,
|
|
12
|
+
last_seen_at: <%= raw @error.last_seen_at.to_json %>,
|
|
13
|
+
occurrence_count: <%= raw @error.occurrence_count.to_json %>,
|
|
14
|
+
resolved: <%= raw @error.resolved?.to_json %>,
|
|
15
|
+
resolved_at: <%= raw @error.resolved_at.to_json %>,
|
|
16
|
+
resolved_by_name: <%= raw @error.resolved_by_name.to_json %>,
|
|
17
|
+
platform: <%= raw @error.platform.to_json %>,
|
|
18
|
+
user_id: <%= raw @error.user_id.to_json %>,
|
|
19
|
+
severity: <%= raw @error.severity.to_json %>,
|
|
20
|
+
priority_level: <%= raw (@error.priority_level || 0).to_json %>,
|
|
21
|
+
<% if @error.respond_to?(:app_version) %>
|
|
22
|
+
app_version: <%= raw @error.app_version.to_json %>,
|
|
23
|
+
<% end %>
|
|
24
|
+
<% if @error.respond_to?(:git_commit) %>
|
|
25
|
+
git_commit: <%= raw @error.git_commit.to_json %>,
|
|
26
|
+
<% end %>
|
|
27
|
+
created_at: <%= raw @error.created_at.to_json %>,
|
|
28
|
+
updated_at: <%= raw @error.updated_at.to_json %>
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const jsonString = JSON.stringify(errorData, null, 2);
|
|
32
|
+
const blob = new Blob([jsonString], { type: 'application/json' });
|
|
33
|
+
const url = URL.createObjectURL(blob);
|
|
34
|
+
const link = document.createElement('a');
|
|
35
|
+
link.href = url;
|
|
36
|
+
link.download = `error_${errorData.id}_${errorData.error_type.replace(/[^a-zA-Z0-9]/g, '_')}.json`;
|
|
37
|
+
document.body.appendChild(link);
|
|
38
|
+
link.click();
|
|
39
|
+
document.body.removeChild(link);
|
|
40
|
+
URL.revokeObjectURL(url);
|
|
41
|
+
|
|
42
|
+
// Visual feedback
|
|
43
|
+
const button = event.currentTarget;
|
|
44
|
+
const originalHTML = button.innerHTML;
|
|
45
|
+
button.innerHTML = '<i class="bi bi-check"></i> Downloaded!';
|
|
46
|
+
button.classList.remove('btn-outline-secondary');
|
|
47
|
+
button.classList.add('btn-success');
|
|
48
|
+
|
|
49
|
+
setTimeout(() => {
|
|
50
|
+
button.innerHTML = originalHTML;
|
|
51
|
+
button.classList.remove('btn-success');
|
|
52
|
+
button.classList.add('btn-outline-secondary');
|
|
53
|
+
}, 2000);
|
|
54
|
+
}
|
|
55
|
+
</script>
|
|
56
|
+
|
|
3
57
|
<div class="py-4">
|
|
4
58
|
<!-- Breadcrumbs -->
|
|
5
59
|
<nav aria-label="breadcrumb" class="mb-3">
|
|
@@ -27,7 +81,7 @@
|
|
|
27
81
|
</h2>
|
|
28
82
|
</div>
|
|
29
83
|
<div class="d-flex gap-2">
|
|
30
|
-
<button type="button" class="btn btn-outline-secondary" onclick="downloadErrorJSON()" title="Download error details as JSON">
|
|
84
|
+
<button type="button" class="btn btn-outline-secondary" onclick="downloadErrorJSON(event)" title="Download error details as JSON">
|
|
31
85
|
<i class="bi bi-download"></i> Export JSON
|
|
32
86
|
</button>
|
|
33
87
|
<% if @error.resolved? %>
|
|
@@ -42,60 +96,6 @@
|
|
|
42
96
|
</div>
|
|
43
97
|
</div>
|
|
44
98
|
|
|
45
|
-
<script>
|
|
46
|
-
function downloadErrorJSON() {
|
|
47
|
-
const errorData = {
|
|
48
|
-
id: <%= @error.id %>,
|
|
49
|
-
error_type: <%= json_escape @error.error_type.to_json %>,
|
|
50
|
-
message: <%= json_escape @error.message.to_json %>,
|
|
51
|
-
backtrace: <%= json_escape @error.backtrace.to_json %>,
|
|
52
|
-
occurred_at: <%= json_escape @error.occurred_at.to_json %>,
|
|
53
|
-
first_seen_at: <%= json_escape @error.first_seen_at.to_json %>,
|
|
54
|
-
last_seen_at: <%= json_escape @error.last_seen_at.to_json %>,
|
|
55
|
-
occurrence_count: <%= @error.occurrence_count %>,
|
|
56
|
-
resolved: <%= @error.resolved? %>,
|
|
57
|
-
resolved_at: <%= json_escape @error.resolved_at.to_json %>,
|
|
58
|
-
resolved_by_name: <%= json_escape @error.resolved_by_name.to_json %>,
|
|
59
|
-
platform: <%= json_escape @error.platform.to_json %>,
|
|
60
|
-
user_id: <%= json_escape @error.user_id.to_json %>,
|
|
61
|
-
severity: <%= json_escape @error.severity.to_json %>,
|
|
62
|
-
priority_level: <%= @error.priority_level || 0 %>,
|
|
63
|
-
<% if @error.respond_to?(:app_version) %>
|
|
64
|
-
app_version: <%= json_escape @error.app_version.to_json %>,
|
|
65
|
-
<% end %>
|
|
66
|
-
<% if @error.respond_to?(:git_commit) %>
|
|
67
|
-
git_commit: <%= json_escape @error.git_commit.to_json %>,
|
|
68
|
-
<% end %>
|
|
69
|
-
created_at: <%= json_escape @error.created_at.to_json %>,
|
|
70
|
-
updated_at: <%= json_escape @error.updated_at.to_json %>
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const jsonString = JSON.stringify(errorData, null, 2);
|
|
74
|
-
const blob = new Blob([jsonString], { type: 'application/json' });
|
|
75
|
-
const url = URL.createObjectURL(blob);
|
|
76
|
-
const link = document.createElement('a');
|
|
77
|
-
link.href = url;
|
|
78
|
-
link.download = `error_${errorData.id}_${errorData.error_type.replace(/[^a-zA-Z0-9]/g, '_')}.json`;
|
|
79
|
-
document.body.appendChild(link);
|
|
80
|
-
link.click();
|
|
81
|
-
document.body.removeChild(link);
|
|
82
|
-
URL.revokeObjectURL(url);
|
|
83
|
-
|
|
84
|
-
// Visual feedback
|
|
85
|
-
const button = event.currentTarget;
|
|
86
|
-
const originalHTML = button.innerHTML;
|
|
87
|
-
button.innerHTML = '<i class="bi bi-check"></i> Downloaded!';
|
|
88
|
-
button.classList.remove('btn-outline-secondary');
|
|
89
|
-
button.classList.add('btn-success');
|
|
90
|
-
|
|
91
|
-
setTimeout(() => {
|
|
92
|
-
button.innerHTML = originalHTML;
|
|
93
|
-
button.classList.remove('btn-success');
|
|
94
|
-
button.classList.add('btn-outline-secondary');
|
|
95
|
-
}, 2000);
|
|
96
|
-
}
|
|
97
|
-
</script>
|
|
98
|
-
|
|
99
99
|
<div class="row g-4">
|
|
100
100
|
<!-- Error Information -->
|
|
101
101
|
<div class="col-md-8">
|
|
@@ -741,7 +741,9 @@
|
|
|
741
741
|
<div class="mb-3">
|
|
742
742
|
<small class="text-muted d-block mb-1">Workflow Status</small>
|
|
743
743
|
<% if @error.respond_to?(:status) %>
|
|
744
|
-
|
|
744
|
+
<% badge_color = @error.status_badge_color %>
|
|
745
|
+
<% text_dark = %w[info warning light].include?(badge_color) ? 'text-dark' : '' %>
|
|
746
|
+
<span class="badge bg-<%= badge_color %> <%= text_dark %> fs-6">
|
|
745
747
|
<%= @error.status.titleize %>
|
|
746
748
|
</span>
|
|
747
749
|
<% elsif @error.resolved? %>
|
|
@@ -100,8 +100,7 @@ module RailsErrorDashboard
|
|
|
100
100
|
.count
|
|
101
101
|
.sort_by { |_, count| -count }
|
|
102
102
|
.first(10)
|
|
103
|
-
.map { |user_id, count|
|
|
104
|
-
.to_h
|
|
103
|
+
.map { |user_id, count| { user_id: user_id, email: find_user_email(user_id, user_model), count: count } }
|
|
105
104
|
end
|
|
106
105
|
|
|
107
106
|
def find_user_email(user_id, user_model)
|
|
@@ -29,6 +29,7 @@ module RailsErrorDashboard
|
|
|
29
29
|
query = filter_by_resolved(query)
|
|
30
30
|
query = filter_by_platform(query)
|
|
31
31
|
query = filter_by_application(query)
|
|
32
|
+
query = filter_by_user_id(query)
|
|
32
33
|
query = filter_by_search(query)
|
|
33
34
|
query = filter_by_severity(query)
|
|
34
35
|
query = filter_by_timeframe(query)
|
|
@@ -82,6 +83,12 @@ module RailsErrorDashboard
|
|
|
82
83
|
query.where(application_id: @filters[:application_id])
|
|
83
84
|
end
|
|
84
85
|
|
|
86
|
+
def filter_by_user_id(query)
|
|
87
|
+
return query unless @filters[:user_id].present?
|
|
88
|
+
|
|
89
|
+
query.where(user_id: @filters[:user_id])
|
|
90
|
+
end
|
|
91
|
+
|
|
85
92
|
def filter_by_search(query)
|
|
86
93
|
return query unless @filters[:search].present?
|
|
87
94
|
|
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.1.
|
|
4
|
+
version: 0.1.29
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Anjan Jagirdar
|
|
@@ -116,7 +116,7 @@ dependencies:
|
|
|
116
116
|
version: 1.3.0
|
|
117
117
|
- - "<"
|
|
118
118
|
- !ruby/object:Gem::Version
|
|
119
|
-
version: 1.3.
|
|
119
|
+
version: 1.3.7
|
|
120
120
|
type: :runtime
|
|
121
121
|
prerelease: false
|
|
122
122
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -126,7 +126,7 @@ dependencies:
|
|
|
126
126
|
version: 1.3.0
|
|
127
127
|
- - "<"
|
|
128
128
|
- !ruby/object:Gem::Version
|
|
129
|
-
version: 1.3.
|
|
129
|
+
version: 1.3.7
|
|
130
130
|
- !ruby/object:Gem::Dependency
|
|
131
131
|
name: rspec-rails
|
|
132
132
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -305,6 +305,7 @@ files:
|
|
|
305
305
|
- app/views/rails_error_dashboard/errors/_pattern_insights.html.erb
|
|
306
306
|
- app/views/rails_error_dashboard/errors/_stats.html.erb
|
|
307
307
|
- app/views/rails_error_dashboard/errors/_timeline.html.erb
|
|
308
|
+
- app/views/rails_error_dashboard/errors/_user_errors_table.html.erb
|
|
308
309
|
- app/views/rails_error_dashboard/errors/analytics.html.erb
|
|
309
310
|
- app/views/rails_error_dashboard/errors/correlation.html.erb
|
|
310
311
|
- app/views/rails_error_dashboard/errors/index.html.erb
|
|
@@ -386,7 +387,7 @@ metadata:
|
|
|
386
387
|
source_code_uri: https://github.com/AnjanJ/rails_error_dashboard
|
|
387
388
|
changelog_uri: https://github.com/AnjanJ/rails_error_dashboard/blob/main/CHANGELOG.md
|
|
388
389
|
post_install_message: "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n
|
|
389
|
-
\ Rails Error Dashboard v0.1.
|
|
390
|
+
\ Rails Error Dashboard v0.1.29\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\U0001F195
|
|
390
391
|
First time? Quick start:\n rails generate rails_error_dashboard:install\n rails
|
|
391
392
|
db:migrate\n # Add to config/routes.rb:\n mount RailsErrorDashboard::Engine
|
|
392
393
|
=> '/error_dashboard'\n\n\U0001F504 Upgrading from v0.1.x?\n rails db:migrate\n
|