rails_pulse 0.1.4 → 0.2.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.
- 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/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 +73 -0
- data/app/controllers/rails_pulse/queries_controller.rb +4 -1
- data/app/controllers/rails_pulse/requests_controller.rb +40 -8
- data/app/controllers/rails_pulse/routes_controller.rb +4 -2
- 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/taggable.rb +61 -0
- data/app/models/rails_pulse/queries/tables/index.rb +10 -2
- data/app/models/rails_pulse/query.rb +2 -0
- data/app/models/rails_pulse/request.rb +9 -1
- data/app/models/rails_pulse/route.rb +2 -0
- data/app/models/rails_pulse/routes/tables/index.rb +10 -2
- data/app/services/rails_pulse/summary_service.rb +2 -0
- data/app/views/layouts/rails_pulse/_global_filters.html.erb +84 -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/_page_header.html.erb +20 -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/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/configuration.rb +16 -1
- 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 +17 -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' %>
|
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' %>
|
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="tag">
|
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="btn btn--sm tag-add-button"
|
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>
|
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
|
# ====================================================================================================
|
@@ -41,17 +41,36 @@ module RailsPulse
|
|
41
41
|
# Check for existing Rails Pulse tables
|
42
42
|
tables_exist = rails_pulse_tables_exist?
|
43
43
|
|
44
|
-
|
44
|
+
root_path = respond_to?(:destination_root) ? destination_root : Rails.root
|
45
|
+
schema_path = File.join(root_path, "db/rails_pulse_schema.rb")
|
46
|
+
|
47
|
+
if !tables_exist && File.exist?(schema_path)
|
45
48
|
:schema_only
|
46
49
|
elsif !tables_exist
|
47
50
|
:not_installed
|
48
|
-
elsif
|
51
|
+
elsif has_separate_database_config?
|
49
52
|
:separate
|
50
53
|
else
|
51
54
|
:single
|
52
55
|
end
|
53
56
|
end
|
54
57
|
|
58
|
+
def has_separate_database_config?
|
59
|
+
root_path = respond_to?(:destination_root) ? destination_root : Rails.root
|
60
|
+
config_path = File.join(root_path, "config/database.yml")
|
61
|
+
|
62
|
+
return false unless File.exist?(config_path)
|
63
|
+
|
64
|
+
require "yaml"
|
65
|
+
db_config = YAML.load_file(config_path)
|
66
|
+
|
67
|
+
# Check if any environment has a rails_pulse database configuration
|
68
|
+
db_config.values.any? { |env| env.is_a?(Hash) && env.key?("rails_pulse") }
|
69
|
+
rescue => e
|
70
|
+
# If we can't read the file, assume single database
|
71
|
+
false
|
72
|
+
end
|
73
|
+
|
55
74
|
def rails_pulse_tables_exist?
|
56
75
|
return false unless defined?(ActiveRecord::Base)
|
57
76
|
|
@@ -65,7 +84,8 @@ module RailsPulse
|
|
65
84
|
|
66
85
|
def get_rails_pulse_table_names
|
67
86
|
# Load the schema file to get the table names dynamically
|
68
|
-
|
87
|
+
root_path = respond_to?(:destination_root) ? destination_root : Rails.root
|
88
|
+
schema_file = File.join(root_path, "db/rails_pulse_schema.rb")
|
69
89
|
|
70
90
|
if File.exist?(schema_file)
|
71
91
|
# Read the schema file and extract the required_tables array
|
@@ -84,44 +104,114 @@ module RailsPulse
|
|
84
104
|
end
|
85
105
|
|
86
106
|
def upgrade_single_database
|
87
|
-
|
107
|
+
# Check for new migrations in gem
|
108
|
+
gem_migrations = get_gem_migrations
|
109
|
+
existing_migrations = get_user_migrations("db/migrate")
|
110
|
+
new_migrations = gem_migrations - existing_migrations
|
111
|
+
|
112
|
+
if new_migrations.any?
|
113
|
+
say "Found #{new_migrations.size} new migration(s) to copy:", :blue
|
114
|
+
new_migrations.each do |migration|
|
115
|
+
say " - #{migration}", :blue
|
116
|
+
copy_gem_migration_to(migration, "db/migrate")
|
117
|
+
end
|
88
118
|
|
89
|
-
|
90
|
-
say "
|
91
|
-
|
92
|
-
|
119
|
+
say "\nMigrations copied successfully!", :green
|
120
|
+
say "\nNext steps:", :green
|
121
|
+
say "1. Run: rails db:migrate"
|
122
|
+
say "2. Restart your Rails server"
|
123
|
+
else
|
124
|
+
# Fall back to detecting missing columns
|
125
|
+
missing_columns = detect_missing_columns
|
93
126
|
|
94
|
-
|
95
|
-
|
127
|
+
if missing_columns.empty?
|
128
|
+
say "Rails Pulse is up to date! No migration needed.", :green
|
129
|
+
return
|
130
|
+
end
|
96
131
|
|
97
|
-
|
132
|
+
# Format missing columns by table for the template
|
133
|
+
missing_by_table = format_missing_columns_by_table(missing_columns)
|
98
134
|
|
99
|
-
|
100
|
-
@migration_version = ActiveRecord::Migration.current_version
|
101
|
-
@missing_columns = missing_by_table
|
135
|
+
say "Creating upgrade migration for missing columns: #{missing_columns.keys.join(', ')}", :blue
|
102
136
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
)
|
137
|
+
# Set instance variables for template
|
138
|
+
@migration_version = ActiveRecord::Migration.current_version
|
139
|
+
@missing_columns = missing_by_table
|
107
140
|
|
108
|
-
|
141
|
+
migration_template(
|
142
|
+
"migrations/upgrade_rails_pulse_tables.rb",
|
143
|
+
"db/migrate/upgrade_rails_pulse_tables.rb"
|
144
|
+
)
|
109
145
|
|
110
|
-
|
146
|
+
say <<~MESSAGE
|
111
147
|
|
112
|
-
|
113
|
-
1. Run: rails db:migrate
|
114
|
-
2. Restart your Rails server
|
148
|
+
Upgrade migration created successfully!
|
115
149
|
|
116
|
-
|
150
|
+
Next steps:
|
151
|
+
1. Run: rails db:migrate
|
152
|
+
2. Restart your Rails server
|
117
153
|
|
118
|
-
|
154
|
+
This migration will add: #{missing_columns.keys.join(', ')}
|
155
|
+
|
156
|
+
MESSAGE
|
157
|
+
end
|
119
158
|
end
|
120
159
|
|
121
160
|
def upgrade_separate_database
|
122
|
-
#
|
123
|
-
|
124
|
-
|
161
|
+
# Check for new migrations in gem
|
162
|
+
gem_migrations = get_gem_migrations
|
163
|
+
existing_migrations = get_user_migrations("db/rails_pulse_migrate")
|
164
|
+
new_migrations = gem_migrations - existing_migrations
|
165
|
+
|
166
|
+
if new_migrations.any?
|
167
|
+
say "Found #{new_migrations.size} new migration(s) to copy:", :blue
|
168
|
+
new_migrations.each do |migration|
|
169
|
+
say " - #{migration}", :blue
|
170
|
+
copy_gem_migration_to(migration, "db/rails_pulse_migrate")
|
171
|
+
end
|
172
|
+
|
173
|
+
say "\nMigrations copied successfully!", :green
|
174
|
+
say "\nNext steps:", :green
|
175
|
+
say "1. Run migrations for the rails_pulse database:"
|
176
|
+
say " rails db:migrate (will run migrations for all databases)"
|
177
|
+
say " OR manually run the migration files in db/rails_pulse_migrate/"
|
178
|
+
say "2. Restart your Rails server"
|
179
|
+
else
|
180
|
+
# Fall back to detecting missing columns
|
181
|
+
missing_columns = detect_missing_columns
|
182
|
+
|
183
|
+
if missing_columns.empty?
|
184
|
+
say "Rails Pulse is up to date! No migrations needed.", :green
|
185
|
+
else
|
186
|
+
# Format missing columns by table for the template
|
187
|
+
missing_by_table = format_missing_columns_by_table(missing_columns)
|
188
|
+
|
189
|
+
say "Creating upgrade migration for missing columns: #{missing_columns.keys.join(', ')}", :blue
|
190
|
+
|
191
|
+
# Set instance variables for template
|
192
|
+
@migration_version = ActiveRecord::Migration.current_version
|
193
|
+
@missing_columns = missing_by_table
|
194
|
+
|
195
|
+
migration_template(
|
196
|
+
"migrations/upgrade_rails_pulse_tables.rb",
|
197
|
+
"db/rails_pulse_migrate/upgrade_rails_pulse_tables.rb"
|
198
|
+
)
|
199
|
+
|
200
|
+
say <<~MESSAGE
|
201
|
+
|
202
|
+
Upgrade migration created successfully!
|
203
|
+
|
204
|
+
Next steps:
|
205
|
+
1. Run migrations for the rails_pulse database:
|
206
|
+
rails db:migrate (will run migrations for all databases)
|
207
|
+
OR manually run the migration files in db/rails_pulse_migrate/
|
208
|
+
2. Restart your Rails server
|
209
|
+
|
210
|
+
This migration will add: #{missing_columns.keys.join(', ')}
|
211
|
+
|
212
|
+
MESSAGE
|
213
|
+
end
|
214
|
+
end
|
125
215
|
end
|
126
216
|
|
127
217
|
def offer_conversion_to_migrations
|
@@ -165,7 +255,8 @@ module RailsPulse
|
|
165
255
|
end
|
166
256
|
|
167
257
|
def get_expected_schema_from_file
|
168
|
-
|
258
|
+
root_path = respond_to?(:destination_root) ? destination_root : Rails.root
|
259
|
+
schema_file = File.join(root_path, "db/rails_pulse_schema.rb")
|
169
260
|
return {} unless File.exist?(schema_file)
|
170
261
|
|
171
262
|
schema_content = File.read(schema_file)
|
@@ -221,6 +312,31 @@ module RailsPulse
|
|
221
312
|
|
222
313
|
missing_by_table
|
223
314
|
end
|
315
|
+
|
316
|
+
def get_gem_migrations
|
317
|
+
gem_migrations_path = File.expand_path("../../../db/rails_pulse_migrate", __dir__)
|
318
|
+
return [] unless File.directory?(gem_migrations_path)
|
319
|
+
|
320
|
+
Dir.glob("#{gem_migrations_path}/*.rb").map { |f| File.basename(f) }
|
321
|
+
end
|
322
|
+
|
323
|
+
def get_user_migrations(directory)
|
324
|
+
# Use destination_root in tests, Rails.root in production
|
325
|
+
root_path = respond_to?(:destination_root) ? destination_root : Rails.root
|
326
|
+
full_directory = File.join(root_path, directory)
|
327
|
+
|
328
|
+
return [] unless File.directory?(full_directory)
|
329
|
+
|
330
|
+
Dir.glob("#{full_directory}/*.rb").map { |f| File.basename(f) }
|
331
|
+
end
|
332
|
+
|
333
|
+
def copy_gem_migration_to(migration_name, destination)
|
334
|
+
gem_migrations_path = File.expand_path("../../../db/rails_pulse_migrate", __dir__)
|
335
|
+
source_file = File.join(gem_migrations_path, migration_name)
|
336
|
+
destination_file = File.join(destination, migration_name)
|
337
|
+
|
338
|
+
copy_file source_file, destination_file
|
339
|
+
end
|
224
340
|
end
|
225
341
|
end
|
226
342
|
end
|