rails_pulse 0.1.0 → 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 +74 -178
  3. data/Rakefile +75 -173
  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 +20 -9
  25. data/app/models/rails_pulse/dashboard/tables/slow_routes.rb +19 -7
  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 +6 -12
  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 +28 -12
  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
@@ -28,12 +28,12 @@
28
28
  <dialog class="sheet sheet--left" style="--sheet-size: 288px;" data-rails-pulse--dialog-target="menu" data-action="click->rails-pulse--dialog#closeOnClickOutside">
29
29
  <div class="sheet__content p-2">
30
30
  <div class="sidebar-menu">
31
- <a class="btn sidebar-menu__button" href="#">
31
+ <%= link_to rails_pulse.root_path, class: "btn sidebar-menu__button" do %>
32
32
  <div class="flex flex-col text-start leading-tight overflow-hidden">
33
33
  <span class="overflow-ellipsis font-semibold">Rails Pulse</span>
34
34
  <span class="overflow-ellipsis text-xs">Open Source</span>
35
35
  </div>
36
- </a>
36
+ <% end %>
37
37
  <div class="sidebar-menu__content">
38
38
  <div class="sidebar-menu__group">
39
39
  <nav class="sidebar-menu__items">
@@ -47,9 +47,9 @@
47
47
  </div>
48
48
 
49
49
  <div class="flex items-center gap">
50
- <a class="flex items-center gap mie-2" href="#">
50
+ <%= link_to rails_pulse.root_path, class: "flex items-center gap mie-2" do %>
51
51
  <span class="font-bold">Rails Pulse</span>
52
- </a>
52
+ <% end %>
53
53
  <nav class="flex items-center gap text-sm text-subtle show@md" style="--column-gap: 1rem">
54
54
  <%= render 'layouts/rails_pulse/menu_items' %>
55
55
  </nav>
@@ -0,0 +1,11 @@
1
+ <div class="flex items-center justify-center pbs-12 pbe-12 pis-6 pie-6 mb-8">
2
+ <div class="flex items-center gap">
3
+ <div class="shrink-0">
4
+ <img src="<%= asset_path('search.svg') %>" class="w-32 h-24 opacity-50" alt="No data available" />
5
+ </div>
6
+ <div class="text-subtle pis-8">
7
+ <p class="text-lg font-semibold mbe-2"><%= title %></p>
8
+ <p class="text-sm"><%= description %></p>
9
+ </div>
10
+ </div>
11
+ </div>
@@ -1,15 +1,15 @@
1
1
  <%
2
- @component_data ||= {}
3
- id ||= @component_data[:id]
4
- context ||= @component_data[:context]
5
- title ||= @component_data[:title]
6
- summary ||= @component_data[:summary]
7
- line_chart_data ||= @component_data[:line_chart_data]
8
- trend_icon ||= @component_data[:trend_icon]
9
- trend_amount ||= @component_data[:trend_amount]
10
- trend_text ||= @component_data[:trend_text]
2
+ # Use the passed data object directly
3
+ id = data[:id]
4
+ context = data[:context]
5
+ title = data[:title]
6
+ summary = data[:summary]
7
+ line_chart_data = data[:line_chart_data]
8
+ trend_icon = data[:trend_icon]
9
+ trend_amount = data[:trend_amount]
10
+ trend_text = data[:trend_text]
11
11
  %>
12
- <div class="grid-item" data-controller="rails-pulse--chart">
12
+ <div class="grid-item" data-controller="rails-pulse--chart" id="<%= id %>">
13
13
  <%= render 'rails_pulse/components/panel', { title: title, card_classes: 'card-compact' } do %>
14
14
  <div class="row mbs-2" style="height: 50px; margin-bottom: 0;">
15
15
  <div class="grid-item">
@@ -35,20 +35,6 @@
35
35
  <%= trend_amount %>
36
36
  <p class="mis-2 text-subtle text-xs"><%= trend_text %></p>
37
37
  </span>
38
- <%
39
- refresh_path = rails_pulse.cache_path(id: id, context: context, refresh: true)
40
- cached_iso = @cached_at ? @cached_at.iso8601 : nil
41
- %>
42
- <%= link_to refresh_path,
43
- data: {
44
- controller: "rails-pulse--timezone",
45
- rails_pulse__timezone_target_frame_value: "#{id}_card",
46
- rails_pulse__timezone_cached_at_value: cached_iso,
47
- turbo_frame: "#{id}_card",
48
- turbo_prefetch: "false"
49
- } do %>
50
- <%= rails_pulse_icon 'refresh-cw', height: "15px", width: "15px" %>
51
- <% end %>
52
38
  </div>
53
39
  </div>
54
40
  <% end %>
@@ -1,64 +1,82 @@
1
1
  <div class="row">
2
- <%= cached_component(component: "metric_card", id: "average_response_times", class: "grid-item block") %>
3
- <%= cached_component(component: "metric_card", id: "percentile_response_times", class: "grid-item block") %>
4
- <%= cached_component(component: "metric_card", id: "request_count_totals", class: "grid-item block") %>
5
- <%= cached_component(component: "metric_card", id: "error_rate_per_route", class: "grid-item block") %>
2
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @average_query_times_metric_card } %>
3
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @percentile_response_times_metric_card } %>
4
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @request_count_totals_metric_card } %>
5
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @error_rate_per_route_metric_card } %>
6
6
  </div>
7
7
 
8
8
  <div class="row">
9
9
  <div class="grid-item" style="height:300px">
10
- <%= cached_component(
11
- component: "panel",
12
- id: "dashboard_average_response_time",
13
- card_classes: 'b-full',
10
+ <%= render 'rails_pulse/components/panel', {
14
11
  title: 'Average Response Time',
12
+ card_classes: 'b-full',
15
13
  help_heading: 'Average Response Time',
16
14
  help_text: 'This panel measures the average server-side response time in milliseconds for all HTTP requests processed by your application. This is the time it takes from the server receiving a request until the request is returned to the client in milliseconds.',
17
- actions: [{ url: routes_path, icon: 'external-link', title: 'View details', data: { turbo_frame: '_top' } }],
18
- refresh_action: true,
19
- content_partial: 'rails_pulse/dashboard/charts/bar_chart')
20
- %>
15
+ actions: [{ url: routes_path, icon: 'external-link', title: 'View details', data: { turbo_frame: '_top' } }]
16
+ } do %>
17
+ <% if @average_response_time_chart_data.present? %>
18
+ <div class="chart-container chart-container--slim">
19
+ <%= bar_chart(
20
+ @average_response_time_chart_data,
21
+ code: false,
22
+ id: "dashboard_average_response_time_chart",
23
+ height: "100%",
24
+ options: bar_chart_options(
25
+ units: "ms"
26
+ )
27
+ ) %>
28
+ </div>
29
+ <% end %>
30
+ <% end %>
21
31
  </div>
22
32
 
23
33
  <div class="grid-item">
24
- <%= cached_component(
25
- component: "panel",
26
- id: "dashboard_p95_response_time",
27
- card_classes: 'b-full',
34
+ <%= render 'rails_pulse/components/panel', {
28
35
  title: 'Query Performance',
36
+ card_classes: 'b-full',
29
37
  help_heading: 'Query Performance',
30
38
  help_text: 'This panel measures the 95th percentile response time in milliseconds for queries in your application. This represents the query performance below which 95% of queries fall, indicating the upper-bound performance for most database operations.',
31
- actions: [{ url: routes_path, icon: 'external-link', title: 'View details', data: { turbo_frame: '_top' } }],
32
- refresh_action: true,
33
- content_partial: 'rails_pulse/dashboard/charts/bar_chart')
34
- %>
39
+ actions: [{ url: queries_path, icon: 'external-link', title: 'View details', data: { turbo_frame: '_top' } }]
40
+ } do %>
41
+ <% if @p95_response_time_chart_data.present? %>
42
+ <div class="chart-container chart-container--slim">
43
+ <%= bar_chart(
44
+ @p95_response_time_chart_data,
45
+ code: false,
46
+ id: "dashboard_p95_response_time_chart",
47
+ height: "100%",
48
+ options: bar_chart_options(
49
+ units: "ms"
50
+ )
51
+ ) %>
52
+ </div>
53
+ <% end %>
54
+ <% end %>
35
55
  </div>
36
56
  </div>
37
57
 
38
58
  <div class="row">
39
59
  <div class="grid-item">
40
- <%= cached_component(
41
- component: "panel",
42
- id: "dashboard_slow_routes",
60
+ <%= render 'rails_pulse/components/panel', {
43
61
  title: 'Slowest Routes This Week',
44
62
  help_heading: 'Slowest Routes',
45
- help_text: 'This panel shows the slowest routes in your application this week compared to last week, including average response time, week-over-week change, and total request count.',
46
- refresh_action: true,
47
- content_partial: 'rails_pulse/dashboard/tables/standard_table',
48
- class: "table-container")
49
- %>
63
+ help_text: 'This panel shows the slowest routes in your application this week, including average response time and total request count.',
64
+ actions: [{ url: routes_path, icon: 'external-link', title: 'View details', data: { turbo_frame: '_top' } }],
65
+ card_classes: 'table-container'
66
+ } do %>
67
+ <%= render 'rails_pulse/components/table', table_data: @slow_routes_table_data %>
68
+ <% end %>
50
69
  </div>
51
70
 
52
71
  <div class="grid-item">
53
- <%= cached_component(
54
- component: "panel",
55
- id: "dashboard_slow_queries",
72
+ <%= render 'rails_pulse/components/panel', {
56
73
  title: 'Slowest Queries This Week',
57
- help_heading: 'Slowest Queries',
74
+ help_heading: 'Slowest Queries',
58
75
  help_text: 'This panel shows the slowest database queries in your application this week, including average execution time and when they were last seen.',
59
- refresh_action: true,
60
- content_partial: 'rails_pulse/dashboard/tables/standard_table',
61
- class: "table-container")
62
- %>
76
+ actions: [{ url: queries_path, icon: 'external-link', title: 'View details', data: { turbo_frame: '_top' } }],
77
+ card_classes: 'table-container'
78
+ } do %>
79
+ <%= render 'rails_pulse/components/table', table_data: @slow_queries_table_data %>
80
+ <% end %>
63
81
  </div>
64
82
  </div>
@@ -1,5 +1,5 @@
1
1
  <% columns = [
2
- { field: :occurred_at, label: 'Occurred At', class: 'w-auto' },
2
+ { field: :occurred_at, label: 'Timestamp', class: 'w-auto' },
3
3
  { field: :duration, label: 'Duration', class: 'w-32'}
4
4
  ] %>
5
5
 
@@ -1,28 +1,26 @@
1
1
  <% columns = [
2
2
  { field: :normalized_sql, label: 'Query', class: 'w-auto' },
3
- { field: :execution_count, label: 'Executions', class: 'w-24' },
4
- { field: :average_query_time_ms, label: 'Avg Time', class: 'w-24' },
5
- { field: :total_time_consumed, label: 'Total Time', class: 'w-28' },
6
- { field: :performance_status, label: 'Status', class: 'w-16' },
7
- { field: :occurred_at, label: 'Last Seen', class: 'w-32' }
3
+ { field: :execution_count_sort, label: 'Executions', class: 'w-24' },
4
+ { field: :avg_duration_sort, label: 'Avg Time', class: 'w-24' },
5
+ { field: :total_time_consumed_sort, label: 'Total Time', class: 'w-28' },
6
+ { field: :performance_status, label: 'Status', class: 'w-16', sortable: false }
8
7
  ] %>
9
8
 
10
9
  <table class="table mbs-4">
11
10
  <%= render "rails_pulse/components/table_head", columns: columns %>
12
11
 
13
12
  <tbody>
14
- <% @table_data.each do |query| %>
13
+ <% @table_data.each do |summary| %>
15
14
  <tr>
16
15
  <td class="whitespace-nowrap">
17
16
  <div>
18
- <%= link_to html_escape(truncate_sql(query.normalized_sql)), query_path(query), data: { turbo_frame: '_top', } %>
17
+ <%= link_to html_escape(truncate_sql(summary.normalized_sql)), query_path(summary.query_id), data: { turbo_frame: '_top', } %>
19
18
  </div>
20
19
  </td>
21
- <td class="whitespace-nowrap"><%= number_with_delimiter query.execution_count %></td>
22
- <td class="whitespace-nowrap"><%= query.average_query_time_ms.to_i %> ms</td>
23
- <td class="whitespace-nowrap"><%= number_with_delimiter query.total_time_consumed.to_i %> ms</td>
24
- <td class="whitespace-nowrap text-center"><%= query_status_indicator(query.average_query_time_ms) %></td>
25
- <td class="whitespace-nowrap"><%= human_readable_occurred_at(query.occurred_at) if query.occurred_at %></td>
20
+ <td class="whitespace-nowrap"><%= number_with_delimiter summary.execution_count %></td>
21
+ <td class="whitespace-nowrap"><%= summary.avg_duration.to_i %> ms</td>
22
+ <td class="whitespace-nowrap"><%= number_with_delimiter summary.total_time_consumed.to_i %> ms</td>
23
+ <td class="whitespace-nowrap text-center"><%= query_status_indicator(summary.avg_duration) %></td>
26
24
  </tr>
27
25
  <% end %>
28
26
  </tbody>
@@ -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>