rails_pulse 0.1.1 → 0.1.2

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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +72 -176
  3. data/Rakefile +77 -2
  4. data/app/assets/stylesheets/rails_pulse/application.css +0 -12
  5. data/app/controllers/concerns/chart_table_concern.rb +21 -4
  6. data/app/controllers/concerns/response_range_concern.rb +6 -3
  7. data/app/controllers/concerns/time_range_concern.rb +5 -10
  8. data/app/controllers/concerns/zoom_range_concern.rb +1 -1
  9. data/app/controllers/rails_pulse/application_controller.rb +8 -4
  10. data/app/controllers/rails_pulse/dashboard_controller.rb +12 -0
  11. data/app/controllers/rails_pulse/queries_controller.rb +65 -50
  12. data/app/controllers/rails_pulse/requests_controller.rb +24 -12
  13. data/app/controllers/rails_pulse/routes_controller.rb +59 -24
  14. data/app/helpers/rails_pulse/application_helper.rb +0 -1
  15. data/app/helpers/rails_pulse/chart_formatters.rb +3 -3
  16. data/app/helpers/rails_pulse/chart_helper.rb +6 -2
  17. data/app/helpers/rails_pulse/status_helper.rb +10 -4
  18. data/app/javascript/rails_pulse/controllers/index_controller.js +117 -33
  19. data/app/javascript/rails_pulse/controllers/pagination_controller.js +17 -27
  20. data/app/jobs/rails_pulse/backfill_summaries_job.rb +41 -0
  21. data/app/jobs/rails_pulse/summary_job.rb +53 -0
  22. data/app/models/rails_pulse/dashboard/charts/average_response_time.rb +28 -7
  23. data/app/models/rails_pulse/dashboard/charts/p95_response_time.rb +18 -22
  24. data/app/models/rails_pulse/dashboard/tables/slow_queries.rb +18 -7
  25. data/app/models/rails_pulse/dashboard/tables/slow_routes.rb +34 -41
  26. data/app/models/rails_pulse/operation.rb +1 -1
  27. data/app/models/rails_pulse/queries/cards/average_query_times.rb +47 -23
  28. data/app/models/rails_pulse/queries/cards/execution_rate.rb +33 -26
  29. data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +34 -45
  30. data/app/models/rails_pulse/queries/charts/average_query_times.rb +23 -97
  31. data/app/models/rails_pulse/queries/tables/index.rb +74 -0
  32. data/app/models/rails_pulse/query.rb +1 -0
  33. data/app/models/rails_pulse/requests/charts/average_response_times.rb +23 -84
  34. data/app/models/rails_pulse/route.rb +1 -6
  35. data/app/models/rails_pulse/routes/cards/average_response_times.rb +45 -23
  36. data/app/models/rails_pulse/routes/cards/error_rate_per_route.rb +38 -45
  37. data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +34 -47
  38. data/app/models/rails_pulse/routes/cards/request_count_totals.rb +30 -25
  39. data/app/models/rails_pulse/routes/charts/average_response_times.rb +23 -100
  40. data/app/models/rails_pulse/routes/tables/index.rb +57 -40
  41. data/app/models/rails_pulse/summary.rb +143 -0
  42. data/app/services/rails_pulse/summary_service.rb +199 -0
  43. data/app/views/layouts/rails_pulse/application.html.erb +4 -4
  44. data/app/views/rails_pulse/components/_empty_state.html.erb +11 -0
  45. data/app/views/rails_pulse/components/_metric_card.html.erb +10 -24
  46. data/app/views/rails_pulse/dashboard/index.html.erb +54 -36
  47. data/app/views/rails_pulse/queries/_show_table.html.erb +1 -1
  48. data/app/views/rails_pulse/queries/_table.html.erb +10 -12
  49. data/app/views/rails_pulse/queries/index.html.erb +41 -34
  50. data/app/views/rails_pulse/queries/show.html.erb +38 -31
  51. data/app/views/rails_pulse/requests/_operations.html.erb +32 -26
  52. data/app/views/rails_pulse/requests/_table.html.erb +1 -3
  53. data/app/views/rails_pulse/requests/index.html.erb +42 -34
  54. data/app/views/rails_pulse/routes/_table.html.erb +13 -13
  55. data/app/views/rails_pulse/routes/index.html.erb +43 -35
  56. data/app/views/rails_pulse/routes/show.html.erb +42 -35
  57. data/config/initializers/rails_pulse.rb +0 -12
  58. data/db/migrate/20241222000001_create_rails_pulse_summaries.rb +54 -0
  59. data/db/rails_pulse_schema.rb +121 -0
  60. data/lib/generators/rails_pulse/install_generator.rb +41 -4
  61. data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +60 -0
  62. data/lib/generators/rails_pulse/templates/rails_pulse.rb +0 -12
  63. data/lib/rails_pulse/configuration.rb +0 -11
  64. data/lib/rails_pulse/engine.rb +0 -1
  65. data/lib/rails_pulse/version.rb +1 -1
  66. data/lib/tasks/rails_pulse.rake +58 -0
  67. data/public/rails-pulse-assets/rails-pulse.css +1 -1
  68. data/public/rails-pulse-assets/rails-pulse.css.map +1 -1
  69. data/public/rails-pulse-assets/rails-pulse.js +1 -1
  70. data/public/rails-pulse-assets/rails-pulse.js.map +3 -3
  71. data/public/rails-pulse-assets/search.svg +43 -0
  72. metadata +27 -11
  73. data/app/controllers/rails_pulse/caches_controller.rb +0 -115
  74. data/app/helpers/rails_pulse/cached_component_helper.rb +0 -73
  75. data/app/models/rails_pulse/component_cache_key.rb +0 -33
  76. data/app/views/rails_pulse/caches/show.html.erb +0 -9
  77. data/db/migrate/20250227235904_create_routes.rb +0 -12
  78. data/db/migrate/20250227235915_create_requests.rb +0 -19
  79. data/db/migrate/20250228000000_create_queries.rb +0 -14
  80. data/db/migrate/20250228000056_create_operations.rb +0 -24
  81. data/lib/rails_pulse/migration.rb +0 -29
@@ -1,11 +1,12 @@
1
1
  <%= render 'rails_pulse/components/breadcrumbs' %>
2
2
 
3
- <div class="row">
4
- <%= cached_component(component: "metric_card", id: "average_response_times", context: "routes", class: "grid-item block") %>
5
- <%= cached_component(component: "metric_card", id: "percentile_response_times", context: "routes", class: "grid-item block") %>
6
- <%= cached_component(component: "metric_card", id: "request_count_totals", context: "routes", class: "grid-item block") %>
7
- <%= cached_component(component: "metric_card", id: "error_rate_per_route", context: "routes", class: "grid-item block") %>
8
- </div>
3
+ <% unless turbo_frame_request? %>
4
+ <div class="row">
5
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @average_query_times_metric_card } %>
6
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @percentile_query_times_metric_card } %>
7
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @execution_rate_metric_card } %>
8
+ </div>
9
+ <% end %>
9
10
 
10
11
  <div
11
12
  class="row"
@@ -16,12 +17,12 @@
16
17
  <%= render 'rails_pulse/components/panel', { title: 'Average Query Time', } do %>
17
18
  <%= search_form_for @ransack_query, url: queries_path, class: "flex items-center justify-between gap mb-4" do |form| %>
18
19
  <div class="flex items-center grow gap">
19
- <%= form.select :occurred_at_range,
20
+ <%= form.select :period_start_range,
20
21
  RailsPulse::QueriesController::TIME_RANGE_OPTIONS,
21
22
  { selected: @selected_time_range },
22
23
  { class: "input" }
23
24
  %>
24
- <%= form.select :duration,
25
+ <%= form.select :avg_duration,
25
26
  duration_options(:query),
26
27
  { selected: @selected_response_range },
27
28
  { class: "input" }
@@ -31,33 +32,39 @@
31
32
  </div>
32
33
  <% end %>
33
34
 
34
- <% if @chart_data.present? %>
35
- <div
36
- class="chart-container chart-container--slim"
37
- data-rails-pulse--index-target="chart"
38
- >
39
- <%= bar_chart(
40
- @chart_data,
41
- code: false,
42
- id: "average_query_times_chart",
43
- height: "100%",
44
- options: bar_chart_options(
45
- units: "ms",
46
- zoom: true,
47
- chart_start: 0,
48
- chart_end: @chart_data.length - 1,
49
- xaxis_formatter: @xaxis_formatter,
50
- tooltip_formatter: @tooltip_formatter,
51
- zoom_start: @zoom_start,
52
- zoom_end: @zoom_end,
53
- chart_data: @chart_data
54
- )
55
- ) %>
56
- </div>
57
- <% end %>
35
+ <% if @has_data %>
36
+ <% if @chart_data && @chart_data.values.any? { |v| v > 0 } %>
37
+ <div
38
+ class="chart-container chart-container--slim"
39
+ data-rails-pulse--index-target="chart"
40
+ >
41
+ <%= bar_chart(
42
+ @chart_data,
43
+ code: false,
44
+ id: "average_query_times_chart",
45
+ height: "100%",
46
+ options: bar_chart_options(
47
+ units: "ms",
48
+ zoom: true,
49
+ chart_start: 0,
50
+ chart_end: @chart_data.length - 1,
51
+ xaxis_formatter: @xaxis_formatter,
52
+ tooltip_formatter: @tooltip_formatter,
53
+ zoom_start: @zoom_start,
54
+ zoom_end: @zoom_end,
55
+ chart_data: @chart_data
56
+ )
57
+ ) %>
58
+ </div>
59
+ <% end %>
58
60
 
59
- <%= turbo_frame_tag :queries_index_table, data: { rails_pulse__index_target: "indexTable" } do %>
60
- <%= render 'rails_pulse/queries/table' %>
61
+ <%= turbo_frame_tag :queries_index_table, data: { rails_pulse__index_target: "indexTable" } do %>
62
+ <%= render 'rails_pulse/queries/table' %>
63
+ <% end %>
64
+ <% else %>
65
+ <%= render 'rails_pulse/components/empty_state',
66
+ title: 'No query data found for the selected filters.',
67
+ description: 'Try adjusting your time range or filters to see results.' %>
61
68
  <% end %>
62
69
  <% end %>
63
70
  </div>
@@ -1,10 +1,12 @@
1
1
  <%= render 'rails_pulse/components/breadcrumbs' %>
2
2
 
3
- <div class="row">
4
- <%= cached_component(component: "metric_card", id: "average_query_times", context: "query_#{@query.id}", class: "grid-item block") %>
5
- <%= cached_component(component: "metric_card", id: "request_count_totals", context: "query_#{@query.id}", class: "grid-item block") %>
6
- <%= cached_component(component: "metric_card", id: "execution_rate", context: "query_#{@query.id}", class: "grid-item block") %>
7
- </div>
3
+ <% unless turbo_frame_request? %>
4
+ <div class="row">
5
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @average_query_times_metric_card } %>
6
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @percentile_query_times_metric_card } %>
7
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @execution_rate_metric_card } %>
8
+ </div>
9
+ <% end %>
8
10
 
9
11
  <div class="mb-4">
10
12
  <%= render 'rails_pulse/components/code_panel', { title: 'Normalized SQL', value: @query.normalized_sql } %>
@@ -14,13 +16,12 @@
14
16
  class="row"
15
17
  data-controller="rails-pulse--index"
16
18
  data-rails-pulse--index-chart-id-value="query_responses_chart"
17
- data-rails-pulse--index-occurred-at-param-value="occurred_at"
18
19
  >
19
20
  <div class="grid-item">
20
21
  <%= render 'rails_pulse/components/panel', { title: 'Query Responses' } do %>
21
22
  <%= search_form_for @ransack_query, url: query_path(@query), class: "flex items-center justify-between gap mb-4" do |form| %>
22
23
  <div class="flex items-center grow gap">
23
- <%= form.select :occurred_at_range,
24
+ <%= form.select :period_start_range,
24
25
  RailsPulse::RoutesController::TIME_RANGE_OPTIONS,
25
26
  { selected: @selected_time_range },
26
27
  { class: "input" }
@@ -35,30 +36,36 @@
35
36
  </div>
36
37
  <% end %>
37
38
 
38
- <% if @chart_data.present? %>
39
- <div
40
- class="chart-container chart-container--slim"
41
- data-rails-pulse--index-target="chart"
42
- >
43
- <%= bar_chart(
44
- @chart_data,
45
- code: false,
46
- id: "query_responses_chart",
47
- height: "100%",
48
- options: bar_chart_options(
49
- units: "ms",
50
- zoom: true,
51
- chart_start: 0,
52
- chart_end: @chart_data.length - 1,
53
- xaxis_formatter: @xaxis_formatter,
54
- tooltip_formatter: @tooltip_formatter
55
- )
56
- ) %>
57
- </div>
58
- <% end %>
39
+ <% if @has_data %>
40
+ <% if @chart_data && @chart_data.values.any? { |v| v > 0 } %>
41
+ <div
42
+ class="chart-container chart-container--slim"
43
+ data-rails-pulse--index-target="chart"
44
+ >
45
+ <%= bar_chart(
46
+ @chart_data,
47
+ code: false,
48
+ id: "query_responses_chart",
49
+ height: "100%",
50
+ options: bar_chart_options(
51
+ units: "ms",
52
+ zoom: true,
53
+ chart_start: 0,
54
+ chart_end: @chart_data.length - 1,
55
+ xaxis_formatter: @xaxis_formatter,
56
+ tooltip_formatter: @tooltip_formatter
57
+ )
58
+ ) %>
59
+ </div>
60
+ <% end %>
59
61
 
60
- <%= turbo_frame_tag :index_table do %>
61
- <%= render 'rails_pulse/queries/show_table' %>
62
+ <%= turbo_frame_tag :index_table do %>
63
+ <%= render 'rails_pulse/queries/show_table' %>
64
+ <% end %>
65
+ <% else %>
66
+ <%= render 'rails_pulse/components/empty_state',
67
+ title: 'No query responses found for the selected filters.',
68
+ description: 'Try adjusting your time range or filters to see results.' %>
62
69
  <% end %>
63
70
  <% end %>
64
71
  </div>
@@ -76,7 +83,7 @@
76
83
  <tbody>
77
84
  <% @query.operations.group_by(&:codebase_location).each do |codebase_location, operations| %>
78
85
  <tr>
79
- <td><%= link_to codebase_location, '#' %></td>
86
+ <td><%= codebase_location %></td>
80
87
  <td><%= (operations.sum(&:duration) / operations.count).round(2) %></td>
81
88
  <td><%= operations.count %></td>
82
89
  </tr>
@@ -1,21 +1,22 @@
1
1
  <div class="row">
2
2
  <div class="grid-item">
3
3
  <%= render 'rails_pulse/components/panel', { title: 'Event Sequence ' } do %>
4
- <table class="table operations-table">
5
- <thead>
6
- <tr>
7
- <th class="w-8"></th>
8
- <th class="operations-label-cell">Operation</th>
9
- <th class="operations-duration-cell">Duration</th>
10
- <th class="w-20">Impact</th>
11
- <th class="w-16"></th>
12
- <th class="operations-event-cell">Timeline</th>
13
- <th class="w-16">Actions</th>
14
- </tr>
15
- </thead>
16
- <tbody>
17
- <% total_request_duration = @request.duration.to_f %>
18
- <% @operation_timeline.bars.each_with_index do |bar, index| %>
4
+ <% if operations.any? %>
5
+ <table class="table operations-table">
6
+ <thead>
7
+ <tr>
8
+ <th class="w-8"></th>
9
+ <th class="operations-label-cell">Operation</th>
10
+ <th class="operations-duration-cell">Duration</th>
11
+ <th class="w-20">Impact</th>
12
+ <th class="w-16"></th>
13
+ <th class="operations-event-cell">Timeline</th>
14
+ <th class="w-16">Actions</th>
15
+ </tr>
16
+ </thead>
17
+ <tbody>
18
+ <% total_request_duration = @request.duration.to_f %>
19
+ <% @operation_timeline.bars.each_with_index do |bar, index| %>
19
20
  <tbody data-controller="rails-pulse--expandable-row">
20
21
  <!-- Main operation row -->
21
22
  <tr
@@ -30,33 +31,33 @@
30
31
  <%= rails_pulse_icon('chevron-right', width: '16', class: 'transition-transform duration-200') %>
31
32
  </div>
32
33
  </td>
33
-
34
+
34
35
  <td class="operations-label-cell">
35
36
  <span class="text-link" style="color: <%= event_color(bar.operation.operation_type) %>;">
36
37
  <%= html_escape(bar.operation.label) %>
37
38
  </span>
38
39
  </td>
39
-
40
+
40
41
  <td class="whitespace-nowrap">
41
42
  <%= bar.duration.round(2) %> ms
42
43
  </td>
43
-
44
+
44
45
  <td class="whitespace-nowrap">
45
46
  <% impact_percentage = total_request_duration > 0 ? (bar.operation.duration / total_request_duration * 100).round(1) : 0 %>
46
47
  <%= impact_percentage %>%
47
48
  </td>
48
-
49
+
49
50
  <td class="whitespace-nowrap text-center">
50
51
  <%= operation_status_indicator(bar.operation) %>
51
52
  </td>
52
-
53
+
53
54
  <td class="operations-event-cell">
54
55
  <div
55
56
  class="operations-event"
56
57
  style="left: <%= bar.left_pct %>%; width: <%= bar.width_pct %>%; background-color: <%= event_color(bar.operation.operation_type) %>;">
57
58
  </div>
58
59
  </td>
59
-
60
+
60
61
  <td>
61
62
  <%= link_to rails_pulse_icon('eye', width: '16', class: 'inline-block mbi-2'), operation_path(bar.operation), title: 'View details', data: { turbo_frame: "_top" } %>
62
63
  <% if bar.operation.operation_type == "sql" && bar.operation.query.present? %>
@@ -64,9 +65,9 @@
64
65
  <% end %>
65
66
  </td>
66
67
  </tr>
67
-
68
+
68
69
  <!-- Expandable details row -->
69
- <tr
70
+ <tr
70
71
  class="operation-details-row hidden"
71
72
  data-rails-pulse--expandable-row-target="details"
72
73
  data-operation-id="<%= bar.operation.id %>"
@@ -77,9 +78,14 @@
77
78
  </td>
78
79
  </tr>
79
80
  </tbody>
80
- <% end %>
81
- </tbody>
82
- </table>
81
+ <% end %>
82
+ </tbody>
83
+ </table>
84
+ <% else %>
85
+ <%= render 'rails_pulse/components/empty_state',
86
+ title: 'No operations found for this request.',
87
+ description: 'This request may not have had any tracked operations.' %>
88
+ <% end %>
83
89
  <% end %>
84
90
  </div>
85
91
  </div>
@@ -3,8 +3,7 @@
3
3
  columns << { field: :route_path, label: 'Route', class: 'w-auto' } if @route.blank?
4
4
 
5
5
  columns += [
6
- { field: :occurred_at, label: 'Timestamp', class: (@route.present? ? 'w-auto' : 'w-40') },
7
- { field: :duration, label: 'Duration', class: 'w-24' },
6
+ { field: :duration, label: 'Response Time', class: 'w-24' },
8
7
  { field: :status, label: 'HTTP Status', class: 'w-20' },
9
8
  { field: :status_indicator, label: 'Status', class: 'w-16' }
10
9
  ]
@@ -19,7 +18,6 @@
19
18
  <% if @route.blank? %>
20
19
  <td class="whitespace-nowrap"><%= link_to route_request.route.path_and_method, request_path(route_request), data: { turbo_frame: '_top' } %></td>
21
20
  <% end %>
22
- <td class="whitespace-nowrap"><%= link_to human_readable_occurred_at(route_request.occurred_at), request_path(route_request), data: { turbo_frame: '_top' } %></td>
23
21
  <td class="whitespace-nowrap"><%= route_request.duration.round(2) %> ms</td>
24
22
  <td class="whitespace-nowrap"><%= route_request.status %></td>
25
23
  <td class="whitespace-nowrap text-center"><%= request_status_indicator(route_request.duration) %></td>
@@ -1,11 +1,13 @@
1
1
  <%= render 'rails_pulse/components/breadcrumbs' %>
2
2
 
3
- <div class="row">
4
- <%= cached_component(component: "metric_card", id: "average_response_times", context: "requests", class: "grid-item block") %>
5
- <%= cached_component(component: "metric_card", id: "percentile_response_times", context: "requests", class: "grid-item block") %>
6
- <%= cached_component(component: "metric_card", id: "request_count_totals", context: "requests", class: "grid-item block") %>
7
- <%= cached_component(component: "metric_card", id: "error_rate_per_route", context: "requests", class: "grid-item block") %>
8
- </div>
3
+ <% unless turbo_frame_request? %>
4
+ <div class="row">
5
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @average_response_times_metric_card } %>
6
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @percentile_response_times_metric_card } %>
7
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @request_count_totals_metric_card } %>
8
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @error_rate_per_route_metric_card } %>
9
+ </div>
10
+ <% end %>
9
11
 
10
12
  <div
11
13
  class="row"
@@ -16,12 +18,12 @@
16
18
  <%= render 'rails_pulse/components/panel', { title: 'Average Response Time', } do %>
17
19
  <%= search_form_for @ransack_query, url: requests_path, class: "flex items-center justify-between gap mb-4" do |form| %>
18
20
  <div class="flex items-center grow gap">
19
- <%= form.select :occurred_at_range,
21
+ <%= form.select :period_start_range,
20
22
  RailsPulse::RequestsController::TIME_RANGE_OPTIONS,
21
23
  { selected: @selected_time_range },
22
24
  { class: "input" }
23
25
  %>
24
- <%= form.select :duration,
26
+ <%= form.select :avg_duration,
25
27
  duration_options(:request),
26
28
  { selected: @selected_response_range },
27
29
  { class: "input" }
@@ -31,33 +33,39 @@
31
33
  </div>
32
34
  <% end %>
33
35
 
34
- <% if @chart_data.present? %>
35
- <div
36
- class="chart-container chart-container--slim"
37
- data-rails-pulse--index-target="chart"
38
- >
39
- <%= bar_chart(
40
- @chart_data,
41
- code: false,
42
- id: "average_response_times_chart",
43
- height: "100%",
44
- options: bar_chart_options(
45
- units: "ms",
46
- zoom: true,
47
- chart_start: 0,
48
- chart_end: @chart_data.length - 1,
49
- xaxis_formatter: @xaxis_formatter,
50
- tooltip_formatter: @tooltip_formatter,
51
- zoom_start: @zoom_start,
52
- zoom_end: @zoom_end,
53
- chart_data: @chart_data
54
- )
55
- ) %>
56
- </div>
57
- <% end %>
36
+ <% if @has_data %>
37
+ <% if @chart_data && @chart_data.values.any? { |v| v > 0 } %>
38
+ <div
39
+ class="chart-container chart-container--slim"
40
+ data-rails-pulse--index-target="chart"
41
+ >
42
+ <%= bar_chart(
43
+ @chart_data,
44
+ code: false,
45
+ id: "average_response_times_chart",
46
+ height: "100%",
47
+ options: bar_chart_options(
48
+ units: "ms",
49
+ zoom: true,
50
+ chart_start: 0,
51
+ chart_end: @chart_data.length - 1,
52
+ xaxis_formatter: @xaxis_formatter,
53
+ tooltip_formatter: @tooltip_formatter,
54
+ zoom_start: @zoom_start,
55
+ zoom_end: @zoom_end,
56
+ chart_data: @chart_data
57
+ )
58
+ ) %>
59
+ </div>
60
+ <% end %>
58
61
 
59
- <%= turbo_frame_tag :requests_index_table, data: { rails_pulse__index_target: "indexTable" } do %>
60
- <%= render 'rails_pulse/requests/table' %>
62
+ <%= turbo_frame_tag :requests_index_table, data: { rails_pulse__index_target: "indexTable" } do %>
63
+ <%= render 'rails_pulse/requests/table' %>
64
+ <% end %>
65
+ <% else %>
66
+ <%= render 'rails_pulse/components/empty_state',
67
+ title: 'No request data found for the selected filters.',
68
+ description: 'Try adjusting your time range or filters to see results.' %>
61
69
  <% end %>
62
70
  <% end %>
63
71
  </div>
@@ -1,26 +1,26 @@
1
1
  <% columns = [
2
- { field: :path, label: 'Route', class: 'w-auto' },
3
- { field: :average_response_time_ms, label: 'Average Response Time', class: 'w-36' },
4
- { field: :max_response_time_ms, label: 'Max Response Time', class: 'w-32' },
5
- { field: :request_count, label: 'Requests', class: 'w-24' },
2
+ { field: :route_path, label: 'Route', class: 'w-auto' },
3
+ { field: :avg_duration_sort, label: 'Average Response Time', class: 'w-36' },
4
+ { field: :max_duration_sort, label: 'Max Response Time', class: 'w-32' },
5
+ { field: :count_sort, label: 'Requests', class: 'w-24' },
6
6
  { field: :requests_per_minute, label: 'Requests Per Minute', class: 'w-28' },
7
7
  { field: :error_rate_percentage, label: 'Error Rate (%)', class: 'w-20' },
8
- { field: :status_indicator, label: 'Status', class: 'w-16' }
8
+ { field: :status_indicator, label: 'Status', class: 'w-16', sortable: false }
9
9
  ] %>
10
10
 
11
11
  <table class="table mbs-4">
12
12
  <%= render "rails_pulse/components/table_head", columns: columns %>
13
13
 
14
14
  <tbody>
15
- <% @table_data.each do |route| %>
15
+ <% @table_data.each do |summary| %>
16
16
  <tr>
17
- <td class="whitespace-nowrap"><%= link_to route.path_and_method, route, data: { turbo_frame: '_top' } %></td>
18
- <td class="whitespace-nowrap"><%= route.average_response_time_ms.to_i %> ms</td>
19
- <td class="whitespace-nowrap"><%= route.max_response_time_ms.to_i %> ms</td>
20
- <td class="whitespace-nowrap"><%= number_with_delimiter route.request_count %></td>
21
- <td class="whitespace-nowrap"><%= route.requests_per_minute < 1 ? '< 1' : route.requests_per_minute.round(2) %></td>
22
- <td class="whitespace-nowrap"><%= route.error_rate_percentage %>%</td>
23
- <td class="whitespace-nowrap text-center"><%= route_status_indicator(route.status_indicator) %></td>
17
+ <td class="whitespace-nowrap"><%= link_to "#{summary.path} #{summary.route_method}", route_path(summary.route_id), data: { turbo_frame: '_top' } %></td>
18
+ <td class="whitespace-nowrap"><%= summary.avg_duration.to_i %> ms</td>
19
+ <td class="whitespace-nowrap"><%= summary.max_duration.to_i %> ms</td>
20
+ <td class="whitespace-nowrap"><%= number_with_delimiter summary.count %></td>
21
+ <td class="whitespace-nowrap"><%= summary.count < 1 ? '< 1' : (summary.count / 60.0).round(2) %></td>
22
+ <td class="whitespace-nowrap"><%= ((summary.error_count.to_f / summary.count) * 100).round(2) %>%</td>
23
+ <td class="whitespace-nowrap text-center"><%= route_status_indicator(summary.avg_duration >= 500 ? 1 : 0) %></td>
24
24
  </tr>
25
25
  <% end %>
26
26
  </tbody>
@@ -1,11 +1,13 @@
1
1
  <%= render 'rails_pulse/components/breadcrumbs' %>
2
2
 
3
- <div class="row">
4
- <%= cached_component(component: "metric_card", id: "average_response_times", context: "routes", class: "grid-item block") %>
5
- <%= cached_component(component: "metric_card", id: "percentile_response_times", context: "routes", class: "grid-item block") %>
6
- <%= cached_component(component: "metric_card", id: "request_count_totals", context: "routes", class: "grid-item block") %>
7
- <%= cached_component(component: "metric_card", id: "error_rate_per_route", context: "routes", class: "grid-item block") %>
8
- </div>
3
+ <% unless turbo_frame_request? %>
4
+ <div class="row">
5
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @average_query_times_metric_card } %>
6
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @percentile_response_times_metric_card } %>
7
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @request_count_totals_metric_card } %>
8
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @error_rate_per_route_metric_card } %>
9
+ </div>
10
+ <% end %>
9
11
 
10
12
  <div
11
13
  class="row"
@@ -16,13 +18,13 @@
16
18
  <%= render 'rails_pulse/components/panel', { title: 'Average Response Time', } do %>
17
19
  <%= search_form_for @ransack_query, url: routes_path, class: "flex items-center justify-between gap mb-4" do |form| %>
18
20
  <div class="flex items-center grow gap">
19
- <%= form.search_field :path_cont, placeholder: "Filter by route", autocomplete: "off", class: "input", style: "max-inline-size: 250px" %>
20
- <%= form.select :occurred_at_range,
21
+ <%= form.search_field :route_path_cont, placeholder: "Filter by route", autocomplete: "off", class: "input", style: "max-inline-size: 250px" %>
22
+ <%= form.select :period_start_range,
21
23
  RailsPulse::RoutesController::TIME_RANGE_OPTIONS,
22
24
  { selected: @selected_time_range },
23
25
  { class: "input" }
24
26
  %>
25
- <%= form.select :duration,
27
+ <%= form.select :avg_duration,
26
28
  duration_options(:route),
27
29
  { selected: @selected_response_range },
28
30
  { class: "input" }
@@ -32,33 +34,39 @@
32
34
  </div>
33
35
  <% end %>
34
36
 
35
- <% if @chart_data.present? %>
36
- <div
37
- class="chart-container chart-container--slim"
38
- data-rails-pulse--index-target="chart"
39
- >
40
- <%= bar_chart(
41
- @chart_data,
42
- code: false,
43
- id: "average_response_times_chart",
44
- height: "100%",
45
- options: bar_chart_options(
46
- units: "ms",
47
- zoom: true,
48
- chart_start: 0,
49
- chart_end: @chart_data.length - 1,
50
- xaxis_formatter: @xaxis_formatter,
51
- tooltip_formatter: @tooltip_formatter,
52
- zoom_start: @zoom_start,
53
- zoom_end: @zoom_end,
54
- chart_data: @chart_data
55
- )
56
- ) %>
57
- </div>
58
- <% end %>
37
+ <% if @has_data %>
38
+ <% if @chart_data && @chart_data.values.any? { |v| v > 0 } %>
39
+ <div
40
+ class="chart-container chart-container--slim"
41
+ data-rails-pulse--index-target="chart"
42
+ >
43
+ <%= bar_chart(
44
+ @chart_data,
45
+ code: false,
46
+ id: "average_response_times_chart",
47
+ height: "100%",
48
+ options: bar_chart_options(
49
+ units: "ms",
50
+ zoom: true,
51
+ chart_start: 0,
52
+ chart_end: @chart_data.length - 1,
53
+ xaxis_formatter: @xaxis_formatter,
54
+ tooltip_formatter: @tooltip_formatter,
55
+ zoom_start: @zoom_start,
56
+ zoom_end: @zoom_end,
57
+ chart_data: @chart_data
58
+ )
59
+ ) %>
60
+ </div>
61
+ <% end %>
59
62
 
60
- <%= turbo_frame_tag :routes_index_table, data: { rails_pulse__index_target: "indexTable" } do %>
61
- <%= render 'rails_pulse/routes/table' %>
63
+ <%= turbo_frame_tag :routes_index_table, data: { rails_pulse__index_target: "indexTable" } do %>
64
+ <%= render 'rails_pulse/routes/table' %>
65
+ <% end %>
66
+ <% else %>
67
+ <%= render 'rails_pulse/components/empty_state',
68
+ title: 'No route data found for the selected filters.',
69
+ description: 'Try adjusting your time range or filters to see results.' %>
62
70
  <% end %>
63
71
  <% end %>
64
72
  </div>