rails_error_dashboard 0.1.3 → 0.1.5

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.
@@ -146,6 +146,22 @@ module RailsErrorDashboard
146
146
  @resolution_rate = analytics[:resolution_rate]
147
147
  @mobile_errors = analytics[:mobile_errors]
148
148
  @api_errors = analytics[:api_errors]
149
+
150
+ # Get recurring issues data
151
+ recurring = Queries::RecurringIssues.call(days)
152
+ @recurring_data = recurring
153
+
154
+ # Get release correlation data
155
+ correlation = Queries::ErrorCorrelation.new(days: days)
156
+ @errors_by_version = correlation.errors_by_version
157
+ @problematic_releases = correlation.problematic_releases
158
+ @release_comparison = calculate_release_comparison
159
+
160
+ # Get MTTR data
161
+ mttr_data = Queries::MttrStats.call(days)
162
+ @mttr_stats = mttr_data
163
+ @overall_mttr = mttr_data[:overall_mttr]
164
+ @mttr_by_platform = mttr_data[:mttr_by_platform]
149
165
  end
150
166
 
151
167
  def platform_comparison
@@ -219,8 +235,32 @@ module RailsErrorDashboard
219
235
  @platform_specific_errors = correlation.platform_specific_errors
220
236
  end
221
237
 
238
+ def settings
239
+ @config = RailsErrorDashboard.configuration
240
+ end
241
+
222
242
  private
223
243
 
244
+ def calculate_release_comparison
245
+ return {} if @errors_by_version.empty? || @errors_by_version.count < 2
246
+
247
+ versions_sorted = @errors_by_version.sort_by { |_, data| data[:last_seen] || Time.at(0) }.reverse
248
+ latest = versions_sorted.first
249
+ previous = versions_sorted.second
250
+
251
+ return {} if latest.nil? || previous.nil?
252
+
253
+ {
254
+ latest_version: latest[0],
255
+ latest_count: latest[1][:count],
256
+ latest_critical: latest[1][:critical_count],
257
+ previous_version: previous[0],
258
+ previous_count: previous[1][:count],
259
+ previous_critical: previous[1][:critical_count],
260
+ change_percentage: previous[1][:count] > 0 ? ((latest[1][:count] - previous[1][:count]).to_f / previous[1][:count] * 100).round(1) : 0.0
261
+ }
262
+ end
263
+
224
264
  def filter_params
225
265
  {
226
266
  error_type: params[:error_type],
@@ -228,11 +268,16 @@ module RailsErrorDashboard
228
268
  platform: params[:platform],
229
269
  search: params[:search],
230
270
  severity: params[:severity],
271
+ timeframe: params[:timeframe],
272
+ frequency: params[:frequency],
231
273
  # Phase 3: Workflow filter params
232
274
  status: params[:status],
233
275
  assigned_to: params[:assigned_to],
234
276
  priority_level: params[:priority_level],
235
- hide_snoozed: params[:hide_snoozed]
277
+ hide_snoozed: params[:hide_snoozed],
278
+ # Sorting params
279
+ sort_by: params[:sort_by],
280
+ sort_direction: params[:sort_direction]
236
281
  }
237
282
  end
238
283
 
@@ -55,5 +55,43 @@ module RailsErrorDashboard
55
55
  "var(--text-color)"
56
56
  end
57
57
  end
58
+
59
+ # Returns the current user name for filtering "My Errors"
60
+ # Uses configured dashboard username or system username
61
+ # @return [String] Current user identifier
62
+ def current_user_name
63
+ RailsErrorDashboard.configuration.dashboard_username || ENV["USER"] || "unknown"
64
+ end
65
+
66
+ # Generates a sortable column header link
67
+ # @param label [String] The column label to display
68
+ # @param column [String] The column name to sort by
69
+ # @return [String] HTML safe link with sort indicator
70
+ def sortable_header(label, column)
71
+ current_sort = params[:sort_by]
72
+ current_direction = params[:sort_direction] || "desc"
73
+
74
+ # Determine new direction: if clicking same column, toggle; otherwise default to desc
75
+ new_direction = if current_sort == column
76
+ current_direction == "asc" ? "desc" : "asc"
77
+ else
78
+ "desc"
79
+ end
80
+
81
+ # Choose icon based on current state
82
+ icon = if current_sort == column
83
+ current_direction == "asc" ? "▲" : "▼"
84
+ else
85
+ "⇅" # Unsorted indicator
86
+ end
87
+
88
+ # Preserve existing filter params while adding sort params
89
+ link_params = params.permit!.to_h.merge(sort_by: column, sort_direction: new_direction)
90
+
91
+ link_to errors_path(link_params), class: "text-decoration-none" do
92
+ content_tag(:span, "#{label} ", class: current_sort == column ? "fw-bold" : "") +
93
+ content_tag(:span, icon, class: "text-muted small")
94
+ end
95
+ end
58
96
  end
59
97
  end
@@ -133,6 +133,11 @@ module RailsErrorDashboard
133
133
  SystemStackError
134
134
  SignalException
135
135
  ActiveRecord::StatementInvalid
136
+ LoadError
137
+ SyntaxError
138
+ ActiveRecord::ConnectionNotEstablished
139
+ Redis::ConnectionError
140
+ OpenSSL::SSL::SSLError
136
141
  ].freeze
137
142
 
138
143
  HIGH_SEVERITY_ERROR_TYPES = %w[
@@ -141,6 +146,11 @@ module RailsErrorDashboard
141
146
  TypeError
142
147
  NoMethodError
143
148
  NameError
149
+ ZeroDivisionError
150
+ FloatDomainError
151
+ IndexError
152
+ KeyError
153
+ RangeError
144
154
  ].freeze
145
155
 
146
156
  MEDIUM_SEVERITY_ERROR_TYPES = %w[
@@ -148,6 +158,10 @@ module RailsErrorDashboard
148
158
  Timeout::Error
149
159
  Net::ReadTimeout
150
160
  Net::OpenTimeout
161
+ ActiveRecord::RecordNotUnique
162
+ JSON::ParserError
163
+ CSV::MalformedCSVError
164
+ Errno::ECONNREFUSED
151
165
  ].freeze
152
166
 
153
167
  # Find existing error by hash or create new one
@@ -1,16 +1,54 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
- <title>Rails error dashboard</title>
4
+ <title>Rails Error Dashboard</title>
5
5
  <%= csrf_meta_tags %>
6
6
  <%= csp_meta_tag %>
7
7
 
8
8
  <%= yield :head %>
9
9
 
10
10
  <%= stylesheet_link_tag "rails_error_dashboard/application", media: "all" %>
11
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
12
+ <script src="https://cdn.jsdelivr.net/npm/chartkick@5.0.1/dist/chartkick.min.js"></script>
11
13
  </head>
12
14
  <body>
13
15
 
16
+ <!-- Navigation -->
17
+ <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
18
+ <div class="container-fluid">
19
+ <%= link_to "Error Dashboard", root_path, class: "navbar-brand" %>
20
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
21
+ <span class="navbar-toggler-icon"></span>
22
+ </button>
23
+ <div class="collapse navbar-collapse" id="navbarNav">
24
+ <ul class="navbar-nav me-auto">
25
+ <li class="nav-item">
26
+ <%= link_to overview_path, class: "nav-link #{controller_name == 'errors' && action_name == 'overview' ? 'active' : ''}" do %>
27
+ <i class="bi bi-speedometer2"></i> Dashboard
28
+ <% end %>
29
+ </li>
30
+ <li class="nav-item">
31
+ <%= link_to errors_path, class: "nav-link #{controller_name == 'errors' && action_name == 'index' ? 'active' : ''}" do %>
32
+ <i class="bi bi-list-ul"></i> Errors
33
+ <% end %>
34
+ </li>
35
+ <li class="nav-item">
36
+ <%= link_to analytics_errors_path, class: "nav-link #{controller_name == 'errors' && action_name == 'analytics' ? 'active' : ''}" do %>
37
+ <i class="bi bi-graph-up"></i> Analytics
38
+ <% end %>
39
+ </li>
40
+ </ul>
41
+ <ul class="navbar-nav">
42
+ <li class="nav-item">
43
+ <a class="nav-link" href="#" id="theme-toggle">
44
+ <i class="bi bi-moon-stars"></i>
45
+ </a>
46
+ </li>
47
+ </ul>
48
+ </div>
49
+ </div>
50
+ </nav>
51
+
14
52
  <%= yield %>
15
53
 
16
54
  </body>
@@ -1,7 +1,7 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
- <title>Error Dashboard</title>
4
+ <title><%= Rails.application.class.module_parent_name %> - Error Dashboard</title>
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
6
  <%= csrf_meta_tags %>
7
7
  <%= csp_meta_tag %>
@@ -736,7 +736,10 @@
736
736
  <i class="bi bi-list fs-4"></i>
737
737
  </button>
738
738
  <a class="navbar-brand" href="<%= root_path %>">
739
- <i class="bi bi-bug-fill"></i> Error Dashboard
739
+ <i class="bi bi-bug-fill"></i>
740
+ <span class="d-none d-sm-inline"><%= Rails.application.class.module_parent_name %></span>
741
+ <span class="d-none d-md-inline text-white-50 mx-2">|</span>
742
+ <span class="d-none d-md-inline">Error Dashboard</span>
740
743
  </a>
741
744
  </div>
742
745
  <div class="d-flex align-items-center gap-3">
@@ -771,6 +774,11 @@
771
774
  <i class="bi bi-graph-up"></i> Analytics
772
775
  <% end %>
773
776
  </li>
777
+ <li class="nav-item">
778
+ <%= link_to settings_path, class: "nav-link #{request.path == settings_path ? 'active' : ''}" do %>
779
+ <i class="bi bi-gear"></i> Settings
780
+ <% end %>
781
+ </li>
774
782
  </ul>
775
783
 
776
784
  <h6 class="mt-4">QUICK FILTERS</h6>
@@ -780,6 +788,16 @@
780
788
  <i class="bi bi-exclamation-circle"></i> Unresolved
781
789
  <% end %>
782
790
  </li>
791
+ <li class="nav-item">
792
+ <%= link_to errors_path(severity: 'critical'), class: "nav-link" do %>
793
+ <i class="bi bi-exclamation-triangle-fill text-danger"></i> Critical
794
+ <% end %>
795
+ </li>
796
+ <li class="nav-item">
797
+ <%= link_to errors_path(priority_level: 'high'), class: "nav-link" do %>
798
+ <i class="bi bi-flag-fill text-warning"></i> High Priority
799
+ <% end %>
800
+ </li>
783
801
  <li class="nav-item">
784
802
  <%= link_to errors_path(platform: 'iOS'), class: "nav-link" do %>
785
803
  <i class="bi bi-phone"></i> iOS Errors
@@ -143,6 +143,14 @@
143
143
  });
144
144
  </script>
145
145
  </div>
146
+ <div class="card-footer bg-white border-top-0">
147
+ <div class="d-flex gap-2 flex-wrap">
148
+ <small class="text-muted me-2">Quick Links:</small>
149
+ <% @errors_by_platform.keys.each do |platform| %>
150
+ <%= link_to platform, errors_path(platform: platform), class: "btn btn-sm btn-outline-secondary" %>
151
+ <% end %>
152
+ </div>
153
+ </div>
146
154
  </div>
147
155
  </div>
148
156
  </div>
@@ -249,6 +257,7 @@
249
257
  <th>User</th>
250
258
  <th>Error Count</th>
251
259
  <th>Percentage</th>
260
+ <th>Actions</th>
252
261
  </tr>
253
262
  </thead>
254
263
  <tbody>
@@ -269,6 +278,9 @@
269
278
  </div>
270
279
  </div>
271
280
  </td>
281
+ <td>
282
+ <%= link_to "View Errors", errors_path(search: email), class: "btn btn-sm btn-outline-primary" %>
283
+ </td>
272
284
  </tr>
273
285
  <% end %>
274
286
  </tbody>
@@ -330,4 +342,309 @@
330
342
  </div>
331
343
  </div>
332
344
  </div>
345
+
346
+ <!-- Recurring Issues Analysis -->
347
+ <div class="card mb-4">
348
+ <div class="card-header bg-white">
349
+ <h5 class="mb-0"><i class="bi bi-arrow-repeat"></i> Recurring Issues</h5>
350
+ </div>
351
+ <div class="card-body">
352
+ <% if @recurring_data[:high_frequency_errors].any? %>
353
+ <h6 class="text-muted mb-3">High Frequency Errors</h6>
354
+ <div class="table-responsive mb-4">
355
+ <table class="table table-sm">
356
+ <thead>
357
+ <tr>
358
+ <th>Error Type</th>
359
+ <th>Total Occurrences</th>
360
+ <th>Duration</th>
361
+ <th>First Seen</th>
362
+ <th>Last Seen</th>
363
+ <th>Status</th>
364
+ </tr>
365
+ </thead>
366
+ <tbody>
367
+ <% @recurring_data[:high_frequency_errors].first(10).each do |error| %>
368
+ <tr>
369
+ <td><code class="small"><%= error[:error_type] %></code></td>
370
+ <td><span class="badge bg-danger"><%= error[:total_occurrences] %></span></td>
371
+ <td><%= error[:duration_days] %> days</td>
372
+ <td><small class="text-muted"><%= error[:first_seen].strftime("%b %d, %Y") %></small></td>
373
+ <td><small class="text-muted"><%= error[:last_seen].strftime("%b %d, %Y %H:%M") %></small></td>
374
+ <td>
375
+ <% if error[:still_active] %>
376
+ <span class="badge bg-warning">Active</span>
377
+ <% else %>
378
+ <span class="badge bg-secondary">Inactive</span>
379
+ <% end %>
380
+ </td>
381
+ </tr>
382
+ <% end %>
383
+ </tbody>
384
+ </table>
385
+ </div>
386
+ <% else %>
387
+ <p class="text-muted">No high-frequency errors found in the last <%= @days %> days.</p>
388
+ <% end %>
389
+
390
+ <% if @recurring_data[:persistent_errors].any? %>
391
+ <h6 class="text-muted mb-3 mt-4">Persistent Unresolved Errors</h6>
392
+ <div class="alert alert-info">
393
+ <i class="bi bi-info-circle"></i> These errors have been unresolved for more than 7 days.
394
+ </div>
395
+ <div class="table-responsive">
396
+ <table class="table table-sm">
397
+ <thead>
398
+ <tr>
399
+ <th>Error Type</th>
400
+ <th>Message</th>
401
+ <th>Platform</th>
402
+ <th>Count</th>
403
+ <th>Age</th>
404
+ <th>Actions</th>
405
+ </tr>
406
+ </thead>
407
+ <tbody>
408
+ <% @recurring_data[:persistent_errors].first(10).each do |error| %>
409
+ <tr>
410
+ <td><code class="small"><%= error[:error_type] %></code></td>
411
+ <td class="text-muted small"><%= error[:message] %></td>
412
+ <td><%= error[:platform] %></td>
413
+ <td><span class="badge bg-secondary"><%= error[:occurrence_count] %>x</span></td>
414
+ <td>
415
+ <span class="badge bg-warning"><%= error[:age_days] %> days</span>
416
+ </td>
417
+ <td>
418
+ <%= link_to "View", error_path(error[:id]), class: "btn btn-sm btn-outline-primary" %>
419
+ </td>
420
+ </tr>
421
+ <% end %>
422
+ </tbody>
423
+ </table>
424
+ </div>
425
+ <% end %>
426
+ </div>
427
+ </div>
428
+
429
+ <!-- Release Comparison Charts -->
430
+ <% if @errors_by_version.present? && @errors_by_version.count >= 2 %>
431
+ <div class="row g-4 mb-4">
432
+ <!-- Errors by Version Chart -->
433
+ <div class="col-md-8">
434
+ <div class="card">
435
+ <div class="card-header bg-white">
436
+ <h5 class="mb-0"><i class="bi bi-git"></i> Errors by Release Version</h5>
437
+ </div>
438
+ <div class="card-body">
439
+ <div id="errors-by-version-chart"></div>
440
+ <script>
441
+ document.addEventListener('DOMContentLoaded', function() {
442
+ const versionData = <%= raw @errors_by_version.transform_values { |v| v[:count] }.to_json %>;
443
+ const colors = window.getChartColors();
444
+
445
+ new Chartkick.ColumnChart("errors-by-version-chart", versionData, {
446
+ colors: ["#8B5CF6", "#EF4444", "#F59E0B", "#10B981"],
447
+ height: "300px",
448
+ xtitle: "Version",
449
+ ytitle: "Error Count",
450
+ library: window.getChartLibraryOptions()
451
+ });
452
+ });
453
+ </script>
454
+ </div>
455
+ </div>
456
+ </div>
457
+
458
+ <!-- Release Comparison Card -->
459
+ <div class="col-md-4">
460
+ <div class="card">
461
+ <div class="card-header bg-white">
462
+ <h5 class="mb-0">Latest vs Previous</h5>
463
+ </div>
464
+ <div class="card-body">
465
+ <% if @release_comparison.present? %>
466
+ <div class="mb-3">
467
+ <small class="text-muted">Latest Version</small>
468
+ <h4><%= @release_comparison[:latest_version] %></h4>
469
+ <p class="mb-0">
470
+ <span class="badge bg-danger"><%= @release_comparison[:latest_count] %> errors</span>
471
+ <% if @release_comparison[:latest_critical] > 0 %>
472
+ <span class="badge bg-dark ms-1"><%= @release_comparison[:latest_critical] %> critical</span>
473
+ <% end %>
474
+ </p>
475
+ </div>
476
+ <div class="mb-3">
477
+ <small class="text-muted">Previous Version</small>
478
+ <h4><%= @release_comparison[:previous_version] %></h4>
479
+ <p class="mb-0">
480
+ <span class="badge bg-secondary"><%= @release_comparison[:previous_count] %> errors</span>
481
+ <% if @release_comparison[:previous_critical] > 0 %>
482
+ <span class="badge bg-secondary ms-1"><%= @release_comparison[:previous_critical] %> critical</span>
483
+ <% end %>
484
+ </p>
485
+ </div>
486
+ <hr>
487
+ <div>
488
+ <small class="text-muted">Change</small>
489
+ <h4 class="<%= @release_comparison[:change_percentage] > 0 ? 'text-danger' : 'text-success' %>">
490
+ <%= @release_comparison[:change_percentage] > 0 ? '↑' : '↓' %>
491
+ <%= @release_comparison[:change_percentage].abs %>%
492
+ </h4>
493
+ </div>
494
+ <% else %>
495
+ <p class="text-muted">Not enough release data for comparison.</p>
496
+ <% end %>
497
+ </div>
498
+ </div>
499
+ </div>
500
+ </div>
501
+
502
+ <!-- Problematic Releases Alert -->
503
+ <% if @problematic_releases.any? %>
504
+ <div class="alert alert-warning mb-4">
505
+ <h5 class="alert-heading"><i class="bi bi-exclamation-triangle"></i> Problematic Releases Detected</h5>
506
+ <p>The following releases have significantly higher error rates (&gt;2x average):</p>
507
+ <ul class="mb-0">
508
+ <% @problematic_releases.each do |release| %>
509
+ <li>
510
+ <strong><%= release[:version] %></strong>:
511
+ <%= release[:error_count] %> errors
512
+ (<%= release[:critical_count] %> critical,
513
+ +<%= release[:deviation_from_avg] %>% from average)
514
+ </li>
515
+ <% end %>
516
+ </ul>
517
+ </div>
518
+ <% end %>
519
+ <% end %>
520
+
521
+ <!-- MTTR (Mean Time to Resolution) -->
522
+ <% if @mttr_stats[:total_resolved] > 0 %>
523
+ <div class="card mb-4">
524
+ <div class="card-header bg-white">
525
+ <h5 class="mb-0"><i class="bi bi-clock-history"></i> Resolution Performance (MTTR)</h5>
526
+ </div>
527
+ <div class="card-body">
528
+ <div class="row g-3 mb-4">
529
+ <!-- Overall MTTR -->
530
+ <div class="col-md-3">
531
+ <div class="card border-info">
532
+ <div class="card-body text-center">
533
+ <small class="text-muted d-block">Overall MTTR</small>
534
+ <h3 class="text-info mb-0"><%= @mttr_stats[:overall_mttr] %>h</h3>
535
+ <small class="text-muted">Average resolution time</small>
536
+ </div>
537
+ </div>
538
+ </div>
539
+
540
+ <!-- Fastest -->
541
+ <div class="col-md-3">
542
+ <div class="card border-success">
543
+ <div class="card-body text-center">
544
+ <small class="text-muted d-block">Fastest</small>
545
+ <h3 class="text-success mb-0"><%= @mttr_stats[:fastest_resolution] || 0 %>m</h3>
546
+ <small class="text-muted">Best resolution time</small>
547
+ </div>
548
+ </div>
549
+ </div>
550
+
551
+ <!-- Slowest -->
552
+ <div class="col-md-3">
553
+ <div class="card border-danger">
554
+ <div class="card-body text-center">
555
+ <small class="text-muted d-block">Slowest</small>
556
+ <h3 class="text-danger mb-0"><%= @mttr_stats[:slowest_resolution] || 0 %>h</h3>
557
+ <small class="text-muted">Longest resolution time</small>
558
+ </div>
559
+ </div>
560
+ </div>
561
+
562
+ <!-- Total Resolved -->
563
+ <div class="col-md-3">
564
+ <div class="card border-secondary">
565
+ <div class="card-body text-center">
566
+ <small class="text-muted d-block">Total Resolved</small>
567
+ <h3 class="text-secondary mb-0"><%= @mttr_stats[:total_resolved] %></h3>
568
+ <small class="text-muted">In last <%= @days %> days</small>
569
+ </div>
570
+ </div>
571
+ </div>
572
+ </div>
573
+
574
+ <!-- MTTR by Platform -->
575
+ <% if @mttr_by_platform.present? && @mttr_by_platform.any? %>
576
+ <div class="mt-4">
577
+ <h6 class="text-muted mb-3">MTTR by Platform</h6>
578
+ <div id="mttr-by-platform-chart"></div>
579
+ <script>
580
+ document.addEventListener('DOMContentLoaded', function() {
581
+ const colors = window.getChartColors();
582
+ new Chartkick.BarChart("mttr-by-platform-chart",
583
+ <%= raw @mttr_by_platform.to_json %>, {
584
+ suffix: " hours",
585
+ height: "200px",
586
+ colors: ["#8B5CF6"],
587
+ library: window.getChartLibraryOptions()
588
+ });
589
+ });
590
+ </script>
591
+ </div>
592
+ <% end %>
593
+
594
+ <!-- MTTR by Severity -->
595
+ <% if @mttr_stats[:mttr_by_severity].present? && @mttr_stats[:mttr_by_severity].any? %>
596
+ <div class="mt-4">
597
+ <h6 class="text-muted mb-3">MTTR by Severity</h6>
598
+ <div class="table-responsive">
599
+ <table class="table table-sm">
600
+ <thead>
601
+ <tr>
602
+ <th>Severity</th>
603
+ <th>Average Time to Resolution</th>
604
+ <th>Actions</th>
605
+ </tr>
606
+ </thead>
607
+ <tbody>
608
+ <% @mttr_stats[:mttr_by_severity].each do |severity, hours| %>
609
+ <tr>
610
+ <td>
611
+ <span class="badge bg-<%= severity_color(severity) %>">
612
+ <%= severity.to_s.capitalize %>
613
+ </span>
614
+ </td>
615
+ <td><strong><%= hours %> hours</strong></td>
616
+ <td>
617
+ <%= link_to "View", errors_path(severity: severity), class: "btn btn-sm btn-outline-primary" %>
618
+ </td>
619
+ </tr>
620
+ <% end %>
621
+ </tbody>
622
+ </table>
623
+ </div>
624
+ </div>
625
+ <% end %>
626
+
627
+ <!-- MTTR Trend -->
628
+ <% if @mttr_stats[:mttr_trend].present? && @mttr_stats[:mttr_trend].any? %>
629
+ <div class="mt-4">
630
+ <h6 class="text-muted mb-3">MTTR Trend (Weekly)</h6>
631
+ <div id="mttr-trend-chart"></div>
632
+ <script>
633
+ document.addEventListener('DOMContentLoaded', function() {
634
+ const colors = window.getChartColors();
635
+ new Chartkick.LineChart("mttr-trend-chart",
636
+ <%= raw @mttr_stats[:mttr_trend].to_json %>, {
637
+ suffix: " hours",
638
+ height: "250px",
639
+ colors: ["#10B981"],
640
+ curve: false,
641
+ library: window.getChartLibraryOptions()
642
+ });
643
+ });
644
+ </script>
645
+ </div>
646
+ <% end %>
647
+ </div>
648
+ </div>
649
+ <% end %>
333
650
  </div>
@@ -82,6 +82,7 @@
82
82
  <th>Critical Errors</th>
83
83
  <th>Unique Error Types</th>
84
84
  <th>Platforms</th>
85
+ <th>Actions</th>
85
86
  </tr>
86
87
  </thead>
87
88
  <tbody>
@@ -101,6 +102,9 @@
101
102
  <span class="badge bg-secondary text-capitalize me-1"><%= platform %></span>
102
103
  <% end %>
103
104
  </td>
105
+ <td>
106
+ <%= link_to "View", errors_path(search: release[:version]), class: "btn btn-sm btn-outline-primary" %>
107
+ </td>
104
108
  </tr>
105
109
  <% end %>
106
110
  </tbody>
@@ -218,6 +222,7 @@
218
222
  <th>Different Error Types</th>
219
223
  <th>Total Errors</th>
220
224
  <th>Error Types</th>
225
+ <th>Actions</th>
221
226
  </tr>
222
227
  </thead>
223
228
  <tbody>
@@ -238,6 +243,9 @@
238
243
  <span class="text-muted small">+<%= user_data[:error_types].count - 3 %> more</span>
239
244
  <% end %>
240
245
  </td>
246
+ <td>
247
+ <%= link_to "View", errors_path(search: user_data[:user_email]), class: "btn btn-sm btn-outline-primary" %>
248
+ </td>
241
249
  </tr>
242
250
  <% end %>
243
251
  </tbody>