rails_pulse 0.1.3 → 0.1.4

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +56 -16
  3. data/Rakefile +169 -86
  4. data/app/controllers/rails_pulse/queries_controller.rb +14 -20
  5. data/app/controllers/rails_pulse/requests_controller.rb +43 -30
  6. data/app/helpers/rails_pulse/breadcrumbs_helper.rb +1 -1
  7. data/app/helpers/rails_pulse/chart_helper.rb +1 -1
  8. data/app/helpers/rails_pulse/formatting_helper.rb +21 -2
  9. data/app/javascript/rails_pulse/controllers/index_controller.js +11 -3
  10. data/app/models/rails_pulse/dashboard/tables/slow_queries.rb +1 -1
  11. data/app/models/rails_pulse/dashboard/tables/slow_routes.rb +1 -1
  12. data/app/models/rails_pulse/queries/cards/average_query_times.rb +1 -1
  13. data/app/models/rails_pulse/queries/cards/execution_rate.rb +56 -17
  14. data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +1 -1
  15. data/app/models/rails_pulse/queries/charts/average_query_times.rb +3 -7
  16. data/app/models/rails_pulse/request.rb +1 -1
  17. data/app/models/rails_pulse/requests/charts/average_response_times.rb +2 -2
  18. data/app/models/rails_pulse/requests/tables/index.rb +77 -0
  19. data/app/models/rails_pulse/routes/cards/average_response_times.rb +1 -1
  20. data/app/models/rails_pulse/routes/cards/error_rate_per_route.rb +1 -1
  21. data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +1 -1
  22. data/app/models/rails_pulse/routes/cards/request_count_totals.rb +16 -5
  23. data/app/models/rails_pulse/routes/tables/index.rb +4 -2
  24. data/app/models/rails_pulse/summary.rb +7 -7
  25. data/app/services/rails_pulse/analysis/query_characteristics_analyzer.rb +11 -3
  26. data/app/views/rails_pulse/components/_metric_card.html.erb +2 -2
  27. data/app/views/rails_pulse/components/_operation_details_popover.html.erb +1 -1
  28. data/app/views/rails_pulse/components/_sparkline_stats.html.erb +1 -1
  29. data/app/views/rails_pulse/dashboard/index.html.erb +1 -1
  30. data/app/views/rails_pulse/queries/_analysis_results.html.erb +53 -23
  31. data/app/views/rails_pulse/queries/_show_table.html.erb +33 -5
  32. data/app/views/rails_pulse/queries/_table.html.erb +3 -7
  33. data/app/views/rails_pulse/requests/_table.html.erb +30 -19
  34. data/app/views/rails_pulse/requests/index.html.erb +8 -0
  35. data/app/views/rails_pulse/requests/show.html.erb +0 -2
  36. data/app/views/rails_pulse/routes/_requests_table.html.erb +39 -0
  37. data/app/views/rails_pulse/routes/_table.html.erb +3 -9
  38. data/app/views/rails_pulse/routes/show.html.erb +3 -5
  39. data/config/initializers/rails_charts_csp_patch.rb +32 -40
  40. data/db/migrate/20250930105043_install_rails_pulse_tables.rb +23 -0
  41. data/db/rails_pulse_schema.rb +1 -1
  42. data/lib/generators/rails_pulse/convert_to_migrations_generator.rb +25 -9
  43. data/lib/generators/rails_pulse/install_generator.rb +9 -5
  44. data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +72 -2
  45. data/lib/generators/rails_pulse/templates/migrations/install_rails_pulse_tables.rb +3 -2
  46. data/lib/generators/rails_pulse/upgrade_generator.rb +2 -1
  47. data/lib/rails_pulse/engine.rb +21 -0
  48. data/lib/rails_pulse/version.rb +1 -1
  49. data/public/rails-pulse-assets/rails-pulse.js +1 -1
  50. data/public/rails-pulse-assets/rails-pulse.js.map +2 -2
  51. metadata +5 -4
  52. data/db/migrate/20241222000001_create_rails_pulse_summaries.rb +0 -54
  53. data/db/migrate/20250916031656_add_analysis_to_rails_pulse_queries.rb +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11728d425611e7ab3a9edb171f05638a783d1c8d3ded8dff112bb615e8a18d11
4
- data.tar.gz: 3e16c2d59f4f4bd8d2df49625d9f6005edc98dec701212a6052f4abfd1cac6f3
3
+ metadata.gz: 4d5eb9055acbfc951ef6874c484b10f0c4c9a02f4886148fd917ec1f83cdc386
4
+ data.tar.gz: fe1db6e87b0788868ab99a4c5ee2c818fb2720b8aaa88a59d86023a8a4bc0f92
5
5
  SHA512:
6
- metadata.gz: 443645ed917ef9beea2d642ff8c4a7d2e57cc87559a09e74dfd75d20d18fb0ff11040eac6e825f3ff0af35637ea23757c88b24e78f35e32f64a7660168aa940a
7
- data.tar.gz: d11227a7a11c24bff167104e712f9d075a394130a196938ed71b360e6cbc82a8f930672187b92d0009ed5c14fee882ff23d3d88a3df25b1ddeff3a8839452b5b
6
+ metadata.gz: 17141eaefe36c4ee6e857fe36ae3ee7c9e500cb6b3dc07ba79b2ba20248cb1a01920d5f0441c09279af0ab04efa020ffe144aeef1f4fb00036094378c1e2fc65
7
+ data.tar.gz: 8bf37d00fc3b161eb84587eef5562cebe493adb6a0729897f00aed9d38a69b1847ba82781473a8d2b3deb4ad1acea764b014460a758bc12ef686028096974355
data/README.md CHANGED
@@ -87,15 +87,22 @@ bundle install
87
87
  Generate the installation files:
88
88
 
89
89
  ```bash
90
+ # Install with single database setup (default - recommended)
90
91
  rails generate rails_pulse:install
91
- ```
92
92
 
93
- Load the database schema:
93
+ # Or install with separate database setup
94
+ rails generate rails_pulse:install --database=separate
95
+ ```
94
96
 
97
+ **For single database setup (default):**
95
98
  ```bash
96
- rails db:prepare
99
+ rails db:migrate # Creates Rails Pulse tables in your main database
97
100
  ```
98
101
 
102
+ **For separate database setup:**
103
+ 1. Configure `config/database.yml` with your Rails Pulse database settings
104
+ 2. Run: `rails db:prepare` to create and load the schema
105
+
99
106
  Add the Rails Pulse route to your application:
100
107
 
101
108
  ```ruby
@@ -122,6 +129,10 @@ Rails Pulse automatically starts collecting performance data once installed. Acc
122
129
  http://localhost:3000/rails_pulse
123
130
  ```
124
131
 
132
+ **Database Setup:**
133
+ - **Single Database (default)**: Rails Pulse tables are created in your main database - no additional configuration needed
134
+ - **Separate Database**: See the [Separate Database Support](#separate-database-support) section for setup instructions
135
+
125
136
  ### Basic Configuration
126
137
 
127
138
  Customize Rails Pulse in `config/initializers/rails_pulse.rb`:
@@ -287,20 +298,36 @@ RailsPulse::CleanupJob.perform_later
287
298
 
288
299
  ## Separate Database Support
289
300
 
290
- Rails Pulse supports storing performance monitoring data in a **separate database**. By default, Rails Pulse stores data in your main application database alongside your existing tables.
301
+ Rails Pulse offers two database setup options to fit your application's needs:
302
+
303
+ ### Option 1: Single Database (Default - Recommended)
304
+
305
+ Stores Rails Pulse data in your main application database alongside your existing tables. This is the simplest setup and works great for most applications.
306
+
307
+ **Advantages:**
308
+ - Zero additional configuration required
309
+ - Simpler backup and deployment strategies
310
+ - Works with any database (SQLite, PostgreSQL, MySQL)
311
+
312
+ **Installation:**
313
+ ```bash
314
+ rails generate rails_pulse:install
315
+ rails db:migrate
316
+ ```
317
+
318
+ ### Option 2: Separate Database
291
319
 
292
- Use a separate database when you want:
320
+ Stores Rails Pulse data in a dedicated database, completely isolated from your main application.
293
321
 
322
+ **Use a separate database when you want:**
294
323
  - **Isolating monitoring data** from your main application database
295
324
  - **Using different database engines** optimized for time-series data
296
325
  - **Scaling monitoring independently** from your application
297
326
  - **Simplified backup strategies** with separate retention policies
298
327
 
299
- **For shared database setup (default)**, no database configuration is needed - simply run `rails db:prepare` after installation.
300
-
301
328
  ### Configuration
302
329
 
303
- To use a separate database, configure the `connects_to` option in your Rails Pulse initializer:
330
+ To use a separate database, install with the `--database=separate` flag, then configure the `connects_to` option in your Rails Pulse initializer:
304
331
 
305
332
  ```ruby
306
333
  RailsPulse.configure do |config|
@@ -356,17 +383,30 @@ production:
356
383
  pool: 5
357
384
  ```
358
385
 
359
- ### Schema Loading
386
+ ### Installation Steps
360
387
 
361
- After installation, load the Rails Pulse database schema:
388
+ **For separate database setup:**
362
389
 
363
- ```bash
364
- rails db:prepare
365
- ```
390
+ 1. **Generate installation files:**
391
+ ```bash
392
+ rails generate rails_pulse:install --database=separate
393
+ ```
394
+
395
+ 2. **Configure `config/database.yml`** (see examples above)
396
+
397
+ 3. **Create and load the schema:**
398
+ ```bash
399
+ rails db:prepare
400
+ ```
401
+ This automatically creates the database and loads the Rails Pulse schema.
402
+
403
+ **Schema Management:**
366
404
 
367
- This command works for both:
368
- - Shared database setup (default): Loads tables into your main application database
369
- - Separate database setup: Automatically loads tables into your configured Rails Pulse database
405
+ The schema file `db/rails_pulse_schema.rb` serves as your single source of truth for the database structure. It:
406
+ - Defines all Rails Pulse tables in one place
407
+ - Is loaded by the installation migration
408
+ - Should not be deleted or modified
409
+ - Future updates will provide migrations in `db/rails_pulse_migrate/`
370
410
 
371
411
  ## Testing
372
412
 
data/Rakefile CHANGED
@@ -1,109 +1,192 @@
1
1
  require "bundler/setup"
2
+ require "bundler/gem_tasks"
2
3
 
3
4
  # Load environment variables from .env file
4
5
  require "dotenv/load" if File.exist?(".env")
5
6
 
6
7
  APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
7
8
  load "rails/tasks/engine.rake"
8
- load "rails/tasks/statistics.rake"
9
9
 
10
- require "bundler/gem_tasks"
10
+ desc "Setup database for testing"
11
+ task :test_setup do
12
+ database = ENV['DB'] || 'sqlite3'
13
+
14
+ puts "\n" + "=" * 50
15
+ puts "🛠️ Rails Pulse Test Setup"
16
+ puts "=" * 50
17
+ puts "Database: #{database.upcase}"
18
+ puts "=" * 50
19
+ puts
20
+
21
+ begin
22
+ # Remove schema.rb to ensure clean migration
23
+ schema_file = "test/dummy/db/schema.rb"
24
+ if File.exist?(schema_file)
25
+ puts "🧹 Removing existing schema.rb file..."
26
+ File.delete(schema_file)
27
+ end
11
28
 
12
- # Test tasks
13
- namespace :test do
14
- desc "Run unit tests (models, helpers, services, instrumentation)"
15
- task :unit do
16
- sh "rails test test/models test/helpers test/services test/support test/instrumentation"
17
- end
29
+ case database.downcase
30
+ when 'sqlite3', 'sqlite'
31
+ puts "📦 Setting up SQLite database..."
32
+ sh "RAILS_ENV=test bin/rails db:drop db:create db:migrate"
33
+
34
+ when 'mysql2', 'mysql'
35
+ puts "🐬 Setting up MySQL database..."
36
+ sh "DB=mysql2 RAILS_ENV=test rails db:drop db:create db:migrate"
37
+
38
+ when 'postgresql', 'postgres'
39
+ puts "🐘 Setting up PostgreSQL database..."
40
+ sh "DB=postgresql RAILS_ENV=test rails db:drop db:create db:migrate"
41
+
42
+ else
43
+ puts "⚠️ Unknown database: #{database}"
44
+ puts "Supported databases: sqlite3, mysql2, postgresql"
45
+ exit 1
46
+ end
18
47
 
19
- desc "Run functional tests (controllers)"
20
- task :functional do
21
- sh "rails test test/controllers"
48
+ puts "\n✅ Database setup complete!"
49
+ puts "Ready to run: rake test"
50
+
51
+ rescue => e
52
+ puts "\n❌ Database setup failed!"
53
+ puts "Error: #{e.message}"
54
+ puts "\nTroubleshooting:"
55
+ puts "• Ensure #{database} is installed and running"
56
+ puts "• Check database credentials in test/dummy/config/database.yml"
57
+ puts "• Verify RAILS_ENV=test environment is configured"
58
+ exit 1
22
59
  end
60
+ end
23
61
 
24
- desc "Run integration tests"
25
- task :integration do
26
- sh "rails test test/integration test/system"
62
+ desc "Run test suite"
63
+ task :test do
64
+ database = ENV['DB'] || 'sqlite3'
65
+
66
+ # Get Rails version from Gemfile.lock or fallback
67
+ rails_version = begin
68
+ require 'rails'
69
+ Rails.version
70
+ rescue LoadError
71
+ # Try to get from Gemfile.lock
72
+ gemfile_lock = File.read('Gemfile.lock') rescue nil
73
+ if gemfile_lock && gemfile_lock.match(/rails \(([^)]+)\)/)
74
+ $1
75
+ else
76
+ 'unknown'
77
+ end
27
78
  end
28
79
 
29
- desc "Run all tests"
30
- task :all do
31
- sh "rails test"
80
+ puts "\n" + "=" * 50
81
+ puts "💛 Rails Pulse Test Suite"
82
+ puts "=" * 50
83
+ puts "Database: #{database.upcase}"
84
+ puts "Rails: #{rails_version}"
85
+ puts "=" * 50
86
+ puts
87
+
88
+ sh "rails test test/controllers test/helpers test/instrumentation test/jobs test/models test/services test/system"
89
+ end
90
+
91
+ desc "Setup database for specific Rails version and database"
92
+ task :test_setup_for_version, [ :database, :rails_version ] do |t, args|
93
+ database = args[:database] || ENV['DB'] || 'sqlite3'
94
+ rails_version = args[:rails_version] || 'rails-8-0'
95
+
96
+ puts "\n" + "=" * 50
97
+ puts "🛠️ Rails Pulse Test Setup"
98
+ puts "=" * 50
99
+ puts "Database: #{database.upcase}"
100
+ puts "Rails: #{rails_version.upcase.gsub('-', ' ')}"
101
+ puts "=" * 50
102
+ puts
103
+
104
+ begin
105
+ # Remove schema.rb to ensure clean migration
106
+ schema_file = "test/dummy/db/schema.rb"
107
+ if File.exist?(schema_file)
108
+ puts "🧹 Removing existing schema.rb file..."
109
+ File.delete(schema_file)
110
+ end
111
+
112
+ if rails_version == "rails-8-0" && database == "sqlite3"
113
+ # Use current default setup
114
+ puts "📦 Setting up #{database.upcase} database with Rails 8.0..."
115
+ sh "RAILS_ENV=test bin/rails db:drop db:create db:migrate"
116
+ else
117
+ # Use appraisal with specific database and Rails version
118
+ puts "📦 Setting up #{database.upcase} database with #{rails_version.upcase.gsub('-', ' ')}..."
119
+ sh "DB=#{database} bundle exec appraisal #{rails_version} rails db:drop db:create db:migrate RAILS_ENV=test"
120
+ end
121
+
122
+ puts "\n✅ Database setup complete for #{database.upcase} + #{rails_version.upcase.gsub('-', ' ')}!"
123
+
124
+ rescue => e
125
+ puts "\n❌ Database setup failed!"
126
+ puts "Error: #{e.message}"
127
+ exit 1
32
128
  end
129
+ end
33
130
 
34
- desc "Run tests across all database and Rails version combinations (local only - CI uses sqlite3 + postgresql)"
35
- task :matrix do
36
- databases = [ "sqlite3", "postgresql", "mysql2" ]
37
- rails_versions = [ "rails-7-2", "rails-8-0" ]
38
-
39
- failed_combinations = []
40
-
41
- databases.each do |database|
42
- rails_versions.each do |rails_version|
43
- puts "\n" + "=" * 80
44
- puts "🧪 Local Testing: #{database.upcase} + #{rails_version.upcase}"
45
- puts "(CI only tests SQLite3 + PostgreSQL for reliability)"
46
- puts "=" * 80
47
-
48
- begin
49
- gemfile = "gemfiles/#{rails_version.gsub('-', '_')}.gemfile"
50
-
51
- # Set environment variables
52
- env_vars = {
53
- "DB" => database,
54
- "BUNDLE_GEMFILE" => gemfile,
55
- "FORCE_DB_CONFIG" => "true"
56
- }
57
-
58
- # Add database-specific environment variables
59
- case database
60
- when "postgresql"
61
- env_vars.merge!({
62
- "POSTGRES_USERNAME" => ENV.fetch("POSTGRES_USERNAME", "postgres"),
63
- "POSTGRES_PASSWORD" => ENV.fetch("POSTGRES_PASSWORD", ""),
64
- "POSTGRES_HOST" => ENV.fetch("POSTGRES_HOST", "localhost"),
65
- "POSTGRES_PORT" => ENV.fetch("POSTGRES_PORT", "5432")
66
- })
67
- when "mysql2"
68
- env_vars.merge!({
69
- "MYSQL_USERNAME" => ENV.fetch("MYSQL_USERNAME", "root"),
70
- "MYSQL_PASSWORD" => ENV.fetch("MYSQL_PASSWORD", "password"),
71
- "MYSQL_HOST" => ENV.fetch("MYSQL_HOST", "localhost"),
72
- "MYSQL_PORT" => ENV.fetch("MYSQL_PORT", "3306")
73
- })
74
- end
75
-
76
- # Build environment string
77
- env_string = env_vars.map { |k, v| "#{k}=#{v}" }.join(" ")
78
-
79
- # Run the test command
80
- sh "#{env_string} bundle exec rails test:all"
81
-
82
- puts "✅ PASSED: #{database} + #{rails_version}"
83
-
84
- rescue => e
85
- puts "❌ FAILED: #{database} + #{rails_version}"
86
- puts "Error: #{e.message}"
87
- failed_combinations << "#{database} + #{rails_version}"
131
+ desc "Test all database and Rails version combinations"
132
+ task :test_matrix do
133
+ databases = %w[sqlite3 postgresql mysql2]
134
+ rails_versions = %w[rails-7-2 rails-8-0]
135
+
136
+ failed_combinations = []
137
+ total_combinations = databases.size * rails_versions.size
138
+ current = 0
139
+
140
+ puts "\n" + "=" * 60
141
+ puts "🚀 Rails Pulse Full Test Matrix"
142
+ puts "=" * 60
143
+ puts "Testing #{total_combinations} combinations..."
144
+ puts "=" * 60
145
+
146
+ databases.each do |database|
147
+ rails_versions.each do |rails_version|
148
+ current += 1
149
+
150
+ puts "\n[#{current}/#{total_combinations}] Testing: #{database.upcase} + #{rails_version.upcase.gsub('-', ' ')}"
151
+ puts "-" * 50
152
+
153
+ begin
154
+ # First setup the database for this specific combination
155
+ Rake::Task[:test_setup_for_version].reenable
156
+ Rake::Task[:test_setup_for_version].invoke(database, rails_version)
157
+
158
+ # Then run the tests
159
+ if rails_version == "rails-8-0" && database == "sqlite3"
160
+ # Current default setup
161
+ sh "bundle exec rake test"
162
+ else
163
+ # Use appraisal with specific database
164
+ sh "DB=#{database} bundle exec appraisal #{rails_version} rake test"
88
165
  end
89
- end
90
- end
91
166
 
92
- puts "\n" + "=" * 80
93
- puts "🏁 Local Test Matrix Results"
94
- puts "(CI automatically tests SQLite3 + PostgreSQL only)"
95
- puts "=" * 80
167
+ puts " PASSED: #{database} + #{rails_version}"
96
168
 
97
- if failed_combinations.empty?
98
- puts " All combinations passed!"
99
- else
100
- puts " Failed combinations:"
101
- failed_combinations.each { |combo| puts " - #{combo}" }
102
- exit 1
169
+ rescue => e
170
+ puts " FAILED: #{database} + #{rails_version}"
171
+ puts " Error: #{e.message}"
172
+ failed_combinations << "#{database} + #{rails_version}"
173
+ end
103
174
  end
104
175
  end
176
+
177
+ puts "\n" + "=" * 60
178
+ puts "🏁 Test Matrix Results"
179
+ puts "=" * 60
180
+
181
+ if failed_combinations.empty?
182
+ puts "🎉 All #{total_combinations} combinations passed!"
183
+ else
184
+ puts "✅ Passed: #{total_combinations - failed_combinations.size}/#{total_combinations}"
185
+ puts "❌ Failed combinations:"
186
+ failed_combinations.each { |combo| puts " • #{combo}" }
187
+ exit 1
188
+ end
105
189
  end
106
190
 
107
- # Override default test task
108
- desc "Run all tests"
109
- task test: "test:all"
191
+
192
+ task default: :test
@@ -55,7 +55,7 @@ module RailsPulse
55
55
  end
56
56
 
57
57
  def table_model
58
- show_action? ? Operation : Summary
58
+ Summary
59
59
  end
60
60
 
61
61
  def chart_class
@@ -76,7 +76,10 @@ module RailsPulse
76
76
  base_params[:avg_duration_gteq] = @start_duration if @start_duration && @start_duration > 0
77
77
 
78
78
  if show_action?
79
- base_params.merge(summarizable_id_eq: @query.id)
79
+ base_params.merge(
80
+ summarizable_id_eq: @query.id,
81
+ summarizable_type_eq: "RailsPulse::Query"
82
+ )
80
83
  else
81
84
  base_params
82
85
  end
@@ -84,13 +87,14 @@ module RailsPulse
84
87
 
85
88
  def build_table_ransack_params(ransack_params)
86
89
  if show_action?
87
- # For Operation model on show page
90
+ # For Summary model on show page
88
91
  params = ransack_params.merge(
89
- occurred_at_gteq: Time.at(@table_start_time),
90
- occurred_at_lt: Time.at(@table_end_time),
91
- query_id_eq: @query.id
92
+ period_start_gteq: Time.at(@table_start_time),
93
+ period_start_lt: Time.at(@table_end_time),
94
+ summarizable_id_eq: @query.id,
95
+ summarizable_type_eq: "RailsPulse::Query"
92
96
  )
93
- params[:duration_gteq] = @start_duration if @start_duration && @start_duration > 0
97
+ params[:avg_duration_gteq] = @start_duration if @start_duration && @start_duration > 0
94
98
  params
95
99
  else
96
100
  # For Summary model on index page
@@ -104,23 +108,13 @@ module RailsPulse
104
108
  end
105
109
 
106
110
  def default_table_sort
107
- show_action? ? "occurred_at desc" : "period_start desc"
111
+ "period_start desc"
108
112
  end
109
113
 
110
114
  def build_table_results
111
115
  if show_action?
112
- # Only show operations that belong to time periods where we have query summaries
113
- # This ensures the table data is consistent with the chart data
114
- @ransack_query.result
115
- .joins(<<~SQL)
116
- INNER JOIN rails_pulse_summaries ON
117
- rails_pulse_summaries.summarizable_id = rails_pulse_operations.query_id AND
118
- rails_pulse_summaries.summarizable_type = 'RailsPulse::Query' AND
119
- rails_pulse_summaries.period_type = '#{period_type}' AND
120
- rails_pulse_operations.occurred_at >= rails_pulse_summaries.period_start AND
121
- rails_pulse_operations.occurred_at < rails_pulse_summaries.period_end
122
- SQL
123
- .distinct
116
+ # For Summary model on show page - ransack params already include query ID and type filters
117
+ @ransack_query.result.where(period_type: period_type)
124
118
  else
125
119
  Queries::Tables::Index.new(
126
120
  ransack_query: @ransack_query,
@@ -5,13 +5,7 @@ module RailsPulse
5
5
  before_action :set_request, only: :show
6
6
 
7
7
  def index
8
- unless turbo_frame_request?
9
- @average_response_times_metric_card = RailsPulse::Routes::Cards::AverageResponseTimes.new(route: nil).to_metric_card
10
- @percentile_response_times_metric_card = RailsPulse::Routes::Cards::PercentileResponseTimes.new(route: nil).to_metric_card
11
- @request_count_totals_metric_card = RailsPulse::Routes::Cards::RequestCountTotals.new(route: nil).to_metric_card
12
- @error_rate_per_route_metric_card = RailsPulse::Routes::Cards::ErrorRatePerRoute.new(route: nil).to_metric_card
13
- end
14
-
8
+ setup_metric_cards
15
9
  setup_chart_and_table_data
16
10
  end
17
11
 
@@ -21,12 +15,22 @@ module RailsPulse
21
15
 
22
16
  private
23
17
 
18
+ def setup_metric_cards
19
+ return if turbo_frame_request?
20
+
21
+ @average_response_times_metric_card = RailsPulse::Routes::Cards::AverageResponseTimes.new(route: nil).to_metric_card
22
+ @percentile_response_times_metric_card = RailsPulse::Routes::Cards::PercentileResponseTimes.new(route: nil).to_metric_card
23
+ @request_count_totals_metric_card = RailsPulse::Routes::Cards::RequestCountTotals.new(route: nil).to_metric_card
24
+ @error_rate_per_route_metric_card = RailsPulse::Routes::Cards::ErrorRatePerRoute.new(route: nil).to_metric_card
25
+ end
26
+
27
+
24
28
  def chart_model
25
- Summary
29
+ RailsPulse::Summary
26
30
  end
27
31
 
28
32
  def table_model
29
- Request
33
+ RailsPulse::Request
30
34
  end
31
35
 
32
36
  def chart_class
@@ -64,27 +68,36 @@ module RailsPulse
64
68
  end
65
69
 
66
70
  def build_table_results
67
- # Only show requests that belong to time periods where we have overall request summaries
68
- # This ensures the table data is consistent with the chart data
69
- @ransack_query.result
70
- .joins(:route)
71
- .joins(<<~SQL)
72
- INNER JOIN rails_pulse_summaries ON
73
- rails_pulse_summaries.summarizable_id = 0 AND
74
- rails_pulse_summaries.summarizable_type = 'RailsPulse::Request' AND
75
- rails_pulse_summaries.period_type = '#{period_type}' AND
76
- rails_pulse_requests.occurred_at >= rails_pulse_summaries.period_start AND
77
- rails_pulse_requests.occurred_at < rails_pulse_summaries.period_end
78
- SQL
79
- .select(
80
- "rails_pulse_requests.id",
81
- "rails_pulse_requests.occurred_at",
82
- "rails_pulse_requests.duration",
83
- "rails_pulse_requests.status",
84
- "rails_pulse_requests.route_id",
85
- "rails_pulse_routes.path"
86
- )
87
- .distinct
71
+ base_query = @ransack_query.result.includes(:route)
72
+
73
+ # If sorting by route_path, we need to join the routes table
74
+ if @ransack_query.sorts.any? { |sort| sort.name == "route_path" }
75
+ base_query = base_query.joins(:route)
76
+ end
77
+
78
+ base_query
79
+ end
80
+
81
+
82
+ def setup_table_data(ransack_params)
83
+ table_ransack_params = build_table_ransack_params(ransack_params)
84
+ @ransack_query = table_model.ransack(table_ransack_params)
85
+
86
+ # Only apply default sort if not using Requests::Tables::Index (which handles its own sorting)
87
+ # For requests, we always use the Tables::Index on the index action
88
+ unless action_name == "index"
89
+ @ransack_query.sorts = default_table_sort if @ransack_query.sorts.empty?
90
+ end
91
+
92
+ table_results = build_table_results
93
+ handle_pagination
94
+
95
+ @pagy, @table_data = pagy(table_results, limit: session_pagination_limit)
96
+ end
97
+
98
+ def handle_pagination
99
+ method = pagination_method
100
+ send(method, params[:limit]) if params[:limit].present?
88
101
  end
89
102
 
90
103
  def set_request
@@ -25,7 +25,7 @@ module RailsPulse
25
25
 
26
26
  return crumbs if path_segments.empty?
27
27
 
28
- current_path = "/rails_pulse"
28
+ current_path = main_app.rails_pulse_path.chomp("/")
29
29
 
30
30
  path_segments.each_with_index do |segment, index|
31
31
  current_path += "/#{segment}"
@@ -27,7 +27,7 @@ module RailsPulse
27
27
  top: "10%",
28
28
  containLabel: true
29
29
  },
30
- animation: false
30
+ animation: true
31
31
  }
32
32
  end
33
33
 
@@ -3,7 +3,8 @@ module RailsPulse
3
3
  def human_readable_occurred_at(occurred_at)
4
4
  return "" unless occurred_at.present?
5
5
  time = occurred_at.is_a?(String) ? Time.parse(occurred_at) : occurred_at
6
- time.strftime("%b %d, %Y %l:%M %p")
6
+ # Convert to local system timezone (same as charts use)
7
+ time.getlocal.strftime("%b %d, %Y %l:%M %p")
7
8
  end
8
9
 
9
10
  def time_ago_in_words(time)
@@ -11,8 +12,10 @@ module RailsPulse
11
12
 
12
13
  # Convert to Time object if it's a string
13
14
  time = Time.parse(time.to_s) if time.is_a?(String)
15
+ # Convert to local system timezone for consistent calculation
16
+ time = time.getlocal
14
17
 
15
- seconds_ago = Time.current - time
18
+ seconds_ago = Time.now - time
16
19
 
17
20
  case seconds_ago
18
21
  when 0..59
@@ -25,5 +28,21 @@ module RailsPulse
25
28
  "#{(seconds_ago / 86400).to_i}d ago"
26
29
  end
27
30
  end
31
+
32
+ def human_readable_summary_period(summary)
33
+ return "" unless summary&.period_start&.present? && summary&.period_end&.present?
34
+
35
+ # Convert UTC times to local system timezone to match chart display
36
+ start_time = summary.period_start.getlocal
37
+ end_time = summary.period_end.getlocal
38
+
39
+
40
+ case summary.period_type
41
+ when "hour"
42
+ start_time.strftime("%b %e %Y, %l:%M %p") + " - " + end_time.strftime("%l:%M %p")
43
+ when "day"
44
+ start_time.strftime("%b %e, %Y")
45
+ end
46
+ end
28
47
  end
29
48
  end