rails_pulse 0.1.0 → 0.1.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 +74 -178
- data/Rakefile +75 -173
- data/app/assets/stylesheets/rails_pulse/application.css +0 -12
- data/app/controllers/concerns/chart_table_concern.rb +21 -4
- data/app/controllers/concerns/response_range_concern.rb +6 -3
- data/app/controllers/concerns/time_range_concern.rb +5 -10
- data/app/controllers/concerns/zoom_range_concern.rb +1 -1
- data/app/controllers/rails_pulse/application_controller.rb +8 -4
- data/app/controllers/rails_pulse/dashboard_controller.rb +12 -0
- data/app/controllers/rails_pulse/queries_controller.rb +65 -50
- data/app/controllers/rails_pulse/requests_controller.rb +24 -12
- data/app/controllers/rails_pulse/routes_controller.rb +59 -24
- data/app/helpers/rails_pulse/application_helper.rb +0 -1
- data/app/helpers/rails_pulse/chart_formatters.rb +3 -3
- data/app/helpers/rails_pulse/chart_helper.rb +6 -2
- data/app/helpers/rails_pulse/status_helper.rb +10 -4
- data/app/javascript/rails_pulse/controllers/index_controller.js +117 -33
- data/app/javascript/rails_pulse/controllers/pagination_controller.js +17 -27
- data/app/jobs/rails_pulse/backfill_summaries_job.rb +41 -0
- data/app/jobs/rails_pulse/summary_job.rb +53 -0
- data/app/models/rails_pulse/dashboard/charts/average_response_time.rb +28 -7
- data/app/models/rails_pulse/dashboard/charts/p95_response_time.rb +18 -22
- data/app/models/rails_pulse/dashboard/tables/slow_queries.rb +20 -9
- data/app/models/rails_pulse/dashboard/tables/slow_routes.rb +19 -7
- data/app/models/rails_pulse/operation.rb +1 -1
- data/app/models/rails_pulse/queries/cards/average_query_times.rb +47 -23
- data/app/models/rails_pulse/queries/cards/execution_rate.rb +33 -26
- data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +34 -45
- data/app/models/rails_pulse/queries/charts/average_query_times.rb +23 -97
- data/app/models/rails_pulse/queries/tables/index.rb +74 -0
- data/app/models/rails_pulse/query.rb +1 -0
- data/app/models/rails_pulse/requests/charts/average_response_times.rb +23 -84
- data/app/models/rails_pulse/route.rb +1 -6
- data/app/models/rails_pulse/routes/cards/average_response_times.rb +45 -23
- data/app/models/rails_pulse/routes/cards/error_rate_per_route.rb +38 -45
- data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +34 -47
- data/app/models/rails_pulse/routes/cards/request_count_totals.rb +30 -25
- data/app/models/rails_pulse/routes/charts/average_response_times.rb +23 -100
- data/app/models/rails_pulse/routes/tables/index.rb +57 -40
- data/app/models/rails_pulse/summary.rb +143 -0
- data/app/services/rails_pulse/summary_service.rb +199 -0
- data/app/views/layouts/rails_pulse/application.html.erb +4 -4
- data/app/views/rails_pulse/components/_empty_state.html.erb +11 -0
- data/app/views/rails_pulse/components/_metric_card.html.erb +10 -24
- data/app/views/rails_pulse/dashboard/index.html.erb +54 -36
- data/app/views/rails_pulse/queries/_show_table.html.erb +1 -1
- data/app/views/rails_pulse/queries/_table.html.erb +10 -12
- data/app/views/rails_pulse/queries/index.html.erb +41 -34
- data/app/views/rails_pulse/queries/show.html.erb +38 -31
- data/app/views/rails_pulse/requests/_operations.html.erb +32 -26
- data/app/views/rails_pulse/requests/_table.html.erb +1 -3
- data/app/views/rails_pulse/requests/index.html.erb +42 -34
- data/app/views/rails_pulse/routes/_table.html.erb +13 -13
- data/app/views/rails_pulse/routes/index.html.erb +43 -35
- data/app/views/rails_pulse/routes/show.html.erb +42 -35
- data/config/initializers/rails_pulse.rb +0 -12
- data/db/migrate/20241222000001_create_rails_pulse_summaries.rb +54 -0
- data/db/rails_pulse_schema.rb +121 -0
- data/lib/generators/rails_pulse/install_generator.rb +41 -4
- data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +60 -0
- data/lib/generators/rails_pulse/templates/rails_pulse.rb +0 -12
- data/lib/rails_pulse/configuration.rb +6 -12
- data/lib/rails_pulse/engine.rb +0 -1
- data/lib/rails_pulse/version.rb +1 -1
- data/lib/tasks/rails_pulse.rake +58 -0
- 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 +1 -1
- data/public/rails-pulse-assets/rails-pulse.js.map +3 -3
- data/public/rails-pulse-assets/search.svg +43 -0
- metadata +28 -12
- data/app/controllers/rails_pulse/caches_controller.rb +0 -115
- data/app/helpers/rails_pulse/cached_component_helper.rb +0 -73
- data/app/models/rails_pulse/component_cache_key.rb +0 -33
- data/app/views/rails_pulse/caches/show.html.erb +0 -9
- data/db/migrate/20250227235904_create_routes.rb +0 -12
- data/db/migrate/20250227235915_create_requests.rb +0 -19
- data/db/migrate/20250228000000_create_queries.rb +0 -14
- data/db/migrate/20250228000056_create_operations.rb +0 -24
- data/lib/rails_pulse/migration.rb +0 -29
data/Rakefile
CHANGED
@@ -1,207 +1,109 @@
|
|
1
1
|
require "bundler/setup"
|
2
2
|
|
3
|
+
# Load environment variables from .env file
|
4
|
+
require "dotenv/load" if File.exist?(".env")
|
5
|
+
|
3
6
|
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
4
7
|
load "rails/tasks/engine.rake"
|
5
|
-
|
6
8
|
load "rails/tasks/statistics.rake"
|
7
9
|
|
8
10
|
require "bundler/gem_tasks"
|
9
11
|
|
10
12
|
# Test tasks
|
11
13
|
namespace :test do
|
12
|
-
desc "Run unit tests (
|
14
|
+
desc "Run unit tests (models, helpers, services, instrumentation)"
|
13
15
|
task :unit do
|
14
|
-
|
15
|
-
ENV["MEMORY_DATABASE"] = "true"
|
16
|
-
Rake::Task["test:run_unit"].invoke
|
16
|
+
sh "rails test test/models test/helpers test/services test/support test/instrumentation"
|
17
17
|
end
|
18
18
|
|
19
|
-
desc "Run functional tests"
|
19
|
+
desc "Run functional tests (controllers)"
|
20
20
|
task :functional do
|
21
|
-
|
22
|
-
ENV["MEMORY_DATABASE"] = "true"
|
23
|
-
Rake::Task["test:run_functional"].invoke
|
21
|
+
sh "rails test test/controllers"
|
24
22
|
end
|
25
23
|
|
26
24
|
desc "Run integration tests"
|
27
25
|
task :integration do
|
28
|
-
|
29
|
-
ENV["MEMORY_DATABASE"] = "false"
|
30
|
-
Rake::Task["test:run_integration"].invoke
|
26
|
+
sh "rails test test/integration test/system"
|
31
27
|
end
|
32
28
|
|
33
|
-
desc "Run all tests
|
29
|
+
desc "Run all tests"
|
34
30
|
task :all do
|
35
|
-
|
36
|
-
puts "\n=== Running #{test_type} tests ==="
|
37
|
-
Rake::Task["test:#{test_type}"].invoke
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
desc "Run unit tests (alias for test:unit)"
|
42
|
-
task units: :unit
|
43
|
-
|
44
|
-
desc "Run functional tests (alias for test:functional)"
|
45
|
-
task functionals: :functional
|
46
|
-
|
47
|
-
desc "Run integration tests (alias for test:integration)"
|
48
|
-
task integrations: :integration
|
49
|
-
|
50
|
-
desc "Run tests with speed optimizations"
|
51
|
-
task fast: :unit
|
52
|
-
|
53
|
-
|
54
|
-
# Internal tasks
|
55
|
-
task :run_unit do
|
56
|
-
sh "rails test test/models test/middleware test/lib test/support"
|
57
|
-
end
|
58
|
-
|
59
|
-
task :run_functional do
|
60
|
-
sh "rails test test/controllers test/helpers"
|
61
|
-
end
|
62
|
-
|
63
|
-
task :run_integration do
|
64
|
-
sh "rails test test/integration"
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
# Speed-optimized test task (unit tests only)
|
69
|
-
desc "Run fast unit tests only"
|
70
|
-
task test_fast: "test:unit"
|
71
|
-
|
72
|
-
# Override default test task to run all tests
|
73
|
-
desc "Run all tests"
|
74
|
-
task test: "test:all"
|
75
|
-
|
76
|
-
# Simplified database testing tasks
|
77
|
-
namespace :test do
|
78
|
-
desc "Run tests with SQLite (default)"
|
79
|
-
task :sqlite do
|
80
|
-
puts "🗂️ Testing with SQLite..."
|
81
|
-
Rake::Task["test:all"].invoke
|
82
|
-
end
|
83
|
-
|
84
|
-
desc "Run tests with PostgreSQL"
|
85
|
-
task :postgresql do
|
86
|
-
puts "🐘 Testing with PostgreSQL..."
|
87
|
-
env = ENV.to_h.merge(
|
88
|
-
"DATABASE_ADAPTER" => "postgresql",
|
89
|
-
"FORCE_DB_CONFIG" => "true"
|
90
|
-
)
|
91
|
-
system(env, "rails test:all") || raise("PostgreSQL tests failed")
|
92
|
-
end
|
93
|
-
|
94
|
-
desc "Run tests with MySQL"
|
95
|
-
task :mysql do
|
96
|
-
puts "🐬 Testing with MySQL..."
|
97
|
-
env = ENV.to_h.merge(
|
98
|
-
"DATABASE_ADAPTER" => "mysql2",
|
99
|
-
"FORCE_DB_CONFIG" => "true",
|
100
|
-
"PARALLEL_WORKERS" => "1"
|
101
|
-
)
|
102
|
-
system(env, "rails test:all") || raise("MySQL tests failed")
|
31
|
+
sh "rails test"
|
103
32
|
end
|
104
33
|
|
105
|
-
desc "Run
|
34
|
+
desc "Run tests across all database and Rails version combinations (local only - CI uses sqlite3 + postgresql)"
|
106
35
|
task :matrix do
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
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}"
|
88
|
+
end
|
130
89
|
end
|
131
90
|
end
|
132
91
|
|
133
|
-
|
134
|
-
puts "
|
135
|
-
puts "
|
136
|
-
puts "="*
|
137
|
-
results.each do |db, status|
|
138
|
-
puts "#{status} #{db}"
|
139
|
-
end
|
140
|
-
puts "="*60
|
92
|
+
puts "\n" + "=" * 80
|
93
|
+
puts "🏁 Local Test Matrix Results"
|
94
|
+
puts "(CI automatically tests SQLite3 + PostgreSQL only)"
|
95
|
+
puts "=" * 80
|
141
96
|
|
142
|
-
|
143
|
-
|
144
|
-
if failed_count > 0
|
145
|
-
puts "\n❌ #{failed_count} database(s) failed tests"
|
146
|
-
exit 1
|
97
|
+
if failed_combinations.empty?
|
98
|
+
puts "✅ All combinations passed!"
|
147
99
|
else
|
148
|
-
puts "
|
149
|
-
|
150
|
-
end
|
151
|
-
|
152
|
-
desc "Run full test matrix (SQLite + PostgreSQL + MySQL)"
|
153
|
-
task :matrix_full do
|
154
|
-
puts "\n🧪 Running full test matrix...\n"
|
155
|
-
|
156
|
-
databases = [
|
157
|
-
{ name: "SQLite", env: {}, emoji: "🗂️" },
|
158
|
-
{ name: "PostgreSQL", env: { "DATABASE_ADAPTER" => "postgresql", "FORCE_DB_CONFIG" => "true" }, emoji: "🐘" },
|
159
|
-
{ name: "MySQL", env: { "DATABASE_ADAPTER" => "mysql2", "FORCE_DB_CONFIG" => "true", "PARALLEL_WORKERS" => "1" }, emoji: "🐬" }
|
160
|
-
]
|
161
|
-
|
162
|
-
results = {}
|
163
|
-
|
164
|
-
databases.each do |db|
|
165
|
-
puts "\n" + "="*60
|
166
|
-
puts "#{db[:emoji]} Testing with #{db[:name]}..."
|
167
|
-
puts "="*60
|
168
|
-
|
169
|
-
begin
|
170
|
-
env = ENV.to_h.merge(db[:env])
|
171
|
-
success = system(env, "rails test:all")
|
172
|
-
|
173
|
-
results[db[:name]] = success ? "✅ PASSED" : "❌ FAILED"
|
174
|
-
rescue => e
|
175
|
-
results[db[:name]] = "❌ FAILED"
|
176
|
-
puts "Error: #{e.message}"
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
# Print summary
|
181
|
-
puts "\n" + "="*60
|
182
|
-
puts "🏁 FULL TEST MATRIX SUMMARY"
|
183
|
-
puts "="*60
|
184
|
-
results.each do |db, status|
|
185
|
-
puts "#{status} #{db}"
|
186
|
-
end
|
187
|
-
puts "="*60
|
188
|
-
|
189
|
-
# Fail if any tests failed
|
190
|
-
failed_count = results.values.count { |status| status.include?("FAILED") }
|
191
|
-
if failed_count > 0
|
192
|
-
puts "\n❌ #{failed_count} database(s) failed tests"
|
100
|
+
puts "❌ Failed combinations:"
|
101
|
+
failed_combinations.each { |combo| puts " - #{combo}" }
|
193
102
|
exit 1
|
194
|
-
else
|
195
|
-
puts "\n🎉 All databases passed!"
|
196
103
|
end
|
197
104
|
end
|
198
105
|
end
|
199
106
|
|
200
|
-
#
|
201
|
-
|
202
|
-
|
203
|
-
end
|
204
|
-
|
205
|
-
def postgresql_available?
|
206
|
-
system("psql --version > /dev/null 2>&1")
|
207
|
-
end
|
107
|
+
# Override default test task
|
108
|
+
desc "Run all tests"
|
109
|
+
task test: "test:all"
|
@@ -80,18 +80,6 @@ a:hover {
|
|
80
80
|
position:absolute;
|
81
81
|
top:0
|
82
82
|
}
|
83
|
-
.bar.db {
|
84
|
-
background-color:#92c282
|
85
|
-
}
|
86
|
-
.bar.app {
|
87
|
-
background-color:#00adc4
|
88
|
-
}
|
89
|
-
.bar.gc {
|
90
|
-
background-color:#323333
|
91
|
-
}
|
92
|
-
.bar.view {
|
93
|
-
background-color:#b48da3
|
94
|
-
}
|
95
83
|
.bar:first-child {
|
96
84
|
border-bottom-left-radius:1px;
|
97
85
|
border-top-left-radius:1px
|
@@ -16,14 +16,17 @@ module ChartTableConcern
|
|
16
16
|
def setup_chart_and_table_data
|
17
17
|
ransack_params = params[:q] || {}
|
18
18
|
|
19
|
-
# Setup chart data first using original time range (no sorting from table)
|
20
19
|
unless turbo_frame_request?
|
21
|
-
|
20
|
+
# Setup chart data first using original time range (no sorting from table)
|
22
21
|
setup_chart_data(ransack_params)
|
22
|
+
setup_chart_formatters
|
23
23
|
end
|
24
24
|
|
25
25
|
# Setup table data using zoom parameters if present, otherwise use chart parameters
|
26
26
|
setup_table_data(ransack_params)
|
27
|
+
|
28
|
+
# Set flag to determine if we have meaningful data to display
|
29
|
+
@has_data = has_meaningful_data?
|
27
30
|
end
|
28
31
|
|
29
32
|
def setup_chart_data(ransack_params)
|
@@ -31,7 +34,10 @@ module ChartTableConcern
|
|
31
34
|
chart_ransack_query = chart_model.ransack(chart_ransack_params)
|
32
35
|
@chart_data = chart_class.new(
|
33
36
|
ransack_query: chart_ransack_query,
|
34
|
-
|
37
|
+
period_type: period_type,
|
38
|
+
start_time: @start_time,
|
39
|
+
end_time: @end_time,
|
40
|
+
start_duration: @start_duration,
|
35
41
|
**chart_options
|
36
42
|
).to_rails_chart
|
37
43
|
end
|
@@ -43,6 +49,7 @@ module ChartTableConcern
|
|
43
49
|
|
44
50
|
table_results = build_table_results
|
45
51
|
handle_pagination
|
52
|
+
|
46
53
|
@pagy, @table_data = pagy(table_results, limit: session_pagination_limit)
|
47
54
|
end
|
48
55
|
|
@@ -56,14 +63,24 @@ module ChartTableConcern
|
|
56
63
|
end
|
57
64
|
|
58
65
|
def setup_chart_formatters
|
59
|
-
@xaxis_formatter = RailsPulse::ChartFormatters.
|
66
|
+
@xaxis_formatter = RailsPulse::ChartFormatters.period_as_time_or_date(@time_diff_hours)
|
60
67
|
@tooltip_formatter = RailsPulse::ChartFormatters.tooltip_as_time_or_date_with_marker(@time_diff_hours)
|
61
68
|
end
|
62
69
|
|
70
|
+
def period_type
|
71
|
+
@time_diff_hours <= 25 ? :hour : :day
|
72
|
+
end
|
73
|
+
|
63
74
|
def group_by
|
64
75
|
@time_diff_hours <= 25 ? :group_by_hour : :group_by_day
|
65
76
|
end
|
66
77
|
|
78
|
+
def has_meaningful_data?
|
79
|
+
has_chart_data = @chart_data && @chart_data.values.any? { |v| v > 0 }
|
80
|
+
has_table_data = @table_data && @table_data.any?
|
81
|
+
has_chart_data || has_table_data
|
82
|
+
end
|
83
|
+
|
67
84
|
def handle_pagination
|
68
85
|
method = pagination_method
|
69
86
|
send(method, params[:limit]) if params[:limit].present?
|
@@ -5,10 +5,13 @@ module ResponseRangeConcern
|
|
5
5
|
ransack_params = params[:q] || {}
|
6
6
|
thresholds = RailsPulse.configuration.public_send("#{type}_thresholds")
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
# Check both avg_duration (for Summary) and duration (for Request/Operation)
|
9
|
+
duration_param = ransack_params[:avg_duration] || ransack_params[:duration]
|
10
|
+
|
11
|
+
if duration_param.present?
|
12
|
+
selected_range = duration_param
|
10
13
|
start_duration =
|
11
|
-
case
|
14
|
+
case duration_param.to_sym
|
12
15
|
when :slow then thresholds[:slow]
|
13
16
|
when :very_slow then thresholds[:very_slow]
|
14
17
|
when :critical then thresholds[:critical]
|
@@ -6,8 +6,7 @@ module TimeRangeConcern
|
|
6
6
|
const_set(:TIME_RANGE_OPTIONS, [
|
7
7
|
[ "Last 24 hours", :last_day ],
|
8
8
|
[ "Last Week", :last_week ],
|
9
|
-
[ "Last Month", :last_month ]
|
10
|
-
[ "All Time", :all_time ]
|
9
|
+
[ "Last Month", :last_month ]
|
11
10
|
].freeze)
|
12
11
|
end
|
13
12
|
|
@@ -18,23 +17,19 @@ module TimeRangeConcern
|
|
18
17
|
|
19
18
|
ransack_params = params[:q] || {}
|
20
19
|
|
21
|
-
if ransack_params[:
|
22
|
-
# Custom time range from routes index chart zoom which filters requests through an association
|
23
|
-
start_time = parse_time_param(ransack_params[:requests_occurred_at_gteq])
|
24
|
-
end_time = parse_time_param(ransack_params[:requests_occurred_at_lt])
|
25
|
-
elsif ransack_params[:occurred_at_gteq].present?
|
20
|
+
if ransack_params[:occurred_at_gteq].present?
|
26
21
|
# Custom time range from chart zoom where there is no association
|
27
22
|
start_time = parse_time_param(ransack_params[:occurred_at_gteq])
|
28
23
|
end_time = parse_time_param(ransack_params[:occurred_at_lt])
|
29
|
-
elsif ransack_params[:
|
24
|
+
elsif ransack_params[:period_start_range]
|
30
25
|
# Predefined time range from dropdown
|
31
|
-
selected_time_range = ransack_params[:
|
26
|
+
selected_time_range = ransack_params[:period_start_range]
|
32
27
|
start_time =
|
33
28
|
case selected_time_range.to_sym
|
34
29
|
when :last_day then 1.day.ago
|
35
30
|
when :last_week then 1.week.ago
|
36
31
|
when :last_month then 1.month.ago
|
37
|
-
|
32
|
+
else 1.day.ago # Default fallback
|
38
33
|
end
|
39
34
|
end
|
40
35
|
|
@@ -2,8 +2,9 @@ module RailsPulse
|
|
2
2
|
class ApplicationController < ActionController::Base
|
3
3
|
before_action :authenticate_rails_pulse_user!
|
4
4
|
|
5
|
-
def set_pagination_limit
|
6
|
-
|
5
|
+
def set_pagination_limit(limit = nil)
|
6
|
+
limit = limit || params[:limit]
|
7
|
+
session[:pagination_limit] = limit.to_i if limit.present?
|
7
8
|
render json: { status: "ok" }
|
8
9
|
end
|
9
10
|
|
@@ -54,8 +55,11 @@ module RailsPulse
|
|
54
55
|
end
|
55
56
|
|
56
57
|
def session_pagination_limit
|
57
|
-
#
|
58
|
-
session[:pagination_limit] || 10
|
58
|
+
# Use URL param if present, otherwise session, otherwise default
|
59
|
+
limit = params[:limit].presence || session[:pagination_limit] || 10
|
60
|
+
# Update session if URL param was used
|
61
|
+
session[:pagination_limit] = limit.to_i if params[:limit].present?
|
62
|
+
limit.to_i
|
59
63
|
end
|
60
64
|
|
61
65
|
def store_pagination_limit(limit)
|
@@ -1,6 +1,18 @@
|
|
1
1
|
module RailsPulse
|
2
2
|
class DashboardController < ApplicationController
|
3
3
|
def index
|
4
|
+
@average_query_times_metric_card = RailsPulse::Routes::Cards::AverageResponseTimes.new(route: nil).to_metric_card
|
5
|
+
@percentile_response_times_metric_card = RailsPulse::Routes::Cards::PercentileResponseTimes.new(route: nil).to_metric_card
|
6
|
+
@request_count_totals_metric_card = RailsPulse::Routes::Cards::RequestCountTotals.new(route: nil).to_metric_card
|
7
|
+
@error_rate_per_route_metric_card = RailsPulse::Routes::Cards::ErrorRatePerRoute.new(route: nil).to_metric_card
|
8
|
+
|
9
|
+
# Generate chart data for inline rendering
|
10
|
+
@average_response_time_chart_data = RailsPulse::Dashboard::Charts::AverageResponseTime.new.to_chart_data
|
11
|
+
@p95_response_time_chart_data = RailsPulse::Dashboard::Charts::P95ResponseTime.new.to_chart_data
|
12
|
+
|
13
|
+
# Generate table data for inline rendering
|
14
|
+
@slow_routes_table_data = RailsPulse::Dashboard::Tables::SlowRoutes.new.to_table_data
|
15
|
+
@slow_queries_table_data = RailsPulse::Dashboard::Tables::SlowQueries.new.to_table_data
|
4
16
|
end
|
5
17
|
end
|
6
18
|
end
|
@@ -5,21 +5,23 @@ module RailsPulse
|
|
5
5
|
before_action :set_query, only: :show
|
6
6
|
|
7
7
|
def index
|
8
|
+
setup_metric_cards
|
8
9
|
setup_chart_and_table_data
|
9
10
|
end
|
10
11
|
|
11
12
|
def show
|
13
|
+
setup_metric_cards
|
12
14
|
setup_chart_and_table_data
|
13
15
|
end
|
14
16
|
|
15
17
|
private
|
16
18
|
|
17
19
|
def chart_model
|
18
|
-
|
20
|
+
Summary
|
19
21
|
end
|
20
22
|
|
21
23
|
def table_model
|
22
|
-
show_action? ? Operation :
|
24
|
+
show_action? ? Operation : Summary
|
23
25
|
end
|
24
26
|
|
25
27
|
def chart_class
|
@@ -31,73 +33,67 @@ module RailsPulse
|
|
31
33
|
end
|
32
34
|
|
33
35
|
def build_chart_ransack_params(ransack_params)
|
34
|
-
base_params = ransack_params.except(:s)
|
36
|
+
base_params = ransack_params.except(:s).merge(
|
37
|
+
period_start_gteq: Time.at(@start_time),
|
38
|
+
period_start_lt: Time.at(@end_time)
|
39
|
+
)
|
40
|
+
|
41
|
+
# Only add duration filter if we have a meaningful threshold
|
42
|
+
base_params[:avg_duration_gteq] = @start_duration if @start_duration && @start_duration > 0
|
35
43
|
|
36
44
|
if show_action?
|
37
|
-
base_params.merge(
|
38
|
-
query_id_eq: @query.id,
|
39
|
-
occurred_at_gteq: @start_time,
|
40
|
-
occurred_at_lt: @end_time,
|
41
|
-
duration_gteq: @start_duration
|
42
|
-
)
|
45
|
+
base_params.merge(summarizable_id_eq: @query.id)
|
43
46
|
else
|
44
|
-
base_params
|
45
|
-
operations_occurred_at_gteq: @start_time,
|
46
|
-
operations_occurred_at_lt: @end_time,
|
47
|
-
operations_duration_gteq: @start_duration
|
48
|
-
)
|
47
|
+
base_params
|
49
48
|
end
|
50
49
|
end
|
51
50
|
|
52
51
|
def build_table_ransack_params(ransack_params)
|
53
52
|
if show_action?
|
54
|
-
|
55
|
-
|
56
|
-
occurred_at_gteq: @table_start_time,
|
57
|
-
occurred_at_lt: @table_end_time,
|
58
|
-
|
53
|
+
# For Operation model on show page
|
54
|
+
params = ransack_params.merge(
|
55
|
+
occurred_at_gteq: Time.at(@table_start_time),
|
56
|
+
occurred_at_lt: Time.at(@table_end_time),
|
57
|
+
query_id_eq: @query.id
|
59
58
|
)
|
59
|
+
params[:duration_gteq] = @start_duration if @start_duration && @start_duration > 0
|
60
|
+
params
|
60
61
|
else
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
62
|
+
# For Summary model on index page
|
63
|
+
params = ransack_params.merge(
|
64
|
+
period_start_gteq: Time.at(@table_start_time),
|
65
|
+
period_start_lt: Time.at(@table_end_time)
|
65
66
|
)
|
67
|
+
params[:avg_duration_gteq] = @start_duration if @start_duration && @start_duration > 0
|
68
|
+
params
|
66
69
|
end
|
67
70
|
end
|
68
71
|
|
69
72
|
def default_table_sort
|
70
|
-
"occurred_at desc"
|
73
|
+
show_action? ? "occurred_at desc" : "period_start desc"
|
71
74
|
end
|
72
75
|
|
73
76
|
def build_table_results
|
74
77
|
if show_action?
|
75
|
-
@ransack_query.result
|
78
|
+
@ransack_query.result
|
76
79
|
else
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
.group("rails_pulse_queries.id, rails_pulse_queries.normalized_sql, rails_pulse_queries.created_at, rails_pulse_queries.updated_at")
|
84
|
-
.select(
|
85
|
-
"rails_pulse_queries.*",
|
86
|
-
optimized_aggregations_sql
|
87
|
-
)
|
80
|
+
Queries::Tables::Index.new(
|
81
|
+
ransack_query: @ransack_query,
|
82
|
+
period_type: period_type,
|
83
|
+
start_time: @start_time,
|
84
|
+
params: params
|
85
|
+
).to_table
|
88
86
|
end
|
89
87
|
end
|
90
88
|
|
91
89
|
private
|
92
90
|
|
93
|
-
def
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
"MAX(rails_pulse_operations.occurred_at) AS occurred_at"
|
100
|
-
].join(", ")
|
91
|
+
def setup_metric_cards
|
92
|
+
return if turbo_frame_request?
|
93
|
+
|
94
|
+
@average_query_times_metric_card = RailsPulse::Queries::Cards::AverageQueryTimes.new(query: @query).to_metric_card
|
95
|
+
@percentile_query_times_metric_card = RailsPulse::Queries::Cards::PercentileQueryTimes.new(query: @query).to_metric_card
|
96
|
+
@execution_rate_metric_card = RailsPulse::Queries::Cards::ExecutionRate.new(query: @query).to_metric_card
|
101
97
|
end
|
102
98
|
|
103
99
|
def show_action?
|
@@ -108,14 +104,33 @@ module RailsPulse
|
|
108
104
|
show_action? ? :set_pagination_limit : :store_pagination_limit
|
109
105
|
end
|
110
106
|
|
111
|
-
def
|
112
|
-
|
107
|
+
def setup_table_data(ransack_params)
|
108
|
+
table_ransack_params = build_table_ransack_params(ransack_params)
|
109
|
+
@ransack_query = table_model.ransack(table_ransack_params)
|
110
|
+
|
111
|
+
# Only apply default sort if not using Queries::Tables::Index (which handles its own sorting)
|
112
|
+
if show_action?
|
113
|
+
@ransack_query.sorts = default_table_sort if @ransack_query.sorts.empty?
|
114
|
+
end
|
115
|
+
|
116
|
+
table_results = build_table_results
|
117
|
+
handle_pagination
|
118
|
+
|
119
|
+
@pagy, @table_data = pagy(table_results, limit: session_pagination_limit)
|
120
|
+
end
|
121
|
+
|
122
|
+
def handle_pagination
|
123
|
+
method = pagination_method
|
124
|
+
send(method, params[:limit]) if params[:limit].present?
|
113
125
|
end
|
114
126
|
|
115
|
-
def
|
116
|
-
@
|
117
|
-
@
|
118
|
-
|
127
|
+
def setup_time_and_response_ranges
|
128
|
+
@start_time, @end_time, @selected_time_range, @time_diff_hours = setup_time_range
|
129
|
+
@start_duration, @selected_response_range = setup_duration_range(:query)
|
130
|
+
end
|
131
|
+
|
132
|
+
def set_query
|
133
|
+
@query = Query.find(params[:id])
|
119
134
|
end
|
120
135
|
end
|
121
136
|
end
|