rails_error_dashboard 0.5.8 → 0.5.9
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 +3 -2
- data/app/controllers/rails_error_dashboard/errors_controller.rb +41 -17
- data/app/views/rails_error_dashboard/errors/_breadcrumbs_group.html.erb +1 -1
- data/app/views/rails_error_dashboard/errors/_discussion.html.erb +76 -43
- data/app/views/rails_error_dashboard/errors/_issue_section.html.erb +78 -24
- data/app/views/rails_error_dashboard/errors/_show_scripts.html.erb +1 -0
- data/app/views/rails_error_dashboard/errors/_sidebar_metadata.html.erb +77 -73
- data/app/views/rails_error_dashboard/errors/show.html.erb +10 -8
- data/config/routes.rb +0 -1
- data/lib/rails_error_dashboard/services/codeberg_issue_client.rb +23 -0
- data/lib/rails_error_dashboard/services/github_issue_client.rb +23 -0
- data/lib/rails_error_dashboard/services/gitlab_issue_client.rb +23 -0
- data/lib/rails_error_dashboard/services/issue_tracker_client.rb +6 -0
- data/lib/rails_error_dashboard/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 18faae83cf7bba23409732000c1cb230ba7ef067f23a454084445f754df4be0d
|
|
4
|
+
data.tar.gz: ad82e112e173f8247cf31b214b040ba82e4035040bbdf14d8c53a5782db36bb1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0d0032aeb56ccc44a8c8966da338f75c37e71002534fd0bbf3e019cfe0582df37638e50863e8d4a77212731c1190de214d521521df2e986a23d36a575748d5d9
|
|
7
|
+
data.tar.gz: 4862e358faa4a87f1091e33cf08c0e654a7e959b8af551a1223e3c9d098d383d503342aa8c2fb4d66f20c95fc4864276df631be6b93bcdd22ef50269516ef997
|
data/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
[](https://rubygems.org/gems/rails_error_dashboard)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
[](https://github.com/AnjanJ/rails_error_dashboard/actions)
|
|
7
|
+
[](https://github.com/sponsors/AnjanJ)
|
|
7
8
|
[](https://buymeacoffee.com/anjanj)
|
|
8
9
|
|
|
9
10
|
**Self-hosted Rails error monitoring — free, forever.**
|
|
@@ -479,9 +480,9 @@ Special thanks to [@bonniesimon](https://github.com/bonniesimon), [@gundestrup](
|
|
|
479
480
|
|
|
480
481
|
## Support
|
|
481
482
|
|
|
482
|
-
If this gem saves you some headaches (or some money on error tracking SaaS), consider
|
|
483
|
+
If this gem saves you some headaches (or some money on error tracking SaaS), consider sponsoring the project. It keeps RED going and lets me know people are finding it useful.
|
|
483
484
|
|
|
484
|
-
<a href="https://www.buymeacoffee.com/anjanj" target="_blank"><img src="https://
|
|
485
|
+
<a href="https://github.com/sponsors/AnjanJ" target="_blank"><img src="https://img.shields.io/badge/Sponsor_on_GitHub-ea4aaa?style=for-the-badge&logo=githubsponsors&logoColor=white" alt="Sponsor on GitHub"></a> <a href="https://www.buymeacoffee.com/anjanj" target="_blank"><img src="https://img.shields.io/badge/Buy_Me_A_Coffee-FFDD00?style=for-the-badge&logo=buymeacoffee&logoColor=black" alt="Buy Me A Coffee"></a>
|
|
485
486
|
|
|
486
487
|
---
|
|
487
488
|
|
|
@@ -81,6 +81,10 @@ module RailsErrorDashboard
|
|
|
81
81
|
@related_errors = @error.related_errors(limit: 5, application_id: @current_application_id)
|
|
82
82
|
@error_markdown = Services::MarkdownErrorFormatter.call(@error, related_errors: @related_errors)
|
|
83
83
|
|
|
84
|
+
# Fetch platform issue state and comments if linked
|
|
85
|
+
@platform_issue = fetch_platform_issue(@error)
|
|
86
|
+
@platform_comments = fetch_platform_comments(@error)
|
|
87
|
+
|
|
84
88
|
# Dispatch plugin event for error viewed
|
|
85
89
|
RailsErrorDashboard::PluginRegistry.dispatch(:on_error_viewed, @error)
|
|
86
90
|
end
|
|
@@ -144,11 +148,12 @@ module RailsErrorDashboard
|
|
|
144
148
|
result = Commands::CreateIssue.call(params[:id], dashboard_url: dashboard_url)
|
|
145
149
|
|
|
146
150
|
if result[:success]
|
|
147
|
-
flash[:notice] = "Issue created
|
|
151
|
+
flash[:notice] = "Issue created successfully"
|
|
152
|
+
flash[:new_issue_url] = result[:issue_url]
|
|
148
153
|
else
|
|
149
154
|
flash[:alert] = "Failed to create issue: #{result[:error]}"
|
|
150
155
|
end
|
|
151
|
-
redirect_to error_path(params[:id])
|
|
156
|
+
redirect_to error_path(params[:id], anchor: "issue-tracking")
|
|
152
157
|
end
|
|
153
158
|
|
|
154
159
|
def link_issue
|
|
@@ -159,21 +164,7 @@ module RailsErrorDashboard
|
|
|
159
164
|
else
|
|
160
165
|
flash[:alert] = "Failed to link issue: #{result[:error]}"
|
|
161
166
|
end
|
|
162
|
-
redirect_to error_path(params[:id])
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def unlink_issue
|
|
166
|
-
error = ErrorLog.find(params[:id])
|
|
167
|
-
error.update!(
|
|
168
|
-
external_issue_url: nil,
|
|
169
|
-
external_issue_number: nil,
|
|
170
|
-
external_issue_provider: nil
|
|
171
|
-
)
|
|
172
|
-
flash[:notice] = "Issue unlinked"
|
|
173
|
-
redirect_to error_path(error)
|
|
174
|
-
rescue => e
|
|
175
|
-
flash[:alert] = "Failed to unlink issue: #{e.message}"
|
|
176
|
-
redirect_to error_path(params[:id])
|
|
167
|
+
redirect_to error_path(params[:id], anchor: "issue-tracking")
|
|
177
168
|
end
|
|
178
169
|
|
|
179
170
|
def analytics
|
|
@@ -563,6 +554,39 @@ module RailsErrorDashboard
|
|
|
563
554
|
@applications = Application.ordered_by_name.pluck(:name, :id)
|
|
564
555
|
end
|
|
565
556
|
|
|
557
|
+
def fetch_platform_issue(error)
|
|
558
|
+
return nil unless error.external_issue_url.present? && error.external_issue_number.present?
|
|
559
|
+
return nil unless RailsErrorDashboard.configuration.enable_issue_tracking
|
|
560
|
+
|
|
561
|
+
cache_key = "red/issue_state/#{error.external_issue_provider}/#{error.external_issue_number}"
|
|
562
|
+
Rails.cache.fetch(cache_key, expires_in: 60.seconds) do
|
|
563
|
+
client = Services::IssueTrackerClient.from_config
|
|
564
|
+
return nil unless client
|
|
565
|
+
|
|
566
|
+
result = client.fetch_issue(number: error.external_issue_number)
|
|
567
|
+
result[:success] ? result : nil
|
|
568
|
+
end
|
|
569
|
+
rescue => e
|
|
570
|
+
nil
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
def fetch_platform_comments(error)
|
|
574
|
+
return [] unless error.external_issue_url.present? && error.external_issue_number.present?
|
|
575
|
+
return [] unless RailsErrorDashboard.configuration.enable_issue_tracking
|
|
576
|
+
|
|
577
|
+
# Cache for 60 seconds to avoid API hammering on page refreshes
|
|
578
|
+
cache_key = "red/issue_comments/#{error.external_issue_provider}/#{error.external_issue_number}"
|
|
579
|
+
Rails.cache.fetch(cache_key, expires_in: 60.seconds) do
|
|
580
|
+
client = Services::IssueTrackerClient.from_config
|
|
581
|
+
return [] unless client
|
|
582
|
+
|
|
583
|
+
result = client.fetch_comments(number: error.external_issue_number, per_page: 20)
|
|
584
|
+
result[:success] ? result[:comments] : []
|
|
585
|
+
end
|
|
586
|
+
rescue => e
|
|
587
|
+
[]
|
|
588
|
+
end
|
|
589
|
+
|
|
566
590
|
def check_default_credentials
|
|
567
591
|
@default_credentials_warning = RailsErrorDashboard.configuration.default_credentials?
|
|
568
592
|
end
|
|
@@ -196,7 +196,7 @@
|
|
|
196
196
|
</h5>
|
|
197
197
|
<small class="text-muted">Activity trail leading up to this error</small>
|
|
198
198
|
</div>
|
|
199
|
-
<div class="card-body p-0">
|
|
199
|
+
<div class="card-body p-0" style="max-height: 400px; overflow-y: auto;">
|
|
200
200
|
<div class="table-responsive">
|
|
201
201
|
<table class="table table-sm table-hover mb-0">
|
|
202
202
|
<thead class="table-light">
|
|
@@ -1,66 +1,99 @@
|
|
|
1
1
|
<% has_linked_issue = error.respond_to?(:external_issue_url) && error.external_issue_url.present? %>
|
|
2
|
-
<%
|
|
2
|
+
<% has_audit_comments = error.respond_to?(:comments) && error.comments.any? %>
|
|
3
|
+
<% platform_comments = @platform_comments || [] %>
|
|
3
4
|
<% issue_tracking_enabled = RailsErrorDashboard.configuration.enable_issue_tracking %>
|
|
4
5
|
|
|
5
|
-
<% if has_linked_issue
|
|
6
|
+
<% if has_linked_issue && (platform_comments.any? || has_audit_comments) %>
|
|
6
7
|
<div class="card mb-4" id="section-discussion">
|
|
7
|
-
<div class="card-header bg-white">
|
|
8
|
+
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
|
8
9
|
<h5 class="mb-0">
|
|
9
10
|
<i class="bi bi-chat-dots"></i> Discussion
|
|
10
|
-
<% if
|
|
11
|
-
<span class="badge bg-secondary ms-2"><%=
|
|
11
|
+
<% if platform_comments.any? %>
|
|
12
|
+
<span class="badge bg-secondary ms-2"><%= platform_comments.size %></span>
|
|
12
13
|
<% end %>
|
|
13
14
|
</h5>
|
|
15
|
+
<a href="<%= error.external_issue_url %>" target="_blank" rel="noopener" class="btn btn-sm btn-outline-primary">
|
|
16
|
+
<i class="bi bi-box-arrow-up-right me-1"></i> Reply on <%= error.external_issue_provider&.capitalize || "Platform" %>
|
|
17
|
+
</a>
|
|
14
18
|
</div>
|
|
15
|
-
<div class="card-body">
|
|
16
|
-
<% if
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
Link or create an issue above to start a discussion on your issue tracker.
|
|
37
|
-
</p>
|
|
19
|
+
<div class="card-body p-0">
|
|
20
|
+
<% if platform_comments.any? %>
|
|
21
|
+
<div style="max-height: 400px; overflow-y: auto;">
|
|
22
|
+
<% platform_comments.each_with_index do |comment, index| %>
|
|
23
|
+
<div class="px-3 py-3 <%= index < platform_comments.size - 1 ? 'border-bottom' : '' %>">
|
|
24
|
+
<div class="d-flex align-items-start gap-2 mb-2">
|
|
25
|
+
<% if comment[:avatar_url].present? %>
|
|
26
|
+
<img src="<%= comment[:avatar_url] %>" alt="<%= comment[:author] %>" width="24" height="24" class="rounded-circle" style="flex-shrink: 0;">
|
|
27
|
+
<% else %>
|
|
28
|
+
<i class="bi bi-person-circle" style="font-size: 1.3em; flex-shrink: 0;"></i>
|
|
29
|
+
<% end %>
|
|
30
|
+
<div>
|
|
31
|
+
<strong class="text-primary"><%= comment[:author] || "Unknown" %></strong>
|
|
32
|
+
<small class="text-muted ms-2"><%= comment[:created_at] ? Time.parse(comment[:created_at]).strftime("%b %d, %Y %H:%M") : "" %></small>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="ms-4 text-break" style="font-size: 0.9em;">
|
|
36
|
+
<%= simple_format(h(comment[:body].to_s.truncate(500)), {}, wrapper_tag: "span") %>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
<% end %>
|
|
38
40
|
</div>
|
|
39
41
|
<% end %>
|
|
40
42
|
|
|
41
|
-
<% if
|
|
42
|
-
<!-- Audit trail —
|
|
43
|
-
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
<% end %>
|
|
43
|
+
<% if has_audit_comments %>
|
|
44
|
+
<!-- Audit trail — workflow actions -->
|
|
45
|
+
<div class="px-3 py-2 bg-light border-top">
|
|
46
|
+
<small class="text-muted fw-bold"><i class="bi bi-clock-history"></i> Activity Log</small>
|
|
47
|
+
</div>
|
|
47
48
|
<% error.comments.recent_first.each_with_index do |comment, index| %>
|
|
48
|
-
<div class="<%= index < error.comments.count - 1 ? 'border-bottom' : '' %>
|
|
49
|
-
<div class="d-flex justify-content-between align-items-start
|
|
49
|
+
<div class="px-3 py-2 <%= index < error.comments.count - 1 ? 'border-bottom' : '' %>">
|
|
50
|
+
<div class="d-flex justify-content-between align-items-start">
|
|
50
51
|
<div>
|
|
51
|
-
<
|
|
52
|
-
<i class="bi bi-person-circle"></i> <%= comment.author_name %>
|
|
53
|
-
</strong>
|
|
52
|
+
<small class="text-primary fw-bold"><i class="bi bi-person-circle"></i> <%= comment.author_name %></small>
|
|
54
53
|
<small class="text-muted ms-2"><%= comment.formatted_time %></small>
|
|
55
|
-
<% if comment.recent? %>
|
|
56
|
-
<span class="badge bg-info ms-1">New</span>
|
|
57
|
-
<% end %>
|
|
58
54
|
</div>
|
|
59
55
|
</div>
|
|
60
|
-
<
|
|
56
|
+
<small class="text-muted text-break"><%= simple_format(h(comment.body), {}, wrapper_tag: "span") %></small>
|
|
61
57
|
</div>
|
|
62
58
|
<% end %>
|
|
63
59
|
<% end %>
|
|
64
60
|
</div>
|
|
65
61
|
</div>
|
|
62
|
+
<% elsif has_linked_issue %>
|
|
63
|
+
<!-- Issue linked but no comments yet -->
|
|
64
|
+
<div class="card mb-4" id="section-discussion">
|
|
65
|
+
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
|
66
|
+
<h5 class="mb-0">
|
|
67
|
+
<i class="bi bi-chat-dots"></i> Discussion
|
|
68
|
+
</h5>
|
|
69
|
+
<a href="<%= error.external_issue_url %>" target="_blank" rel="noopener" class="btn btn-sm btn-outline-primary">
|
|
70
|
+
<i class="bi bi-box-arrow-up-right me-1"></i> Start discussion on <%= error.external_issue_provider&.capitalize || "Platform" %>
|
|
71
|
+
</a>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="card-body text-center py-3">
|
|
74
|
+
<p class="text-muted small mb-0">No comments yet. Be the first to discuss this error on <%= error.external_issue_provider&.capitalize || "the platform" %>.</p>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
<% elsif has_audit_comments %>
|
|
78
|
+
<!-- No issue linked, but has audit trail -->
|
|
79
|
+
<div class="card mb-4" id="section-discussion">
|
|
80
|
+
<div class="card-header bg-white">
|
|
81
|
+
<h5 class="mb-0">
|
|
82
|
+
<i class="bi bi-clock-history"></i> Activity Log
|
|
83
|
+
</h5>
|
|
84
|
+
</div>
|
|
85
|
+
<div class="card-body p-0">
|
|
86
|
+
<% error.comments.recent_first.each_with_index do |comment, index| %>
|
|
87
|
+
<div class="px-3 py-2 <%= index < error.comments.count - 1 ? 'border-bottom' : '' %>">
|
|
88
|
+
<div class="d-flex justify-content-between align-items-start">
|
|
89
|
+
<div>
|
|
90
|
+
<small class="text-primary fw-bold"><i class="bi bi-person-circle"></i> <%= comment.author_name %></small>
|
|
91
|
+
<small class="text-muted ms-2"><%= comment.formatted_time %></small>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
<small class="text-muted text-break"><%= simple_format(h(comment.body), {}, wrapper_tag: "span") %></small>
|
|
95
|
+
</div>
|
|
96
|
+
<% end %>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
66
99
|
<% end %>
|
|
@@ -1,38 +1,84 @@
|
|
|
1
1
|
<% if RailsErrorDashboard.configuration.enable_issue_tracking %>
|
|
2
2
|
<div class="card mb-4" id="issue-tracking">
|
|
3
|
-
<div class="card-header bg-white">
|
|
3
|
+
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
|
4
4
|
<h5 class="mb-0">
|
|
5
5
|
<i class="bi bi-link-45deg me-2"></i>
|
|
6
6
|
Issue Tracker
|
|
7
7
|
</h5>
|
|
8
|
+
<% if @error.external_issue_url.present? %>
|
|
9
|
+
<a href="<%= @error.external_issue_url %>" target="_blank" rel="noopener" class="btn btn-sm btn-outline-primary">
|
|
10
|
+
<i class="bi bi-box-arrow-up-right me-1"></i> View Issue
|
|
11
|
+
</a>
|
|
12
|
+
<% end %>
|
|
8
13
|
</div>
|
|
9
14
|
<div class="card-body">
|
|
10
15
|
<% if @error.external_issue_url.present? %>
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
<% provider = @error.external_issue_provider %>
|
|
17
|
+
<% icon = case provider
|
|
18
|
+
when "github" then "bi-github"
|
|
19
|
+
when "gitlab" then "bi-gitlab"
|
|
20
|
+
when "codeberg" then "bi-git"
|
|
21
|
+
else "bi-link-45deg"
|
|
22
|
+
end %>
|
|
23
|
+
<% issue = @platform_issue %>
|
|
24
|
+
|
|
25
|
+
<!-- Issue badge + status -->
|
|
26
|
+
<div class="d-flex align-items-center gap-2 mb-3">
|
|
27
|
+
<span class="badge bg-success">
|
|
28
|
+
<i class="bi <%= icon %> me-1"></i>
|
|
29
|
+
<%= provider&.capitalize || "Linked" %> #<%= @error.external_issue_number || "?" %>
|
|
30
|
+
</span>
|
|
31
|
+
<% if issue %>
|
|
32
|
+
<% state_color = issue[:state] == "open" ? "success" : "secondary" %>
|
|
33
|
+
<% state_icon = issue[:state] == "open" ? "bi-circle-fill" : "bi-check-circle-fill" %>
|
|
34
|
+
<span class="badge bg-<%= state_color %>">
|
|
35
|
+
<i class="bi <%= state_icon %> me-1" style="font-size: 0.6em;"></i>
|
|
36
|
+
<%= issue[:state]&.capitalize %>
|
|
24
37
|
</span>
|
|
25
|
-
|
|
26
|
-
View Issue <i class="bi bi-box-arrow-up-right" style="font-size: 0.8em;"></i>
|
|
27
|
-
</a>
|
|
28
|
-
</div>
|
|
29
|
-
<div>
|
|
30
|
-
<a href="<%= @error.external_issue_url %>" target="_blank" rel="noopener" class="btn btn-sm btn-outline-primary me-1">
|
|
31
|
-
<i class="bi bi-chat-dots"></i> Discuss on <%= provider&.capitalize || "Platform" %>
|
|
32
|
-
</a>
|
|
33
|
-
<%= button_to "Unlink", unlink_issue_error_path(@error), method: :post, class: "btn btn-sm btn-outline-danger", data: { confirm: "Remove issue link?" } %>
|
|
34
|
-
</div>
|
|
38
|
+
<% end %>
|
|
35
39
|
</div>
|
|
40
|
+
|
|
41
|
+
<% if issue %>
|
|
42
|
+
<div class="row">
|
|
43
|
+
<!-- Assignees -->
|
|
44
|
+
<div class="col-md-6 mb-2">
|
|
45
|
+
<small class="text-muted d-block mb-1">Assignees</small>
|
|
46
|
+
<% if issue[:assignees].present? && issue[:assignees].any? %>
|
|
47
|
+
<div class="d-flex align-items-center gap-2 flex-wrap">
|
|
48
|
+
<% issue[:assignees].each do |assignee| %>
|
|
49
|
+
<span class="d-inline-flex align-items-center gap-1 border rounded-pill px-2 py-1" style="font-size: 0.85em;">
|
|
50
|
+
<% if assignee[:avatar_url].present? %>
|
|
51
|
+
<img src="<%= assignee[:avatar_url] %>" width="18" height="18" class="rounded-circle">
|
|
52
|
+
<% end %>
|
|
53
|
+
<%= assignee[:login] %>
|
|
54
|
+
</span>
|
|
55
|
+
<% end %>
|
|
56
|
+
</div>
|
|
57
|
+
<% else %>
|
|
58
|
+
<span class="text-muted" style="font-size: 0.85em;">Unassigned</span>
|
|
59
|
+
<% end %>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<!-- Labels -->
|
|
63
|
+
<div class="col-md-6 mb-2">
|
|
64
|
+
<small class="text-muted d-block mb-1">Labels</small>
|
|
65
|
+
<% if issue[:labels].present? && issue[:labels].any? %>
|
|
66
|
+
<div class="d-flex align-items-center gap-1 flex-wrap">
|
|
67
|
+
<% issue[:labels].each do |label| %>
|
|
68
|
+
<% bg_color = label[:color].present? ? "##{label[:color]}" : "#6c757d" %>
|
|
69
|
+
<% text_color = label[:color].present? && label[:color].scan(/../).map { |c| c.to_i(16) }.sum > 382 ? "#000" : "#fff" %>
|
|
70
|
+
<span class="badge rounded-pill" style="background-color: <%= bg_color %>; color: <%= text_color %>; font-size: 0.78em;">
|
|
71
|
+
<%= label[:name] %>
|
|
72
|
+
</span>
|
|
73
|
+
<% end %>
|
|
74
|
+
</div>
|
|
75
|
+
<% else %>
|
|
76
|
+
<span class="text-muted" style="font-size: 0.85em;">No labels</span>
|
|
77
|
+
<% end %>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
<% end %>
|
|
81
|
+
|
|
36
82
|
<% else %>
|
|
37
83
|
<!-- No linked issue — show create/link options -->
|
|
38
84
|
<div class="row">
|
|
@@ -64,4 +110,12 @@
|
|
|
64
110
|
<% end %>
|
|
65
111
|
</div>
|
|
66
112
|
</div>
|
|
113
|
+
|
|
114
|
+
<% if flash[:new_issue_url].present? %>
|
|
115
|
+
<script>
|
|
116
|
+
(function() {
|
|
117
|
+
window.open('<%= j flash[:new_issue_url] %>', '_blank');
|
|
118
|
+
})();
|
|
119
|
+
</script>
|
|
120
|
+
<% end %>
|
|
67
121
|
<% end %>
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
{ id: 'section-similar-errors', icon: 'bi-diagram-3', label: 'Similar' },
|
|
67
67
|
{ id: 'section-co-occurring', icon: 'bi-intersect', label: 'Co-occurring' },
|
|
68
68
|
{ id: 'section-timeline', icon: 'bi-clock-history', label: 'Timeline' },
|
|
69
|
+
{ id: 'issue-tracking', icon: 'bi-link-45deg', label: 'Issue' },
|
|
69
70
|
{ id: 'section-discussion', icon: 'bi-chat-dots', label: 'Discussion' },
|
|
70
71
|
{ id: 'section-error-cascades', icon: 'bi-share', label: 'Cascades' },
|
|
71
72
|
{ id: 'section-occurrence-patterns', icon: 'bi-graph-up', label: 'Patterns' }
|
|
@@ -71,88 +71,92 @@
|
|
|
71
71
|
<% end %>
|
|
72
72
|
</div>
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
<%
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<%=
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
74
|
+
<% unless RailsErrorDashboard.configuration.enable_issue_tracking %>
|
|
75
|
+
<!-- Workflow Status (hidden when issue tracking enabled — platform is source of truth) -->
|
|
76
|
+
<div class="mb-3">
|
|
77
|
+
<small class="metadata-label d-block mb-1">Workflow Status</small>
|
|
78
|
+
<% if error.respond_to?(:status) %>
|
|
79
|
+
<% badge_color = error.status_badge_color %>
|
|
80
|
+
<% text_dark = %w[info warning light].include?(badge_color) ? 'text-dark' : '' %>
|
|
81
|
+
<span class="badge bg-<%= badge_color %> <%= text_dark %> fs-6">
|
|
82
|
+
<%= error.status.titleize %>
|
|
83
|
+
</span>
|
|
84
|
+
<% elsif error.resolved? %>
|
|
85
|
+
<span class="badge bg-success">
|
|
86
|
+
<i class="bi bi-check-circle"></i> Resolved
|
|
87
|
+
</span>
|
|
88
|
+
<% if error.resolved_at.present? %>
|
|
89
|
+
<br>
|
|
90
|
+
<small class="text-muted mt-1 d-block">
|
|
91
|
+
<%= local_time(error.resolved_at, format: :full) %>
|
|
92
|
+
</small>
|
|
93
|
+
<% end %>
|
|
94
|
+
<% else %>
|
|
95
|
+
<span class="badge bg-danger">
|
|
96
|
+
<i class="bi bi-exclamation-circle"></i> Unresolved
|
|
97
|
+
</span>
|
|
98
|
+
<% end %>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<!-- Reopened indicator -->
|
|
102
|
+
<% if error.reopened? %>
|
|
103
|
+
<div class="mb-3">
|
|
104
|
+
<small class="metadata-label d-block mb-1">Reopened</small>
|
|
105
|
+
<span class="badge bg-warning text-dark">
|
|
106
|
+
<i class="bi bi-arrow-counterclockwise"></i> Reopened
|
|
107
|
+
</span>
|
|
88
108
|
<br>
|
|
89
109
|
<small class="text-muted mt-1 d-block">
|
|
90
|
-
<%= local_time(error.
|
|
110
|
+
<%= local_time(error.reopened_at, format: :full) %>
|
|
91
111
|
</small>
|
|
92
|
-
|
|
93
|
-
<% else %>
|
|
94
|
-
<span class="badge bg-danger">
|
|
95
|
-
<i class="bi bi-exclamation-circle"></i> Unresolved
|
|
96
|
-
</span>
|
|
112
|
+
</div>
|
|
97
113
|
<% end %>
|
|
98
|
-
</div>
|
|
99
114
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
<div class="d-flex align-items-center justify-content-between">
|
|
120
|
-
<div>
|
|
121
|
-
<i class="bi bi-person-fill text-primary"></i>
|
|
122
|
-
<strong><%= error.assigned_to %></strong>
|
|
123
|
-
<% if error.assigned_at.present? %>
|
|
124
|
-
<br>
|
|
125
|
-
<small class="text-muted">
|
|
126
|
-
<%= local_time_ago(error.assigned_at) %>
|
|
127
|
-
</small>
|
|
115
|
+
<!-- Assignment -->
|
|
116
|
+
<% if error.respond_to?(:assigned_to) %>
|
|
117
|
+
<div class="mb-3">
|
|
118
|
+
<small class="metadata-label d-block mb-1">Assigned To</small>
|
|
119
|
+
<% if error.assigned? %>
|
|
120
|
+
<div class="d-flex align-items-center justify-content-between">
|
|
121
|
+
<div>
|
|
122
|
+
<i class="bi bi-person-fill text-primary"></i>
|
|
123
|
+
<strong><%= error.assigned_to %></strong>
|
|
124
|
+
<% if error.assigned_at.present? %>
|
|
125
|
+
<br>
|
|
126
|
+
<small class="text-muted">
|
|
127
|
+
<%= local_time_ago(error.assigned_at) %>
|
|
128
|
+
</small>
|
|
129
|
+
<% end %>
|
|
130
|
+
</div>
|
|
131
|
+
<%= button_to unassign_error_path(error), method: :post, class: "btn btn-sm btn-outline-secondary",
|
|
132
|
+
data: { turbo_confirm: "Remove assignment?" } do %>
|
|
133
|
+
<i class="bi bi-x"></i>
|
|
128
134
|
<% end %>
|
|
129
135
|
</div>
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
<i class="bi bi-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
<button type="button" class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#assignModal">
|
|
137
|
-
<i class="bi bi-person-plus"></i> Assign
|
|
138
|
-
</button>
|
|
139
|
-
<% end %>
|
|
140
|
-
</div>
|
|
136
|
+
<% else %>
|
|
137
|
+
<button type="button" class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#assignModal">
|
|
138
|
+
<i class="bi bi-person-plus"></i> Assign
|
|
139
|
+
</button>
|
|
140
|
+
<% end %>
|
|
141
|
+
</div>
|
|
141
142
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
143
|
+
<!-- Priority -->
|
|
144
|
+
<div class="mb-3">
|
|
145
|
+
<small class="metadata-label d-block mb-1">Priority</small>
|
|
146
|
+
<% if error.respond_to?(:priority_level) %>
|
|
147
|
+
<span class="badge bg-<%= error.priority_color %> fs-6">
|
|
148
|
+
<%= error.priority_label %>
|
|
149
|
+
</span>
|
|
150
|
+
<button type="button" class="btn btn-sm btn-outline-secondary ms-2" data-bs-toggle="modal" data-bs-target="#priorityModal">
|
|
151
|
+
<i class="bi bi-pencil"></i>
|
|
152
|
+
</button>
|
|
153
|
+
<% end %>
|
|
154
|
+
</div>
|
|
155
|
+
<% end %>
|
|
156
|
+
<% end %>
|
|
154
157
|
|
|
155
|
-
|
|
158
|
+
<!-- Snooze (always visible — no platform equivalent) -->
|
|
159
|
+
<% if error.respond_to?(:assigned_to) %>
|
|
156
160
|
<div class="mb-3">
|
|
157
161
|
<small class="metadata-label d-block mb-1">Snooze</small>
|
|
158
162
|
<% if error.respond_to?(:snoozed?) && error.snoozed? %>
|
|
@@ -40,14 +40,16 @@
|
|
|
40
40
|
<i class="bi bi-bell-slash"></i> Muted
|
|
41
41
|
</button>
|
|
42
42
|
<% end %>
|
|
43
|
-
<%
|
|
44
|
-
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
<
|
|
50
|
-
|
|
43
|
+
<% unless RailsErrorDashboard.configuration.enable_issue_tracking %>
|
|
44
|
+
<% if @error.resolved? %>
|
|
45
|
+
<span class="badge bg-success fs-6">
|
|
46
|
+
<i class="bi bi-check-circle"></i> Resolved
|
|
47
|
+
</span>
|
|
48
|
+
<% else %>
|
|
49
|
+
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#resolveModal">
|
|
50
|
+
<i class="bi bi-check-circle"></i> Mark as Resolved
|
|
51
|
+
</button>
|
|
52
|
+
<% end %>
|
|
51
53
|
<% end %>
|
|
52
54
|
</div>
|
|
53
55
|
</div>
|
data/config/routes.rb
CHANGED
|
@@ -89,6 +89,29 @@ module RailsErrorDashboard
|
|
|
89
89
|
end
|
|
90
90
|
end
|
|
91
91
|
|
|
92
|
+
def fetch_issue(number:)
|
|
93
|
+
response = http_get(
|
|
94
|
+
"#{@api_url}/repos/#{@repo}/issues/#{number}",
|
|
95
|
+
auth_headers
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
if response[:status] == 200
|
|
99
|
+
data = response[:body]
|
|
100
|
+
success_response(
|
|
101
|
+
state: data["state"],
|
|
102
|
+
title: data["title"],
|
|
103
|
+
assignees: (data["assignees"] || []).map { |a|
|
|
104
|
+
{ login: a["login"], avatar_url: a["avatar_url"] }
|
|
105
|
+
},
|
|
106
|
+
labels: (data["labels"] || []).map { |l|
|
|
107
|
+
{ name: l["name"], color: l["color"] }
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
else
|
|
111
|
+
error_response("Codeberg API error (#{response[:status]})")
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
92
115
|
private
|
|
93
116
|
|
|
94
117
|
def auth_headers
|
|
@@ -84,6 +84,29 @@ module RailsErrorDashboard
|
|
|
84
84
|
end
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
+
def fetch_issue(number:)
|
|
88
|
+
response = http_get(
|
|
89
|
+
"#{@api_url}/repos/#{@repo}/issues/#{number}",
|
|
90
|
+
auth_headers
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if response[:status] == 200
|
|
94
|
+
data = response[:body]
|
|
95
|
+
success_response(
|
|
96
|
+
state: data["state"],
|
|
97
|
+
title: data["title"],
|
|
98
|
+
assignees: (data["assignees"] || []).map { |a|
|
|
99
|
+
{ login: a["login"], avatar_url: a["avatar_url"] }
|
|
100
|
+
},
|
|
101
|
+
labels: (data["labels"] || []).map { |l|
|
|
102
|
+
{ name: l["name"], color: l["color"] }
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
else
|
|
106
|
+
error_response("GitHub API error (#{response[:status]})")
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
87
110
|
private
|
|
88
111
|
|
|
89
112
|
def auth_headers
|
|
@@ -88,6 +88,29 @@ module RailsErrorDashboard
|
|
|
88
88
|
end
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
+
def fetch_issue(number:)
|
|
92
|
+
response = http_get(
|
|
93
|
+
"#{@api_url}/projects/#{@encoded_repo}/issues/#{number}",
|
|
94
|
+
auth_headers
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if response[:status] == 200
|
|
98
|
+
data = response[:body]
|
|
99
|
+
success_response(
|
|
100
|
+
state: data["state"],
|
|
101
|
+
title: data["title"],
|
|
102
|
+
assignees: (data["assignees"] || []).map { |a|
|
|
103
|
+
{ login: a["username"], avatar_url: a["avatar_url"] }
|
|
104
|
+
},
|
|
105
|
+
labels: (data["labels"] || []).map { |l|
|
|
106
|
+
{ name: l, color: nil }
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
else
|
|
110
|
+
error_response("GitLab API error (#{response[:status]})")
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
91
114
|
private
|
|
92
115
|
|
|
93
116
|
def auth_headers
|
|
@@ -96,6 +96,12 @@ module RailsErrorDashboard
|
|
|
96
96
|
raise NotImplementedError
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
+
# Fetch issue details (status, assignees, labels)
|
|
100
|
+
# @return [Hash] { state:, assignees: [...], labels: [...], title:, success: true } or { success: false }
|
|
101
|
+
def fetch_issue(number:)
|
|
102
|
+
raise NotImplementedError
|
|
103
|
+
end
|
|
104
|
+
|
|
99
105
|
private
|
|
100
106
|
|
|
101
107
|
def http_post(uri, body, headers = {})
|
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.5.
|
|
4
|
+
version: 0.5.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Anjan Jagirdar
|
|
@@ -483,9 +483,9 @@ metadata:
|
|
|
483
483
|
changelog_uri: https://github.com/AnjanJ/rails_error_dashboard/blob/main/CHANGELOG.md
|
|
484
484
|
documentation_uri: https://AnjanJ.github.io/rails_error_dashboard
|
|
485
485
|
bug_tracker_uri: https://github.com/AnjanJ/rails_error_dashboard/issues
|
|
486
|
-
funding_uri: https://
|
|
486
|
+
funding_uri: https://github.com/sponsors/AnjanJ
|
|
487
487
|
post_install_message: "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n
|
|
488
|
-
\ Rails Error Dashboard v0.5.
|
|
488
|
+
\ Rails Error Dashboard v0.5.9\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\U0001F195
|
|
489
489
|
First time? Quick start:\n rails generate rails_error_dashboard:install\n rails
|
|
490
490
|
db:migrate\n # Add to config/routes.rb:\n mount RailsErrorDashboard::Engine
|
|
491
491
|
=> '/error_dashboard'\n\n\U0001F504 Upgrading from v0.1.x?\n rails db:migrate\n
|