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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dc2ad274b6cf17149a3d60eecb3c6cde4280379e8c74c870be26530641267f8b
4
- data.tar.gz: b8bb6d9e448631bb294663b73d33379c7da33e0cba5ab97d79949b1860070cc3
3
+ metadata.gz: 18faae83cf7bba23409732000c1cb230ba7ef067f23a454084445f754df4be0d
4
+ data.tar.gz: ad82e112e173f8247cf31b214b040ba82e4035040bbdf14d8c53a5782db36bb1
5
5
  SHA512:
6
- metadata.gz: 2f1b87689dc7d3be38a036ead3d2e63236265ebe6a7135503e6cff3231a97f87b5203bc9f6b8da86e23735401f26616688adbe43bf2e225f6f3fce6cd808f0d3
7
- data.tar.gz: da68e7b4b68b14f9a8dd349f7c5ccd0704844a4da2d77f97556e9cf3ebda1577d59ba54acb844a3d8de59704c16bf494065e6d23d83abfee07b6ca1a28b7afd8
6
+ metadata.gz: 0d0032aeb56ccc44a8c8966da338f75c37e71002534fd0bbf3e019cfe0582df37638e50863e8d4a77212731c1190de214d521521df2e986a23d36a575748d5d9
7
+ data.tar.gz: 4862e358faa4a87f1091e33cf08c0e654a7e959b8af551a1223e3c9d098d383d503342aa8c2fb4d66f20c95fc4864276df631be6b93bcdd22ef50269516ef997
data/README.md CHANGED
@@ -4,6 +4,7 @@
4
4
  [![Downloads](https://img.shields.io/gem/dt/rails_error_dashboard)](https://rubygems.org/gems/rails_error_dashboard)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
  [![Tests](https://github.com/AnjanJ/rails_error_dashboard/workflows/Tests/badge.svg)](https://github.com/AnjanJ/rails_error_dashboard/actions)
7
+ [![Sponsor](https://img.shields.io/badge/Sponsor-GitHub%20Sponsors-ea4aaa?logo=githubsponsors)](https://github.com/sponsors/AnjanJ)
7
8
  [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-support-yellow?logo=buymeacoffee)](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 buying me a coffee. It keeps the project going and lets me know people are finding it useful.
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://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" width="200"></a>
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>&nbsp;&nbsp;<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: #{result[:issue_url]}"
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
- <% has_comments = error.respond_to?(:comments) && error.comments.any? %>
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 || has_comments || issue_tracking_enabled %>
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 has_comments %>
11
- <span class="badge bg-secondary ms-2"><%= error.comments.count %></span>
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 has_linked_issue %>
17
- <!-- Platform discussion link -->
18
- <% provider = error.external_issue_provider&.capitalize || "Platform" %>
19
- <% icon = case error.external_issue_provider
20
- when "github" then "bi-github"
21
- when "gitlab" then "bi-gitlab"
22
- when "codeberg" then "bi-git"
23
- else "bi-chat-dots"
24
- end %>
25
- <div class="text-center py-3 mb-3">
26
- <a href="<%= error.external_issue_url %>" target="_blank" rel="noopener" class="btn btn-primary">
27
- <i class="bi <%= icon %> me-1"></i>
28
- Discuss on <%= provider %> #<%= error.external_issue_number || "?" %>
29
- <i class="bi bi-box-arrow-up-right ms-1" style="font-size: 0.8em;"></i>
30
- </a>
31
- </div>
32
- <% elsif issue_tracking_enabled %>
33
- <div class="text-center py-3 mb-3">
34
- <p class="text-muted small mb-0">
35
- <i class="bi bi-link-45deg"></i>
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 has_comments %>
42
- <!-- Audit trail — read-only workflow comments (snooze, mute, status changes) -->
43
- <% if has_linked_issue || issue_tracking_enabled %>
44
- <hr class="my-3">
45
- <h6 class="text-muted mb-3"><i class="bi bi-clock-history"></i> Activity Log</h6>
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' : '' %> pb-3 mb-3">
49
- <div class="d-flex justify-content-between align-items-start mb-2">
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
- <strong class="text-primary">
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
- <p class="mb-0 text-break"><%= simple_format(h(comment.body), {}, wrapper_tag: "span") %></p>
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
- <!-- Linked issue display -->
12
- <div class="d-flex align-items-center justify-content-between">
13
- <div>
14
- <% provider = @error.external_issue_provider %>
15
- <% icon = case provider
16
- when "github" then "bi-github"
17
- when "gitlab" then "bi-gitlab"
18
- when "codeberg" then "bi-git"
19
- else "bi-link-45deg"
20
- end %>
21
- <span class="badge bg-success me-2">
22
- <i class="bi <%= icon %> me-1"></i>
23
- <%= provider&.capitalize || "Linked" %> #<%= @error.external_issue_number || "?" %>
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
- <a href="<%= @error.external_issue_url %>" target="_blank" rel="noopener" class="text-decoration-none">
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
- <!-- Phase 3: Workflow Status -->
75
- <div class="mb-3">
76
- <small class="metadata-label d-block mb-1">Workflow Status</small>
77
- <% if error.respond_to?(:status) %>
78
- <% badge_color = error.status_badge_color %>
79
- <% text_dark = %w[info warning light].include?(badge_color) ? 'text-dark' : '' %>
80
- <span class="badge bg-<%= badge_color %> <%= text_dark %> fs-6">
81
- <%= error.status.titleize %>
82
- </span>
83
- <% elsif error.resolved? %>
84
- <span class="badge bg-success">
85
- <i class="bi bi-check-circle"></i> Resolved
86
- </span>
87
- <% if error.resolved_at.present? %>
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.resolved_at, format: :full) %>
110
+ <%= local_time(error.reopened_at, format: :full) %>
91
111
  </small>
92
- <% end %>
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
- <!-- Reopened indicator -->
101
- <% if error.reopened? %>
102
- <div class="mb-3">
103
- <small class="metadata-label d-block mb-1">Reopened</small>
104
- <span class="badge bg-warning text-dark">
105
- <i class="bi bi-arrow-counterclockwise"></i> Reopened
106
- </span>
107
- <br>
108
- <small class="text-muted mt-1 d-block">
109
- <%= local_time(error.reopened_at, format: :full) %>
110
- </small>
111
- </div>
112
- <% end %>
113
-
114
- <!-- Phase 3: Assignment -->
115
- <% if error.respond_to?(:assigned_to) %>
116
- <div class="mb-3">
117
- <small class="metadata-label d-block mb-1">Assigned To</small>
118
- <% if error.assigned? %>
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
- <%= button_to unassign_error_path(error), method: :post, class: "btn btn-sm btn-outline-secondary",
131
- data: { turbo_confirm: "Remove assignment?" } do %>
132
- <i class="bi bi-x"></i>
133
- <% end %>
134
- </div>
135
- <% else %>
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
- <!-- Phase 3: Priority -->
143
- <div class="mb-3">
144
- <small class="metadata-label d-block mb-1">Priority</small>
145
- <% if error.respond_to?(:priority_level) %>
146
- <span class="badge bg-<%= error.priority_color %> fs-6">
147
- <%= error.priority_label %>
148
- </span>
149
- <button type="button" class="btn btn-sm btn-outline-secondary ms-2" data-bs-toggle="modal" data-bs-target="#priorityModal">
150
- <i class="bi bi-pencil"></i>
151
- </button>
152
- <% end %>
153
- </div>
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
- <!-- Phase 3: Snooze -->
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
- <% if @error.resolved? %>
44
- <span class="badge bg-success fs-6">
45
- <i class="bi bi-check-circle"></i> Resolved
46
- </span>
47
- <% else %>
48
- <button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#resolveModal">
49
- <i class="bi bi-check-circle"></i> Mark as Resolved
50
- </button>
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
@@ -23,7 +23,6 @@ RailsErrorDashboard::Engine.routes.draw do
23
23
  post :update_status
24
24
  post :create_issue
25
25
  post :link_issue
26
- post :unlink_issue
27
26
  end
28
27
  collection do
29
28
  get :analytics
@@ -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 = {})
@@ -1,3 +1,3 @@
1
1
  module RailsErrorDashboard
2
- VERSION = "0.5.8"
2
+ VERSION = "0.5.9"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_error_dashboard
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.8
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://buymeacoffee.com/anjanj
486
+ funding_uri: https://github.com/sponsors/AnjanJ
487
487
  post_install_message: "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n
488
- \ Rails Error Dashboard v0.5.8\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\U0001F195
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