rails_pulse 0.2.3 → 0.2.5.pre.pre.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 (122) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +270 -13
  3. data/Rakefile +142 -8
  4. data/app/assets/stylesheets/rails_pulse/components/table.css +16 -1
  5. data/app/assets/stylesheets/rails_pulse/components/tags.css +7 -2
  6. data/app/assets/stylesheets/rails_pulse/components/utilities.css +3 -0
  7. data/app/controllers/concerns/chart_table_concern.rb +3 -3
  8. data/app/controllers/rails_pulse/application_controller.rb +20 -3
  9. data/app/controllers/rails_pulse/assets_controller.rb +18 -2
  10. data/app/controllers/rails_pulse/job_runs_controller.rb +37 -0
  11. data/app/controllers/rails_pulse/jobs_controller.rb +80 -0
  12. data/app/controllers/rails_pulse/operations_controller.rb +43 -31
  13. data/app/controllers/rails_pulse/queries_controller.rb +1 -1
  14. data/app/controllers/rails_pulse/requests_controller.rb +3 -8
  15. data/app/controllers/rails_pulse/routes_controller.rb +1 -1
  16. data/app/controllers/rails_pulse/tags_controller.rb +31 -5
  17. data/app/helpers/rails_pulse/application_helper.rb +79 -3
  18. data/app/helpers/rails_pulse/breadcrumbs_helper.rb +15 -1
  19. data/app/helpers/rails_pulse/chart_helper.rb +32 -2
  20. data/app/helpers/rails_pulse/status_helper.rb +16 -0
  21. data/app/helpers/rails_pulse/tags_helper.rb +39 -1
  22. data/app/javascript/rails_pulse/application.js +3 -54
  23. data/app/javascript/rails_pulse/controllers/chart_controller.js +333 -0
  24. data/app/javascript/rails_pulse/controllers/index_controller.js +9 -14
  25. data/app/javascript/rails_pulse/controllers/pagination_controller.js +27 -33
  26. data/app/jobs/rails_pulse/backfill_summaries_job.rb +0 -2
  27. data/app/jobs/rails_pulse/cleanup_job.rb +0 -2
  28. data/app/jobs/rails_pulse/summary_job.rb +0 -2
  29. data/app/models/concerns/rails_pulse/taggable.rb +25 -2
  30. data/app/models/rails_pulse/charts/operations_chart.rb +33 -0
  31. data/app/models/rails_pulse/dashboard/charts/p95_response_time.rb +1 -2
  32. data/app/models/rails_pulse/dashboard/tables/slow_routes.rb +1 -1
  33. data/app/models/rails_pulse/job.rb +85 -0
  34. data/app/models/rails_pulse/job_run.rb +76 -0
  35. data/app/models/rails_pulse/jobs/cards/average_duration.rb +85 -0
  36. data/app/models/rails_pulse/jobs/cards/base.rb +70 -0
  37. data/app/models/rails_pulse/jobs/cards/failure_rate.rb +85 -0
  38. data/app/models/rails_pulse/jobs/cards/total_jobs.rb +74 -0
  39. data/app/models/rails_pulse/jobs/cards/total_runs.rb +48 -0
  40. data/app/models/rails_pulse/operation.rb +16 -3
  41. data/app/models/rails_pulse/queries/cards/average_query_times.rb +3 -3
  42. data/app/models/rails_pulse/queries/cards/execution_rate.rb +1 -1
  43. data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +1 -1
  44. data/app/models/rails_pulse/queries/charts/average_query_times.rb +1 -1
  45. data/app/models/rails_pulse/queries/tables/index.rb +2 -1
  46. data/app/models/rails_pulse/query.rb +10 -1
  47. data/app/models/rails_pulse/requests/charts/average_response_times.rb +1 -1
  48. data/app/models/rails_pulse/routes/cards/average_response_times.rb +3 -2
  49. data/app/models/rails_pulse/routes/cards/error_rate_per_route.rb +1 -1
  50. data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +1 -1
  51. data/app/models/rails_pulse/routes/cards/request_count_totals.rb +1 -1
  52. data/app/models/rails_pulse/routes/charts/average_response_times.rb +1 -1
  53. data/app/models/rails_pulse/routes/tables/index.rb +2 -1
  54. data/app/models/rails_pulse/summary.rb +10 -3
  55. data/app/services/rails_pulse/summary_service.rb +46 -0
  56. data/app/views/layouts/rails_pulse/_menu_items.html.erb +7 -0
  57. data/app/views/layouts/rails_pulse/application.html.erb +23 -0
  58. data/app/views/rails_pulse/components/_active_filters.html.erb +7 -6
  59. data/app/views/rails_pulse/components/_metric_card.html.erb +2 -2
  60. data/app/views/rails_pulse/components/_page_header.html.erb +8 -7
  61. data/app/views/rails_pulse/components/_sparkline_stats.html.erb +1 -1
  62. data/app/views/rails_pulse/components/_table.html.erb +7 -4
  63. data/app/views/rails_pulse/components/_table_pagination.html.erb +8 -6
  64. data/app/views/rails_pulse/csp_test/show.html.erb +1 -1
  65. data/app/views/rails_pulse/dashboard/charts/_bar_chart.html.erb +1 -1
  66. data/app/views/rails_pulse/dashboard/index.html.erb +5 -4
  67. data/app/views/rails_pulse/job_runs/_operations.html.erb +78 -0
  68. data/app/views/rails_pulse/job_runs/index.html.erb +3 -0
  69. data/app/views/rails_pulse/job_runs/show.html.erb +51 -0
  70. data/app/views/rails_pulse/jobs/_job_runs_table.html.erb +35 -0
  71. data/app/views/rails_pulse/jobs/_table.html.erb +43 -0
  72. data/app/views/rails_pulse/jobs/index.html.erb +34 -0
  73. data/app/views/rails_pulse/jobs/show.html.erb +49 -0
  74. data/app/views/rails_pulse/operations/_operation_analysis_application.html.erb +29 -27
  75. data/app/views/rails_pulse/operations/_operation_analysis_view.html.erb +11 -9
  76. data/app/views/rails_pulse/operations/show.html.erb +10 -8
  77. data/app/views/rails_pulse/queries/_table.html.erb +3 -3
  78. data/app/views/rails_pulse/queries/index.html.erb +2 -1
  79. data/app/views/rails_pulse/queries/show.html.erb +2 -1
  80. data/app/views/rails_pulse/requests/_table.html.erb +6 -6
  81. data/app/views/rails_pulse/routes/_table.html.erb +3 -3
  82. data/app/views/rails_pulse/routes/index.html.erb +2 -1
  83. data/app/views/rails_pulse/routes/show.html.erb +3 -2
  84. data/app/views/rails_pulse/tags/_tag_manager.html.erb +7 -14
  85. data/config/brakeman.ignore +213 -0
  86. data/config/brakeman.yml +68 -0
  87. data/config/importmap.rb +1 -1
  88. data/config/initializers/rails_pulse.rb +52 -0
  89. data/config/routes.rb +6 -0
  90. data/db/rails_pulse_migrate/20250113000000_add_jobs_to_rails_pulse.rb +95 -0
  91. data/db/rails_pulse_migrate/20250122000000_add_query_fingerprinting.rb +150 -0
  92. data/db/rails_pulse_migrate/20250202000000_add_index_to_request_uuid.rb +14 -0
  93. data/db/rails_pulse_schema.rb +186 -103
  94. data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +186 -103
  95. data/lib/generators/rails_pulse/templates/migrations/install_rails_pulse_tables.rb +30 -1
  96. data/lib/generators/rails_pulse/templates/rails_pulse.rb +31 -0
  97. data/lib/rails_pulse/active_job_extensions.rb +13 -0
  98. data/lib/rails_pulse/adapters/delayed_job_plugin.rb +25 -0
  99. data/lib/rails_pulse/adapters/sidekiq_middleware.rb +41 -0
  100. data/lib/rails_pulse/cleanup_service.rb +65 -0
  101. data/lib/rails_pulse/configuration.rb +80 -7
  102. data/lib/rails_pulse/engine.rb +29 -28
  103. data/lib/rails_pulse/extensions/active_record.rb +82 -0
  104. data/lib/rails_pulse/job_run_collector.rb +172 -0
  105. data/lib/rails_pulse/middleware/request_collector.rb +20 -43
  106. data/lib/rails_pulse/subscribers/operation_subscriber.rb +11 -5
  107. data/lib/rails_pulse/tracker.rb +82 -0
  108. data/lib/rails_pulse/version.rb +1 -1
  109. data/lib/rails_pulse.rb +2 -0
  110. data/lib/rails_pulse_server.ru +107 -0
  111. data/lib/tasks/rails_pulse_benchmark.rake +382 -0
  112. data/public/rails-pulse-assets/csp-test.js +10 -10
  113. data/public/rails-pulse-assets/rails-pulse-icons.js +3 -2
  114. data/public/rails-pulse-assets/rails-pulse-icons.js.map +1 -1
  115. data/public/rails-pulse-assets/rails-pulse.css +1 -1
  116. data/public/rails-pulse-assets/rails-pulse.css.map +1 -1
  117. data/public/rails-pulse-assets/rails-pulse.js +48 -48
  118. data/public/rails-pulse-assets/rails-pulse.js.map +4 -4
  119. metadata +38 -30
  120. data/app/models/rails_pulse/requests/charts/operations_chart.rb +0 -35
  121. data/config/initializers/rails_charts_csp_patch.rb +0 -75
  122. data/db/migrate/20250930105043_install_rails_pulse_tables.rb +0 -23
@@ -0,0 +1,51 @@
1
+ <%= render 'rails_pulse/components/page_header', taggable: @run %>
2
+
3
+ <div class="row">
4
+ <div class="grid-item">
5
+ <%= render 'rails_pulse/components/panel', title: 'Job Run Details' do %>
6
+ <dl class="descriptive-list mbs-4">
7
+ <dt>Job Class</dt>
8
+ <dd><%= link_to @job.name, job_path(@job) %></dd>
9
+ <dt>Status</dt>
10
+ <dd>
11
+ <%= @run.status.humanize %>
12
+ </dd>
13
+ <dt>Occurred At</dt>
14
+ <dd><%= @run.occurred_at.strftime("%b %d, %Y %l:%M:%S %p") %></dd>
15
+ <% if @run.enqueued_at %>
16
+ <dt>Enqueued At</dt>
17
+ <dd><%= @run.enqueued_at.strftime("%b %d, %Y %l:%M:%S %p") %></dd>
18
+ <% end %>
19
+ <dt>Duration</dt>
20
+ <dd><%= number_to_human(@run.duration || 0, units: { unit: "ms", thousand: "s" }, precision: 2) %></dd>
21
+ <dt>Attempts</dt>
22
+ <dd><%= @run.attempts %></dd>
23
+ <dt>Queue</dt>
24
+ <dd><%= @job.queue_name || 'default' %></dd>
25
+ </dl>
26
+ <% end %>
27
+ </div>
28
+
29
+ <% if @run.error_class.present? %>
30
+ <div class="grid-item">
31
+ <%= render 'rails_pulse/components/panel', title: 'Error Details' do %>
32
+ <dl class="descriptive-list mbs-4">
33
+ <dt>Error Class</dt>
34
+ <dd><%= @run.error_class %></dd>
35
+ <% if @run.error_message.present? %>
36
+ <dt>Error Message</dt>
37
+ <dd><%= @run.error_message %></dd>
38
+ <% end %>
39
+ </dl>
40
+ <% end %>
41
+ </div>
42
+ <% elsif @run.arguments.present? %>
43
+ <div class="grid-item">
44
+ <%= render 'rails_pulse/components/panel', title: 'Arguments' do %>
45
+ <pre class="mbs-4"><%= JSON.pretty_generate(JSON.parse(@run.arguments)) rescue @run.arguments %></pre>
46
+ <% end %>
47
+ </div>
48
+ <% end %>
49
+ </div>
50
+
51
+ <%= render 'operations', operations: @operations %>
@@ -0,0 +1,35 @@
1
+ <% columns = [
2
+ { field: :occurred_at, label: 'Occurred At' },
3
+ { field: :status, label: 'Status', class: 'w-28' },
4
+ { field: :duration, label: 'Duration', class: 'w-28' },
5
+ { field: :attempts, label: 'Attempts', class: 'w-24' },
6
+ { field: nil, label: 'Tags', class: 'w-32', sortable: false }
7
+ ] %>
8
+
9
+ <table class="table mbs-4" data-controller="rails-pulse--table-sort">
10
+ <%= render "rails_pulse/components/table_head", columns: columns %>
11
+
12
+ <tbody>
13
+ <% @table_data.each do |run| %>
14
+ <tr>
15
+ <td>
16
+ <%= link_to run.occurred_at.strftime("%b %d, %Y %l:%M %p"), job_run_path(@job, run), data: { turbo_frame: '_top' } %>
17
+ </td>
18
+ <td>
19
+ <%= run.status.humanize %>
20
+ </td>
21
+ <td>
22
+ <%= number_to_human(run.duration || 0, units: { unit: "ms", thousand: "s" }, precision: 2) %>
23
+ </td>
24
+ <td>
25
+ <%= run.attempts %>
26
+ </td>
27
+ <td>
28
+ <%= display_tag_badges(run) %>
29
+ </td>
30
+ </tr>
31
+ <% end %>
32
+ </tbody>
33
+ </table>
34
+
35
+ <%= render "rails_pulse/components/table_pagination" %>
@@ -0,0 +1,43 @@
1
+ <% columns = [
2
+ { field: :name, label: 'Job Class', class: 'w-48', cell_class: 'truncate-cell' },
3
+ { field: :queue_name, label: 'Queue', class: 'w-32' },
4
+ { field: :runs_count, label: 'Total Runs', class: 'w-28' },
5
+ { field: :failures_count, label: 'Failures', class: 'w-24' },
6
+ { field: :retries_count, label: 'Retries', class: 'w-24' },
7
+ { field: :avg_duration, label: 'Avg Duration', class: 'w-32' },
8
+ { field: nil, label: 'Tags', class: 'w-32', sortable: false }
9
+ ] %>
10
+
11
+ <table class="table table-fixed mbs-4" data-controller="rails-pulse--table-sort">
12
+ <%= render "rails_pulse/components/table_head", columns: columns %>
13
+
14
+ <tbody>
15
+ <% @table_data.each do |job| %>
16
+ <tr>
17
+ <td class="truncate-cell">
18
+ <%= link_to job.name, job_path(job), data: { turbo_frame: '_top' } %>
19
+ </td>
20
+ <td>
21
+ <%= job.queue_name || 'default' %>
22
+ </td>
23
+ <td>
24
+ <%= number_with_delimiter(job.runs_count) %>
25
+ </td>
26
+ <td>
27
+ <%= number_with_delimiter(job.failures_count) %>
28
+ </td>
29
+ <td>
30
+ <%= number_with_delimiter(job.retries_count) %>
31
+ </td>
32
+ <td>
33
+ <%= number_to_human(job.avg_duration || 0, units: { unit: "ms", thousand: "s" }, precision: 2) %>
34
+ </td>
35
+ <td>
36
+ <%= display_tag_badges(job) %>
37
+ </td>
38
+ </tr>
39
+ <% end %>
40
+ </tbody>
41
+ </table>
42
+
43
+ <%= render "rails_pulse/components/table_pagination" %>
@@ -0,0 +1,34 @@
1
+ <%= render 'rails_pulse/components/page_header', show_active_filters: true, show_global_filters: true %>
2
+
3
+ <% unless turbo_frame_request? %>
4
+ <div class="row">
5
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @total_runs_metric_card } %>
6
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @failure_rate_metric_card } %>
7
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @average_duration_metric_card } %>
8
+ </div>
9
+ <% end %>
10
+
11
+ <%= render 'rails_pulse/components/panel', { title: 'Job Classes' } do %>
12
+ <%= search_form_for @ransack_query, url: jobs_path, class: "flex items-center justify-between gap mb-4" do |form| %>
13
+ <div class="flex items-center grow gap">
14
+ <%= form.search_field :name_cont, placeholder: "Filter by job name", autocomplete: "off", class: "input" %>
15
+ <%= form.select :queue_name_eq,
16
+ options_for_select([["All queues", ""]] + @available_queues.map { |q| [q || "default", q] }, params.dig(:q, :queue_name_eq)),
17
+ {},
18
+ { class: "input" }
19
+ %>
20
+ <%= link_to "Reset", jobs_path, class: "btn btn--borderless show@md" if params.has_key?(:q) %>
21
+ <%= form.submit "Search", class: "btn show@sm" %>
22
+ </div>
23
+ <% end %>
24
+
25
+ <% if @jobs.any? %>
26
+ <%= turbo_frame_tag :index_table do %>
27
+ <%= render 'rails_pulse/jobs/table' %>
28
+ <% end %>
29
+ <% else %>
30
+ <%= render 'rails_pulse/components/empty_state',
31
+ title: 'No jobs found',
32
+ description: 'No background jobs have been executed yet.' %>
33
+ <% end %>
34
+ <% end %>
@@ -0,0 +1,49 @@
1
+ <%= render 'rails_pulse/components/page_header', taggable: @job, show_active_filters: true %>
2
+
3
+ <% unless turbo_frame_request? %>
4
+ <div class="row">
5
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @total_runs_metric_card } %>
6
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @failure_rate_metric_card } %>
7
+ <%= render 'rails_pulse/components/metric_card', { class: "grid-item block", data: @average_duration_metric_card } %>
8
+ </div>
9
+ <% end %>
10
+
11
+ <%= render 'rails_pulse/components/panel', { title: 'Job Runs' } do %>
12
+ <%= search_form_for @ransack_query, url: job_path(@job), class: "flex items-center justify-between gap mb-4", data: { controller: "rails-pulse--custom-range" } do |form| %>
13
+ <div class="flex items-center grow gap">
14
+ <%= time_range_selector(form,
15
+ time_range_options: RailsPulse::JobsController::TIME_RANGE_OPTIONS,
16
+ selected_time_range: @selected_time_range,
17
+ mode: :recent_custom
18
+ ) %>
19
+
20
+ <%= form.select :status_eq,
21
+ options_for_select(
22
+ [["All Statuses", ""]] + RailsPulse::JobRun::STATUSES.map { |s| [s.humanize, s] },
23
+ params.dig(:q, :status_eq)
24
+ ),
25
+ {},
26
+ { class: "input" }
27
+ %>
28
+
29
+ <%= form.select :duration_gteq,
30
+ duration_threshold_filter_options(:job),
31
+ { selected: params.dig(:q, :duration_gteq) },
32
+ { class: "input" }
33
+ %>
34
+
35
+ <%= link_to "Reset", job_path(@job), class: "btn btn--borderless show@md" if params.has_key?(:q) %>
36
+ <%= form.submit "Search", class: "btn show@sm" %>
37
+ </div>
38
+ <% end %>
39
+
40
+ <% if @recent_runs.any? %>
41
+ <%= turbo_frame_tag :index_table do %>
42
+ <%= render 'rails_pulse/jobs/job_runs_table' %>
43
+ <% end %>
44
+ <% else %>
45
+ <%= render 'rails_pulse/components/empty_state',
46
+ title: 'No runs found',
47
+ description: 'This job has not been executed yet or no runs match the selected filters.' %>
48
+ <% end %>
49
+ <% end %>
@@ -6,35 +6,37 @@
6
6
  <dd><%= html_escape(action) %></dd>
7
7
  <% end %>
8
8
 
9
- <% total_db_time = operation.request.operations
10
- .where(operation_type: ["sql"])
11
- .sum(:duration) %>
12
- <% total_view_time = operation.request.operations
13
- .where(operation_type: ["template", "partial", "layout", "collection"])
14
- .sum(:duration) %>
15
- <% pure_controller_time = operation.duration - total_db_time - total_view_time %>
9
+ <% if parent %>
10
+ <% total_db_time = parent.operations
11
+ .where(operation_type: ["sql"])
12
+ .sum(:duration) %>
13
+ <% total_view_time = parent.operations
14
+ .where(operation_type: ["template", "partial", "layout", "collection"])
15
+ .sum(:duration) %>
16
+ <% pure_controller_time = operation.duration - total_db_time - total_view_time %>
16
17
 
17
- <% if pure_controller_time > 0 %>
18
- <dt>Pure Logic Time</dt>
19
- <dd>
20
- <%= pure_controller_time.round(1) %>ms
21
- <% if pure_controller_time > 100 %>
22
- - consider optimization
23
- <% end %>
24
- </dd>
25
- <% end %>
18
+ <% if pure_controller_time > 0 %>
19
+ <dt>Pure Logic Time</dt>
20
+ <dd>
21
+ <%= pure_controller_time.round(1) %>ms
22
+ <% if pure_controller_time > 100 %>
23
+ - consider optimization
24
+ <% end %>
25
+ </dd>
26
+ <% end %>
26
27
 
27
- <% db_operations_count = operation.request.operations
28
- .where(operation_type: ["sql"])
29
- .count %>
30
- <% if db_operations_count > 0 %>
31
- <dt>Database Queries</dt>
32
- <dd>
33
- <%= db_operations_count %>
34
- <% if db_operations_count > 10 %>
35
- - potential N+1 queries
36
- <% end %>
37
- </dd>
28
+ <% db_operations_count = parent.operations
29
+ .where(operation_type: ["sql"])
30
+ .count %>
31
+ <% if db_operations_count > 0 %>
32
+ <dt>Database Queries</dt>
33
+ <dd>
34
+ <%= db_operations_count %>
35
+ <% if db_operations_count > 10 %>
36
+ - potential N+1 queries
37
+ <% end %>
38
+ </dd>
39
+ <% end %>
38
40
  <% end %>
39
41
 
40
42
  <% if operation.codebase_location.present? %>
@@ -21,16 +21,18 @@
21
21
  <dd>Partial template - ensure data is pre-loaded in controller</dd>
22
22
  <% end %>
23
23
 
24
- <% view_start = operation.occurred_at %>
25
- <% view_end = operation.occurred_at + operation.duration %>
26
- <% concurrent_db_ops = operation.request.operations
27
- .where(operation_type: ["sql"])
28
- .where("occurred_at >= ? AND occurred_at <= ?", view_start, view_end)
29
- .count %>
24
+ <% if parent %>
25
+ <% view_start = operation.occurred_at %>
26
+ <% view_end = operation.occurred_at + operation.duration %>
27
+ <% concurrent_db_ops = parent.operations
28
+ .where(operation_type: ["sql"])
29
+ .where("occurred_at >= ? AND occurred_at <= ?", view_start, view_end)
30
+ .count %>
30
31
 
31
- <% if concurrent_db_ops > 0 %>
32
- <dt>Queries During Rendering</dt>
33
- <dd><%= concurrent_db_ops %> database <%= 'query'.pluralize(concurrent_db_ops) %></dd>
32
+ <% if concurrent_db_ops > 0 %>
33
+ <dt>Queries During Rendering</dt>
34
+ <dd><%= concurrent_db_ops %> database <%= 'query'.pluralize(concurrent_db_ops) %></dd>
35
+ <% end %>
34
36
  <% end %>
35
37
 
36
38
  <% if operation.duration > 50 %>
@@ -32,19 +32,21 @@
32
32
  <%= @operation.duration.round(2) %> ms
33
33
  </dd>
34
34
 
35
- <dt>Request Impact</dt>
36
- <dd>
37
- <% impact = (@operation.duration / @request.duration * 100).round(1) %>
38
- <%= impact %>% of total request time
39
- </dd>
35
+ <% if @parent %>
36
+ <dt><%= @request ? 'Request' : 'Job Run' %> Impact</dt>
37
+ <dd>
38
+ <% impact = (@operation.duration / @parent.duration * 100).round(1) %>
39
+ <%= impact %>% of total <%= @request ? 'request' : 'job run' %> time
40
+ </dd>
41
+ <% end %>
40
42
 
41
43
  <dt>Occurred At</dt>
42
44
  <dd><%= human_readable_occurred_at @operation.occurred_at %></dd>
43
45
 
44
46
  <% begin %>
45
- <%= render partial: "operation_analysis_#{categorize_operation(@operation.operation_type)}", locals: { operation: @operation } %>
47
+ <%= render partial: "operation_analysis_#{categorize_operation(@operation.operation_type)}", locals: { operation: @operation, parent: @parent } %>
46
48
  <% rescue ActionView::MissingTemplate %>
47
- <%= render partial: "operation_analysis_generic", locals: { operation: @operation } %>
49
+ <%= render partial: "operation_analysis_generic", locals: { operation: @operation, parent: @parent } %>
48
50
  <% end %>
49
51
 
50
52
  <% if @performance_context.any? %>
@@ -59,7 +61,7 @@
59
61
  <% if @related_operations.any? %>
60
62
  <dt>Related Operations</dt>
61
63
  <dd>
62
- <%= pluralize(@related_operations.count, 'similar operation') %> in this request
64
+ <%= pluralize(@related_operations.count, 'similar operation') %> in this <%= @request ? 'request' : 'job run' %>
63
65
  <% if @related_operations.count > 2 %>
64
66
  <br>Potential N+1 query pattern detected
65
67
  <% end %>
@@ -1,17 +1,17 @@
1
1
  <% columns = [
2
- { field: :normalized_sql, label: 'Query', class: 'w-auto' },
2
+ { field: :normalized_sql, label: 'Query', class: 'w-48', cell_class: 'truncate-cell' },
3
3
  { field: :avg_duration_sort, label: 'Average Query Time', class: 'w-44' },
4
4
  { field: :execution_count_sort, label: 'Executions', class: 'w-24' },
5
5
  { field: nil, label: 'Tags', class: 'w-32' }
6
6
  ] %>
7
7
 
8
- <table class="table mbs-4" data-controller="rails-pulse--table-sort">
8
+ <table class="table table-fixed mbs-4" data-controller="rails-pulse--table-sort">
9
9
  <%= render "rails_pulse/components/table_head", columns: columns %>
10
10
 
11
11
  <tbody>
12
12
  <% @table_data.each do |summary| %>
13
13
  <tr>
14
- <td class="whitespace-nowrap">
14
+ <td class="truncate-cell">
15
15
  <div>
16
16
  <%= link_to html_escape(truncate_sql(summary.normalized_sql)), query_path(summary.query_id), data: { turbo_frame: '_top', } %>
17
17
  </div>
@@ -32,8 +32,9 @@
32
32
  class="chart-container chart-container--slim"
33
33
  data-rails-pulse--index-target="chart"
34
34
  >
35
- <%= bar_chart(
35
+ <%= render_stimulus_chart(
36
36
  @chart_data,
37
+ type: 'bar',
37
38
  code: false,
38
39
  id: "average_query_times_chart",
39
40
  height: "100%",
@@ -32,8 +32,9 @@
32
32
  class="chart-container chart-container--slim"
33
33
  data-rails-pulse--index-target="chart"
34
34
  >
35
- <%= bar_chart(
35
+ <%= render_stimulus_chart(
36
36
  @chart_data,
37
+ type: 'bar',
37
38
  code: false,
38
39
  id: "query_responses_chart",
39
40
  height: "100%",
@@ -1,12 +1,12 @@
1
1
  <% columns = [
2
- { field: :occurred_at, label: 'Timestamp', class: 'w-36' },
3
- { field: :route_path, label: 'Route', class: 'w-auto' },
2
+ { field: :route_path, label: 'Route', cell_class: 'truncate-cell' },
3
+ { field: :occurred_at, label: 'Occurred At', class: 'w-48' },
4
4
  { field: :duration, label: 'Response Time', class: 'w-36' },
5
5
  { field: :status, label: 'Status', class: 'w-20' },
6
6
  { field: nil, label: 'Tags', class: 'w-32' }
7
7
  ] %>
8
8
 
9
- <table class="table mbs-4" data-controller="rails-pulse--table-sort">
9
+ <table class="table table-fixed mbs-4" data-controller="rails-pulse--table-sort">
10
10
  <%= render "rails_pulse/components/table_head", columns: columns %>
11
11
 
12
12
  <tbody>
@@ -21,11 +21,11 @@
21
21
  end
22
22
  %>
23
23
  <tr>
24
- <td class="whitespace-nowrap">
25
- <%= link_to human_readable_occurred_at(request.occurred_at), request_path(request), data: { turbo_frame: '_top' } %>
24
+ <td class="truncate-cell">
25
+ <%= link_to "#{request.route.path} #{request.route.method}", route_path(request.route), data: { turbo_frame: '_top' } %>
26
26
  </td>
27
27
  <td class="whitespace-nowrap">
28
- <%= link_to "#{request.route.path} #{request.route.method}", route_path(request.route), data: { turbo_frame: '_top' } %>
28
+ <%= link_to human_readable_occurred_at(request.occurred_at), request_path(request), data: { turbo_frame: '_top' } %>
29
29
  </td>
30
30
  <td class="whitespace-nowrap">
31
31
  <span class="<%= performance_class %> font-medium">
@@ -1,18 +1,18 @@
1
1
  <% columns = [
2
- { field: :route_path, label: 'Route', class: 'w-auto' },
2
+ { field: :route_path, label: 'Route', class: 'w-48', cell_class: 'truncate-cell' },
3
3
  { field: :avg_duration_sort, label: 'Average Response Time', class: 'w-48' },
4
4
  { field: :max_duration_sort, label: 'Max Response Time', class: 'w-44' },
5
5
  { field: :count_sort, label: 'Requests', class: 'w-24' },
6
6
  { field: nil, label: 'Tags', class: 'w-32' }
7
7
  ] %>
8
8
 
9
- <table class="table mbs-4" data-controller="rails-pulse--table-sort">
9
+ <table class="table table-fixed mbs-4" data-controller="rails-pulse--table-sort">
10
10
  <%= render "rails_pulse/components/table_head", columns: columns %>
11
11
 
12
12
  <tbody>
13
13
  <% @table_data.each do |summary| %>
14
14
  <tr>
15
- <td class="whitespace-nowrap"><%= link_to "#{summary.path} #{summary.route_method}", route_path(summary.route_id), data: { turbo_frame: '_top' } %></td>
15
+ <td class="truncate-cell"><%= link_to "#{summary.path} #{summary.route_method}", route_path(summary.route_id), data: { turbo_frame: '_top' } %></td>
16
16
  <td class="whitespace-nowrap"><%= summary.avg_duration.to_i %> ms</td>
17
17
  <td class="whitespace-nowrap"><%= summary.max_duration.to_i %> ms</td>
18
18
  <td class="whitespace-nowrap"><%= number_with_delimiter summary.count %></td>
@@ -34,8 +34,9 @@
34
34
  class="chart-container chart-container--slim"
35
35
  data-rails-pulse--index-target="chart"
36
36
  >
37
- <%= bar_chart(
37
+ <%= render_stimulus_chart(
38
38
  @chart_data,
39
+ type: 'bar',
39
40
  code: false,
40
41
  id: "average_response_times_chart",
41
42
  height: "100%",
@@ -15,7 +15,7 @@
15
15
  data-rails-pulse--index-chart-id-value="route_responses_chart"
16
16
  >
17
17
  <div class="grid-item">
18
- <%= render 'rails_pulse/components/panel', { title: 'Route Reqeusts', } do %>
18
+ <%= render 'rails_pulse/components/panel', { title: 'Route Requests', } do %>
19
19
  <%= search_form_for @ransack_query, url: route_path(@route), class: "flex items-center justify-between gap mb-4", data: { controller: "rails-pulse--custom-range" } do |form| %>
20
20
  <div class="flex items-center grow gap">
21
21
  <%= time_range_selector(form, time_range_options: RailsPulse::RoutesController::TIME_RANGE_OPTIONS, selected_time_range: @selected_time_range) %>
@@ -35,8 +35,9 @@
35
35
  class="chart-container chart-container--slim"
36
36
  data-rails-pulse--index-target="chart"
37
37
  >
38
- <%= bar_chart(
38
+ <%= render_stimulus_chart(
39
39
  @chart_data,
40
+ type: 'bar',
40
41
  code: false,
41
42
  id: "route_responses_chart",
42
43
  height: "100%",
@@ -7,21 +7,14 @@
7
7
 
8
8
  <div
9
9
  id="tag_manager_<%= taggable_type %>_<%= taggable.id %>"
10
- class="tag-manager"
10
+ class="tag-manager card pi-3 pb-2 text-sm shadow-xs"
11
11
  >
12
+ <% if current_tags.present? %>
13
+ Tags:
14
+ <% end %>
12
15
  <div class="tag-list">
13
16
  <% current_tags.each do |tag| %>
14
- <span class="badge badge--secondary font-normal">
15
- <%= tag %>
16
- <%= button_to remove_tag_path(taggable_type, taggable.id, tag: tag),
17
- method: :delete,
18
- class: "tag-remove",
19
- data: {
20
- turbo_frame: "_top"
21
- } do %>
22
- <span aria-hidden="true">&times;</span>
23
- <% end %>
24
- </span>
17
+ <%= render_tag_badge(tag, variant: :secondary, removable: true, taggable_type: taggable_type, taggable_id: taggable.id) %>
25
18
  <% end %>
26
19
 
27
20
  <% if available_to_add.any? %>
@@ -35,7 +28,7 @@
35
28
  aria-haspopup="true"
36
29
  aria-controls="tag_menu_<%= taggable_type %>_<%= taggable.id %>"
37
30
  >
38
- + tag
31
+ tag +
39
32
  </button>
40
33
 
41
34
  <div
@@ -62,7 +55,7 @@
62
55
  },
63
56
  style: "cursor: pointer;",
64
57
  role: "menuitem" do %>
65
- <%= tag %>
58
+ <%= tag.humanize %>
66
59
  <% end %>
67
60
  <% end %>
68
61
  </div>