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.
- checksums.yaml +4 -4
- data/README.md +78 -0
- data/Rakefile +152 -3
- data/app/assets/images/rails_pulse/rails-pulse-logo.png +0 -0
- data/app/assets/stylesheets/rails_pulse/components/datepicker.css +191 -0
- data/app/assets/stylesheets/rails_pulse/components/switch.css +36 -0
- data/app/assets/stylesheets/rails_pulse/components/tags.css +98 -0
- data/app/assets/stylesheets/rails_pulse/components/utilities.css +26 -0
- data/app/controllers/concerns/chart_table_concern.rb +2 -0
- data/app/controllers/concerns/response_range_concern.rb +15 -2
- data/app/controllers/concerns/tag_filter_concern.rb +26 -0
- data/app/controllers/concerns/time_range_concern.rb +27 -8
- data/app/controllers/rails_pulse/application_controller.rb +74 -0
- data/app/controllers/rails_pulse/dashboard_controller.rb +12 -8
- data/app/controllers/rails_pulse/queries_controller.rb +15 -7
- data/app/controllers/rails_pulse/requests_controller.rb +48 -12
- data/app/controllers/rails_pulse/routes_controller.rb +14 -5
- data/app/controllers/rails_pulse/tags_controller.rb +51 -0
- data/app/helpers/rails_pulse/application_helper.rb +2 -0
- data/app/helpers/rails_pulse/form_helper.rb +75 -0
- data/app/helpers/rails_pulse/tags_helper.rb +29 -0
- data/app/javascript/rails_pulse/application.js +6 -0
- data/app/javascript/rails_pulse/controllers/custom_range_controller.js +115 -0
- data/app/javascript/rails_pulse/controllers/datepicker_controller.js +48 -0
- data/app/javascript/rails_pulse/controllers/global_filters_controller.js +110 -0
- data/app/models/concerns/rails_pulse/taggable.rb +63 -0
- data/app/models/rails_pulse/dashboard/charts/average_response_time.rb +12 -5
- data/app/models/rails_pulse/dashboard/charts/p95_response_time.rb +12 -5
- data/app/models/rails_pulse/dashboard/tables/slow_queries.rb +7 -0
- data/app/models/rails_pulse/dashboard/tables/slow_routes.rb +6 -0
- data/app/models/rails_pulse/queries/cards/average_query_times.rb +10 -6
- data/app/models/rails_pulse/queries/cards/execution_rate.rb +16 -10
- data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +10 -6
- data/app/models/rails_pulse/queries/charts/average_query_times.rb +5 -2
- data/app/models/rails_pulse/queries/tables/index.rb +20 -2
- data/app/models/rails_pulse/query.rb +2 -0
- data/app/models/rails_pulse/request.rb +9 -1
- data/app/models/rails_pulse/requests/charts/average_response_times.rb +12 -6
- data/app/models/rails_pulse/route.rb +2 -0
- data/app/models/rails_pulse/routes/cards/average_response_times.rb +10 -6
- data/app/models/rails_pulse/routes/cards/error_rate_per_route.rb +10 -6
- data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +10 -6
- data/app/models/rails_pulse/routes/cards/request_count_totals.rb +10 -6
- data/app/models/rails_pulse/routes/charts/average_response_times.rb +9 -5
- data/app/models/rails_pulse/routes/tables/index.rb +20 -2
- data/app/models/rails_pulse/summary.rb +55 -0
- data/app/services/rails_pulse/summary_service.rb +2 -0
- data/app/views/layouts/rails_pulse/_global_filters.html.erb +91 -0
- data/app/views/layouts/rails_pulse/_menu_items.html.erb +5 -5
- data/app/views/layouts/rails_pulse/application.html.erb +8 -5
- data/app/views/rails_pulse/components/_active_filters.html.erb +36 -0
- data/app/views/rails_pulse/components/_page_header.html.erb +24 -0
- data/app/views/rails_pulse/dashboard/index.html.erb +4 -0
- data/app/views/rails_pulse/operations/show.html.erb +1 -1
- data/app/views/rails_pulse/queries/_table.html.erb +3 -1
- data/app/views/rails_pulse/queries/index.html.erb +3 -7
- data/app/views/rails_pulse/queries/show.html.erb +3 -7
- data/app/views/rails_pulse/requests/_table.html.erb +3 -1
- data/app/views/rails_pulse/requests/index.html.erb +44 -62
- data/app/views/rails_pulse/requests/show.html.erb +1 -1
- data/app/views/rails_pulse/routes/_requests_table.html.erb +3 -1
- data/app/views/rails_pulse/routes/_table.html.erb +3 -1
- data/app/views/rails_pulse/routes/index.html.erb +4 -8
- data/app/views/rails_pulse/routes/show.html.erb +3 -7
- data/app/views/rails_pulse/tags/_tag_manager.html.erb +73 -0
- data/config/initializers/rails_charts_csp_patch.rb +9 -9
- data/config/routes.rb +5 -0
- data/db/rails_pulse_schema.rb +3 -0
- data/lib/generators/rails_pulse/install_generator.rb +21 -2
- data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +3 -0
- data/lib/generators/rails_pulse/templates/rails_pulse.rb +21 -0
- data/lib/generators/rails_pulse/upgrade_generator.rb +145 -29
- data/lib/rails_pulse/cleanup_service.rb +8 -0
- data/lib/rails_pulse/configuration.rb +16 -1
- data/lib/rails_pulse/engine.rb +25 -0
- data/lib/rails_pulse/version.rb +1 -1
- data/public/rails-pulse-assets/rails-pulse-icons.js +16 -15
- data/public/rails-pulse-assets/rails-pulse-icons.js.map +1 -1
- data/public/rails-pulse-assets/rails-pulse.css +1 -1
- data/public/rails-pulse-assets/rails-pulse.css.map +1 -1
- data/public/rails-pulse-assets/rails-pulse.js +73 -69
- data/public/rails-pulse-assets/rails-pulse.js.map +4 -4
- metadata +18 -3
- data/app/views/rails_pulse/components/_breadcrumbs.html.erb +0 -12
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= render 'rails_pulse/components/
|
|
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
|
-
|
|
13
|
-
data
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
39
|
+
<%= form.select :duration_gteq,
|
|
40
|
+
duration_options(:request),
|
|
41
|
+
{ selected: @selected_response_range, prompt: "Min duration" },
|
|
42
|
+
{ class: "input" }
|
|
43
|
+
%>
|
|
67
44
|
|
|
68
|
-
<%=
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
59
|
+
<% end %>
|
|
@@ -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/
|
|
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
|
|
20
|
-
<%= form.
|
|
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/
|
|
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
|
|
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">×</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
|
|
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
|
-
#
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
data/db/rails_pulse_schema.rb
CHANGED
|
@@ -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
|
|
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
|
|
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
|
# ====================================================================================================
|