rails_pulse 0.1.4 → 0.2.3

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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +78 -0
  3. data/Rakefile +152 -3
  4. data/app/assets/images/rails_pulse/rails-pulse-logo.png +0 -0
  5. data/app/assets/stylesheets/rails_pulse/components/datepicker.css +191 -0
  6. data/app/assets/stylesheets/rails_pulse/components/switch.css +36 -0
  7. data/app/assets/stylesheets/rails_pulse/components/tags.css +98 -0
  8. data/app/assets/stylesheets/rails_pulse/components/utilities.css +26 -0
  9. data/app/controllers/concerns/chart_table_concern.rb +2 -0
  10. data/app/controllers/concerns/response_range_concern.rb +15 -2
  11. data/app/controllers/concerns/tag_filter_concern.rb +26 -0
  12. data/app/controllers/concerns/time_range_concern.rb +27 -8
  13. data/app/controllers/rails_pulse/application_controller.rb +74 -0
  14. data/app/controllers/rails_pulse/dashboard_controller.rb +12 -8
  15. data/app/controllers/rails_pulse/queries_controller.rb +15 -7
  16. data/app/controllers/rails_pulse/requests_controller.rb +48 -12
  17. data/app/controllers/rails_pulse/routes_controller.rb +14 -5
  18. data/app/controllers/rails_pulse/tags_controller.rb +51 -0
  19. data/app/helpers/rails_pulse/application_helper.rb +2 -0
  20. data/app/helpers/rails_pulse/form_helper.rb +75 -0
  21. data/app/helpers/rails_pulse/tags_helper.rb +29 -0
  22. data/app/javascript/rails_pulse/application.js +6 -0
  23. data/app/javascript/rails_pulse/controllers/custom_range_controller.js +115 -0
  24. data/app/javascript/rails_pulse/controllers/datepicker_controller.js +48 -0
  25. data/app/javascript/rails_pulse/controllers/global_filters_controller.js +110 -0
  26. data/app/models/concerns/rails_pulse/taggable.rb +63 -0
  27. data/app/models/rails_pulse/dashboard/charts/average_response_time.rb +12 -5
  28. data/app/models/rails_pulse/dashboard/charts/p95_response_time.rb +12 -5
  29. data/app/models/rails_pulse/dashboard/tables/slow_queries.rb +7 -0
  30. data/app/models/rails_pulse/dashboard/tables/slow_routes.rb +6 -0
  31. data/app/models/rails_pulse/queries/cards/average_query_times.rb +10 -6
  32. data/app/models/rails_pulse/queries/cards/execution_rate.rb +16 -10
  33. data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +10 -6
  34. data/app/models/rails_pulse/queries/charts/average_query_times.rb +5 -2
  35. data/app/models/rails_pulse/queries/tables/index.rb +20 -2
  36. data/app/models/rails_pulse/query.rb +2 -0
  37. data/app/models/rails_pulse/request.rb +9 -1
  38. data/app/models/rails_pulse/requests/charts/average_response_times.rb +12 -6
  39. data/app/models/rails_pulse/route.rb +2 -0
  40. data/app/models/rails_pulse/routes/cards/average_response_times.rb +10 -6
  41. data/app/models/rails_pulse/routes/cards/error_rate_per_route.rb +10 -6
  42. data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +10 -6
  43. data/app/models/rails_pulse/routes/cards/request_count_totals.rb +10 -6
  44. data/app/models/rails_pulse/routes/charts/average_response_times.rb +9 -5
  45. data/app/models/rails_pulse/routes/tables/index.rb +20 -2
  46. data/app/models/rails_pulse/summary.rb +55 -0
  47. data/app/services/rails_pulse/summary_service.rb +2 -0
  48. data/app/views/layouts/rails_pulse/_global_filters.html.erb +91 -0
  49. data/app/views/layouts/rails_pulse/_menu_items.html.erb +5 -5
  50. data/app/views/layouts/rails_pulse/application.html.erb +8 -5
  51. data/app/views/rails_pulse/components/_active_filters.html.erb +36 -0
  52. data/app/views/rails_pulse/components/_page_header.html.erb +24 -0
  53. data/app/views/rails_pulse/dashboard/index.html.erb +4 -0
  54. data/app/views/rails_pulse/operations/show.html.erb +1 -1
  55. data/app/views/rails_pulse/queries/_table.html.erb +3 -1
  56. data/app/views/rails_pulse/queries/index.html.erb +3 -7
  57. data/app/views/rails_pulse/queries/show.html.erb +3 -7
  58. data/app/views/rails_pulse/requests/_table.html.erb +3 -1
  59. data/app/views/rails_pulse/requests/index.html.erb +44 -62
  60. data/app/views/rails_pulse/requests/show.html.erb +1 -1
  61. data/app/views/rails_pulse/routes/_requests_table.html.erb +3 -1
  62. data/app/views/rails_pulse/routes/_table.html.erb +3 -1
  63. data/app/views/rails_pulse/routes/index.html.erb +4 -8
  64. data/app/views/rails_pulse/routes/show.html.erb +3 -7
  65. data/app/views/rails_pulse/tags/_tag_manager.html.erb +73 -0
  66. data/config/initializers/rails_charts_csp_patch.rb +9 -9
  67. data/config/routes.rb +5 -0
  68. data/db/rails_pulse_schema.rb +3 -0
  69. data/lib/generators/rails_pulse/install_generator.rb +21 -2
  70. data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +3 -0
  71. data/lib/generators/rails_pulse/templates/rails_pulse.rb +21 -0
  72. data/lib/generators/rails_pulse/upgrade_generator.rb +145 -29
  73. data/lib/rails_pulse/cleanup_service.rb +8 -0
  74. data/lib/rails_pulse/configuration.rb +16 -1
  75. data/lib/rails_pulse/engine.rb +25 -0
  76. data/lib/rails_pulse/version.rb +1 -1
  77. data/public/rails-pulse-assets/rails-pulse-icons.js +16 -15
  78. data/public/rails-pulse-assets/rails-pulse-icons.js.map +1 -1
  79. data/public/rails-pulse-assets/rails-pulse.css +1 -1
  80. data/public/rails-pulse-assets/rails-pulse.css.map +1 -1
  81. data/public/rails-pulse-assets/rails-pulse.js +73 -69
  82. data/public/rails-pulse-assets/rails-pulse.js.map +4 -4
  83. metadata +18 -3
  84. data/app/views/rails_pulse/components/_breadcrumbs.html.erb +0 -12
@@ -1,4 +1,4 @@
1
- <%= render 'rails_pulse/components/breadcrumbs' %>
1
+ <%= render 'rails_pulse/components/page_header', show_active_filters: true %>
2
2
 
3
3
  <% unless turbo_frame_request? %>
4
4
  <div class="row">
@@ -9,69 +9,51 @@
9
9
  </div>
10
10
  <% end %>
11
11
 
12
- <div
13
- data-controller="rails-pulse--index"
14
- data-rails-pulse--index-chart-id-value="average_response_times_chart"
15
- >
16
- <%= render 'rails_pulse/components/panel', { title: 'Average Response Time', } do %>
17
- <%= search_form_for @ransack_query, url: requests_path, class: "flex items-center justify-between gap mb-4" do |form| %>
18
- <div class="flex items-center grow gap">
19
- <%= form.select :period_start_range,
20
- RailsPulse::RequestsController::TIME_RANGE_OPTIONS,
21
- { selected: @selected_time_range },
22
- { class: "input" }
23
- %>
24
- <%= form.select :avg_duration,
25
- duration_options(:request),
26
- { selected: @selected_response_range },
27
- { class: "input" }
28
- %>
29
- <%= link_to "Reset", requests_path, class: "btn btn--borderless show@md" if params.has_key?(:q) %>
30
- <%= form.submit "Search", class: "btn show@sm" %>
31
- </div>
32
- <% end %>
12
+ <%= render 'rails_pulse/components/panel', { title: 'Requests', } do %>
13
+ <%= search_form_for @ransack_query, url: requests_path, class: "flex items-center justify-between gap mb-4", data: { controller: "rails-pulse--custom-range" } do |form| %>
14
+ <div class="flex items-center grow gap">
15
+ <%= time_range_selector(form,
16
+ time_range_options: RailsPulse::RequestsController::TIME_RANGE_OPTIONS,
17
+ selected_time_range: @selected_time_range,
18
+ mode: :recent_custom
19
+ ) %>
20
+
21
+ <%= form.search_field :route_path_cont,
22
+ placeholder: "Filter by route",
23
+ autocomplete: "off",
24
+ class: "input"
25
+ %>
33
26
 
34
- <% if @has_data %>
35
- <% if @chart_data && @chart_data.values.any? { |v| v > 0 } %>
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
- <% else %>
59
- <div class="p-4 text-center text-muted">
60
- No response time data available for the selected filters.
61
- </div>
62
- <% end %>
27
+ <%= form.select :status_category_eq,
28
+ [
29
+ ["All statuses", ""],
30
+ ["2xx Success", "2"],
31
+ ["3xx Redirect", "3"],
32
+ ["4xx Client Error", "4"],
33
+ ["5xx Server Error", "5"]
34
+ ],
35
+ {},
36
+ { class: "input" }
37
+ %>
63
38
 
64
- <div class="alert flex items-start mbs-8" role="alert">
65
- <p>The chart above shows aggregated average response times grouped by time periods while the table below shows specific request details.</p>
66
- </div>
39
+ <%= form.select :duration_gteq,
40
+ duration_options(:request),
41
+ { selected: @selected_response_range, prompt: "Min duration" },
42
+ { class: "input" }
43
+ %>
67
44
 
68
- <%= turbo_frame_tag :index_table, data: { rails_pulse__index_target: "indexTable" } do %>
69
- <%= render 'rails_pulse/requests/table' %>
70
- <% end %>
71
- <% else %>
72
- <%= render 'rails_pulse/components/empty_state',
73
- title: 'No request data found for the selected filters.',
74
- description: 'Try adjusting your time range or filters to see results.' %>
45
+ <%= link_to "Reset", requests_path, class: "btn btn--borderless show@md" if params.has_key?(:q) %>
46
+ <%= form.submit "Search", class: "btn show@sm" %>
47
+ </div>
48
+ <% end %>
49
+
50
+ <% if @has_data %>
51
+ <%= turbo_frame_tag :index_table do %>
52
+ <%= render 'rails_pulse/requests/table' %>
75
53
  <% end %>
54
+ <% else %>
55
+ <%= render 'rails_pulse/components/empty_state',
56
+ title: 'No request data found for the selected filters.',
57
+ description: 'Try adjusting your time range or filters to see results.' %>
76
58
  <% end %>
77
- </div>
59
+ <% end %>
@@ -1,4 +1,4 @@
1
- <%= render 'rails_pulse/components/breadcrumbs' %>
1
+ <%= render 'rails_pulse/components/page_header', taggable: @request %>
2
2
 
3
3
  <div class="row">
4
4
  <div class="grid-item">
@@ -1,7 +1,8 @@
1
1
  <% columns = [
2
2
  { field: :occurred_at, label: 'Timestamp', class: 'w-auto' },
3
3
  { field: :duration, label: 'Response Time', class: 'w-36' },
4
- { field: :status, label: 'Status', class: 'w-20' }
4
+ { field: :status, label: 'Status', class: 'w-20' },
5
+ { field: nil, label: 'Tags', class: 'w-32' }
5
6
  ] %>
6
7
 
7
8
  <table class="table mbs-4" data-controller="rails-pulse--table-sort">
@@ -31,6 +32,7 @@
31
32
  <span class="text-green-600"><%= request.status %></span>
32
33
  <% end %>
33
34
  </td>
35
+ <td class="whitespace-nowrap"><%= display_tag_badges(request) %></td>
34
36
  </tr>
35
37
  <% end %>
36
38
  </tbody>
@@ -2,7 +2,8 @@
2
2
  { field: :route_path, label: 'Route', class: 'w-auto' },
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
- { field: :count_sort, label: 'Requests', class: 'w-24' }
5
+ { field: :count_sort, label: 'Requests', class: 'w-24' },
6
+ { field: nil, label: 'Tags', class: 'w-32' }
6
7
  ] %>
7
8
 
8
9
  <table class="table mbs-4" data-controller="rails-pulse--table-sort">
@@ -15,6 +16,7 @@
15
16
  <td class="whitespace-nowrap"><%= summary.avg_duration.to_i %> ms</td>
16
17
  <td class="whitespace-nowrap"><%= summary.max_duration.to_i %> ms</td>
17
18
  <td class="whitespace-nowrap"><%= number_with_delimiter summary.count %></td>
19
+ <td class="whitespace-nowrap"><%= display_tag_badges(summary.tags) %></td>
18
20
  </tr>
19
21
  <% end %>
20
22
  </tbody>
@@ -1,4 +1,4 @@
1
- <%= render 'rails_pulse/components/breadcrumbs' %>
1
+ <%= render 'rails_pulse/components/page_header', show_active_filters: true %>
2
2
 
3
3
  <% unless turbo_frame_request? %>
4
4
  <div class="row">
@@ -14,14 +14,10 @@
14
14
  data-rails-pulse--index-chart-id-value="average_response_times_chart"
15
15
  >
16
16
  <%= render 'rails_pulse/components/panel', { title: 'Average Response Time', card_classes: 'b-full' } do %>
17
- <%= search_form_for @ransack_query, url: routes_path, class: "flex items-center justify-between gap mb-4" do |form| %>
17
+ <%= search_form_for @ransack_query, url: routes_path, class: "flex items-center justify-between gap mb-4", data: { controller: "rails-pulse--custom-range" } do |form| %>
18
18
  <div class="flex items-center grow gap">
19
- <%= form.search_field :route_path_cont, placeholder: "Filter by route", autocomplete: "off", class: "input", style: "max-inline-size: 250px" %>
20
- <%= form.select :period_start_range,
21
- RailsPulse::RoutesController::TIME_RANGE_OPTIONS,
22
- { selected: @selected_time_range },
23
- { class: "input" }
24
- %>
19
+ <%= time_range_selector(form, time_range_options: RailsPulse::RoutesController::TIME_RANGE_OPTIONS, selected_time_range: @selected_time_range) %>
20
+ <%= form.search_field :route_path_cont, placeholder: "Filter by route", autocomplete: "off", class: "input" %>
25
21
  <%= form.select :avg_duration,
26
22
  duration_options(:route),
27
23
  { selected: @selected_response_range },
@@ -1,4 +1,4 @@
1
- <%= render 'rails_pulse/components/breadcrumbs' %>
1
+ <%= render 'rails_pulse/components/page_header', taggable: @route %>
2
2
 
3
3
  <% unless turbo_frame_request? %>
4
4
  <div class="row">
@@ -16,13 +16,9 @@
16
16
  >
17
17
  <div class="grid-item">
18
18
  <%= render 'rails_pulse/components/panel', { title: 'Route Reqeusts', } do %>
19
- <%= search_form_for @ransack_query, url: route_path(@route), class: "flex items-center justify-between gap mb-4" do |form| %>
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
- <%= form.select :period_start_range,
22
- RailsPulse::RoutesController::TIME_RANGE_OPTIONS,
23
- { selected: @selected_time_range },
24
- { class: "input" }
25
- %>
21
+ <%= time_range_selector(form, time_range_options: RailsPulse::RoutesController::TIME_RANGE_OPTIONS, selected_time_range: @selected_time_range) %>
26
22
  <%= form.select :avg_duration,
27
23
  duration_options(:route),
28
24
  { selected: @selected_response_range },
@@ -0,0 +1,73 @@
1
+ <%
2
+ taggable_type = taggable.class.name.demodulize.underscore
3
+ available_tags = RailsPulse.configuration.tags
4
+ current_tags = taggable.tag_list
5
+ available_to_add = available_tags - current_tags
6
+ %>
7
+
8
+ <div
9
+ id="tag_manager_<%= taggable_type %>_<%= taggable.id %>"
10
+ class="tag-manager"
11
+ >
12
+ <div class="tag-list">
13
+ <% 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>
25
+ <% end %>
26
+
27
+ <% if available_to_add.any? %>
28
+ <div class="tag-add-container" data-controller="rails-pulse--popover" data-rails-pulse--popover-placement-value="bottom-start">
29
+ <button
30
+ type="button"
31
+ id="tag_menu_button_<%= taggable_type %>_<%= taggable.id %>"
32
+ class="badge badge--positive tag-add-button font-normal"
33
+ data-rails-pulse--popover-target="button"
34
+ data-action="rails-pulse--popover#toggle"
35
+ aria-haspopup="true"
36
+ aria-controls="tag_menu_<%= taggable_type %>_<%= taggable.id %>"
37
+ >
38
+ + tag
39
+ </button>
40
+
41
+ <div
42
+ popover
43
+ class="popover"
44
+ style="--popover-size: 12rem;"
45
+ data-rails-pulse--popover-target="menu"
46
+ >
47
+ <div
48
+ id="tag_menu_<%= taggable_type %>_<%= taggable.id %>"
49
+ class="menu"
50
+ data-controller="rails-pulse--menu"
51
+ data-action="keydown.up->rails-pulse--menu#prev keydown.down->rails-pulse--menu#next"
52
+ role="menu"
53
+ aria-labelledby="tag_menu_button_<%= taggable_type %>_<%= taggable.id %>"
54
+ >
55
+ <% available_to_add.each do |tag| %>
56
+ <%= button_to add_tag_path(taggable_type, taggable.id, tag: tag),
57
+ method: :post,
58
+ class: "btn menu__item i-full",
59
+ data: {
60
+ turbo_frame: "_top",
61
+ rails_pulse__menu_target: "item"
62
+ },
63
+ style: "cursor: pointer;",
64
+ role: "menuitem" do %>
65
+ <%= tag %>
66
+ <% end %>
67
+ <% end %>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ <% end %>
72
+ </div>
73
+ </div>
@@ -61,15 +61,15 @@ if defined?(RailsCharts)
61
61
  end
62
62
  end
63
63
 
64
- # Apply the patch to ApplicationHelper and any modules that include chart helpers
64
+ # Apply the CSP patch only to Rails Pulse helpers, not the entire application
65
+ # By prepending to ChartHelper instead of ApplicationHelper, we scope the patch to RailsPulse
66
+ # namespace only, avoiding conflicts with any chart libraries in the host application
67
+ # (Chartkick, Highcharts, Google Charts, ApexCharts, custom helpers, etc.)
65
68
  Rails.application.config.to_prepare do
66
- if defined?(RailsCharts)
67
- # Patch ActionView::Base to include our CSP patch for line_chart
68
- ActionView::Base.prepend(RailsCharts::CspPatch)
69
-
70
- # Also patch any Rails Pulse helpers that might use charts
71
- if defined?(RailsPulse::ApplicationHelper)
72
- RailsPulse::ApplicationHelper.prepend(RailsCharts::CspPatch)
73
- end
69
+ if defined?(RailsCharts) && defined?(RailsPulse::ChartHelper)
70
+ # Prepend CSP patch to RailsPulse::ChartHelper
71
+ # This wraps only the rails_charts methods, ensuring clean CSP nonce injection
72
+ # without affecting the host application's chart helpers
73
+ RailsPulse::ChartHelper.prepend(RailsCharts::CspPatch)
74
74
  end
75
75
  end
data/config/routes.rb CHANGED
@@ -11,6 +11,11 @@ RailsPulse::Engine.routes.draw do
11
11
  resources :operations, only: %i[show]
12
12
  resources :caches, only: %i[show], as: :cache
13
13
  patch "pagination/limit", to: "application#set_pagination_limit"
14
+ patch "settings/global_filters", to: "application#set_global_filters"
15
+
16
+ # Tag management
17
+ post "tags/:taggable_type/:taggable_id/add", to: "tags#create", as: :add_tag
18
+ delete "tags/:taggable_type/:taggable_id/remove", to: "tags#destroy", as: :remove_tag
14
19
 
15
20
  # CSP compliance testing
16
21
  get "csp_test", to: "csp_test#show", as: :csp_test
@@ -18,6 +18,7 @@ RailsPulse::Schema = lambda do |connection|
18
18
  connection.create_table :rails_pulse_routes do |t|
19
19
  t.string :method, null: false, comment: "HTTP method (e.g., GET, POST)"
20
20
  t.string :path, null: false, comment: "Request path (e.g., /posts/index)"
21
+ t.text :tags, comment: "JSON array of tags for filtering and categorization"
21
22
  t.timestamps
22
23
  end
23
24
 
@@ -34,6 +35,7 @@ RailsPulse::Schema = lambda do |connection|
34
35
  t.text :index_recommendations, comment: "JSON array of database index recommendations"
35
36
  t.text :n_plus_one_analysis, comment: "JSON object with enhanced N+1 query detection results"
36
37
  t.text :suggestions, comment: "JSON array of optimization recommendations"
38
+ t.text :tags, comment: "JSON array of tags for filtering and categorization"
37
39
  t.timestamps
38
40
  end
39
41
 
@@ -47,6 +49,7 @@ RailsPulse::Schema = lambda do |connection|
47
49
  t.string :request_uuid, null: false, comment: "Unique identifier for the request (e.g., UUID)"
48
50
  t.string :controller_action, comment: "Controller and action handling the request (e.g., PostsController#show)"
49
51
  t.timestamp :occurred_at, null: false, comment: "When the request started"
52
+ t.text :tags, comment: "JSON array of tags for filtering and categorization"
50
53
  t.timestamps
51
54
  end
52
55
 
@@ -22,6 +22,25 @@ module RailsPulse
22
22
  create_file "db/rails_pulse_migrate/.keep"
23
23
  end
24
24
 
25
+ def copy_gem_migrations
26
+ gem_migrations_path = File.expand_path("../../../db/rails_pulse_migrate", __dir__)
27
+ destination_dir = separate_database? ? "db/rails_pulse_migrate" : "db/migrate"
28
+
29
+ if File.directory?(gem_migrations_path)
30
+ Dir.glob("#{gem_migrations_path}/*.rb").each do |migration_file|
31
+ migration_name = File.basename(migration_file)
32
+ destination_path = File.join(destination_dir, migration_name)
33
+
34
+ # Only copy if it doesn't already exist in the destination
35
+ # Use File.join with destination_root to check the actual location
36
+ full_destination_path = File.join(destination_root, destination_path)
37
+ unless File.exist?(full_destination_path)
38
+ copy_file migration_file, destination_path
39
+ end
40
+ end
41
+ end
42
+ end
43
+
25
44
  def copy_initializer
26
45
  copy_file "rails_pulse.rb", "config/initializers/rails_pulse.rb"
27
46
  end
@@ -84,7 +103,7 @@ module RailsPulse
84
103
  3. Restart your Rails server
85
104
 
86
105
  The schema file db/rails_pulse_schema.rb is your single source of truth.
87
- Future schema changes will come as regular migrations in db/rails_pulse_migrate/
106
+ Future upgrades will automatically copy new migrations to db/rails_pulse_migrate/
88
107
 
89
108
  MESSAGE
90
109
  end
@@ -99,7 +118,7 @@ module RailsPulse
99
118
  2. Restart your Rails server
100
119
 
101
120
  The schema file db/rails_pulse_schema.rb is your single source of truth.
102
- Future schema changes will come as regular migrations in db/migrate/
121
+ Future upgrades will automatically copy new migrations to db/migrate/
103
122
 
104
123
  Note: The installation migration loads from db/rails_pulse_schema.rb
105
124
  and includes all current Rails Pulse tables and columns.
@@ -18,6 +18,7 @@ RailsPulse::Schema = lambda do |connection|
18
18
  connection.create_table :rails_pulse_routes do |t|
19
19
  t.string :method, null: false, comment: "HTTP method (e.g., GET, POST)"
20
20
  t.string :path, null: false, comment: "Request path (e.g., /posts/index)"
21
+ t.text :tags, comment: "JSON array of tags for filtering and categorization"
21
22
  t.timestamps
22
23
  end
23
24
 
@@ -34,6 +35,7 @@ RailsPulse::Schema = lambda do |connection|
34
35
  t.text :index_recommendations, comment: "JSON array of database index recommendations"
35
36
  t.text :n_plus_one_analysis, comment: "JSON object with enhanced N+1 query detection results"
36
37
  t.text :suggestions, comment: "JSON array of optimization recommendations"
38
+ t.text :tags, comment: "JSON array of tags for filtering and categorization"
37
39
  t.timestamps
38
40
  end
39
41
 
@@ -47,6 +49,7 @@ RailsPulse::Schema = lambda do |connection|
47
49
  t.string :request_uuid, null: false, comment: "Unique identifier for the request (e.g., UUID)"
48
50
  t.string :controller_action, comment: "Controller and action handling the request (e.g., PostsController#show)"
49
51
  t.timestamp :occurred_at, null: false, comment: "When the request started"
52
+ t.text :tags, comment: "JSON array of tags for filtering and categorization"
50
53
  t.timestamps
51
54
  end
52
55
 
@@ -71,6 +71,27 @@ RailsPulse.configure do |config|
71
71
  config.ignored_requests = []
72
72
  config.ignored_queries = []
73
73
 
74
+ # ====================================================================================================
75
+ # TAGGING
76
+ # ====================================================================================================
77
+ # Define custom tags for categorizing routes, requests, and queries.
78
+ # You can add any custom tags you want for filtering and organization.
79
+ #
80
+ # Tag names should be in present tense and describe the current state or category.
81
+ # Examples of good tag names:
82
+ # - "critical" (for high-priority endpoints)
83
+ # - "experimental" (for routes under development)
84
+ # - "deprecated" (for routes being phased out)
85
+ # - "external" (for third-party API calls)
86
+ # - "background" (for async job-related operations)
87
+ # - "admin" (for administrative routes)
88
+ # - "public" (for public-facing routes)
89
+ #
90
+ # Example configuration:
91
+ # config.tags = ["ignored", "critical", "experimental", "deprecated", "external", "admin"]
92
+
93
+ config.tags = [ "ignored", "critical", "experimental" ]
94
+
74
95
  # ====================================================================================================
75
96
  # DATABASE CONFIGURATION
76
97
  # ====================================================================================================