rails_pulse 0.2.4 → 0.2.5.pre.pre.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.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +269 -12
  3. data/Rakefile +142 -8
  4. data/app/assets/stylesheets/rails_pulse/components/table.css +16 -1
  5. data/app/assets/stylesheets/rails_pulse/components/tags.css +7 -2
  6. data/app/assets/stylesheets/rails_pulse/components/utilities.css +3 -0
  7. data/app/controllers/concerns/chart_table_concern.rb +2 -1
  8. data/app/controllers/rails_pulse/application_controller.rb +11 -1
  9. data/app/controllers/rails_pulse/assets_controller.rb +18 -2
  10. data/app/controllers/rails_pulse/job_runs_controller.rb +37 -0
  11. data/app/controllers/rails_pulse/jobs_controller.rb +80 -0
  12. data/app/controllers/rails_pulse/operations_controller.rb +43 -31
  13. data/app/controllers/rails_pulse/queries_controller.rb +1 -1
  14. data/app/controllers/rails_pulse/requests_controller.rb +3 -9
  15. data/app/controllers/rails_pulse/routes_controller.rb +1 -1
  16. data/app/controllers/rails_pulse/tags_controller.rb +31 -5
  17. data/app/helpers/rails_pulse/application_helper.rb +32 -1
  18. data/app/helpers/rails_pulse/breadcrumbs_helper.rb +15 -1
  19. data/app/helpers/rails_pulse/status_helper.rb +16 -0
  20. data/app/helpers/rails_pulse/tags_helper.rb +39 -1
  21. data/app/javascript/rails_pulse/controllers/chart_controller.js +112 -8
  22. data/app/models/concerns/rails_pulse/taggable.rb +25 -2
  23. data/app/models/rails_pulse/charts/operations_chart.rb +33 -0
  24. data/app/models/rails_pulse/dashboard/charts/p95_response_time.rb +1 -2
  25. data/app/models/rails_pulse/dashboard/tables/slow_routes.rb +1 -1
  26. data/app/models/rails_pulse/job.rb +85 -0
  27. data/app/models/rails_pulse/job_run.rb +76 -0
  28. data/app/models/rails_pulse/jobs/cards/average_duration.rb +85 -0
  29. data/app/models/rails_pulse/jobs/cards/base.rb +70 -0
  30. data/app/models/rails_pulse/jobs/cards/failure_rate.rb +85 -0
  31. data/app/models/rails_pulse/jobs/cards/total_jobs.rb +74 -0
  32. data/app/models/rails_pulse/jobs/cards/total_runs.rb +48 -0
  33. data/app/models/rails_pulse/operation.rb +16 -3
  34. data/app/models/rails_pulse/queries/cards/average_query_times.rb +3 -3
  35. data/app/models/rails_pulse/queries/cards/execution_rate.rb +1 -1
  36. data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +1 -1
  37. data/app/models/rails_pulse/queries/tables/index.rb +2 -1
  38. data/app/models/rails_pulse/query.rb +10 -1
  39. data/app/models/rails_pulse/routes/cards/average_response_times.rb +3 -2
  40. data/app/models/rails_pulse/routes/cards/error_rate_per_route.rb +1 -1
  41. data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +1 -1
  42. data/app/models/rails_pulse/routes/cards/request_count_totals.rb +1 -1
  43. data/app/models/rails_pulse/routes/tables/index.rb +2 -1
  44. data/app/models/rails_pulse/summary.rb +10 -3
  45. data/app/services/rails_pulse/summary_service.rb +46 -0
  46. data/app/views/layouts/rails_pulse/_menu_items.html.erb +7 -0
  47. data/app/views/layouts/rails_pulse/application.html.erb +23 -0
  48. data/app/views/rails_pulse/components/_active_filters.html.erb +7 -6
  49. data/app/views/rails_pulse/components/_page_header.html.erb +8 -7
  50. data/app/views/rails_pulse/components/_table.html.erb +7 -4
  51. data/app/views/rails_pulse/dashboard/index.html.erb +1 -1
  52. data/app/views/rails_pulse/job_runs/_operations.html.erb +78 -0
  53. data/app/views/rails_pulse/job_runs/index.html.erb +3 -0
  54. data/app/views/rails_pulse/job_runs/show.html.erb +51 -0
  55. data/app/views/rails_pulse/jobs/_job_runs_table.html.erb +35 -0
  56. data/app/views/rails_pulse/jobs/_table.html.erb +43 -0
  57. data/app/views/rails_pulse/jobs/index.html.erb +34 -0
  58. data/app/views/rails_pulse/jobs/show.html.erb +49 -0
  59. data/app/views/rails_pulse/operations/_operation_analysis_application.html.erb +29 -27
  60. data/app/views/rails_pulse/operations/_operation_analysis_view.html.erb +11 -9
  61. data/app/views/rails_pulse/operations/show.html.erb +10 -8
  62. data/app/views/rails_pulse/queries/_table.html.erb +3 -3
  63. data/app/views/rails_pulse/requests/_table.html.erb +6 -6
  64. data/app/views/rails_pulse/routes/_table.html.erb +3 -3
  65. data/app/views/rails_pulse/routes/show.html.erb +1 -1
  66. data/app/views/rails_pulse/tags/_tag_manager.html.erb +7 -14
  67. data/config/brakeman.ignore +213 -0
  68. data/config/brakeman.yml +68 -0
  69. data/config/initializers/rails_pulse.rb +52 -0
  70. data/config/routes.rb +6 -0
  71. data/db/rails_pulse_migrate/20250113000000_add_jobs_to_rails_pulse.rb +95 -0
  72. data/db/rails_pulse_migrate/20250122000000_add_query_fingerprinting.rb +150 -0
  73. data/db/rails_pulse_migrate/20250202000000_add_index_to_request_uuid.rb +14 -0
  74. data/db/rails_pulse_schema.rb +186 -103
  75. data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +186 -103
  76. data/lib/generators/rails_pulse/templates/migrations/install_rails_pulse_tables.rb +30 -1
  77. data/lib/generators/rails_pulse/templates/rails_pulse.rb +31 -0
  78. data/lib/rails_pulse/active_job_extensions.rb +13 -0
  79. data/lib/rails_pulse/adapters/delayed_job_plugin.rb +25 -0
  80. data/lib/rails_pulse/adapters/sidekiq_middleware.rb +41 -0
  81. data/lib/rails_pulse/cleanup_service.rb +65 -0
  82. data/lib/rails_pulse/configuration.rb +80 -7
  83. data/lib/rails_pulse/engine.rb +34 -3
  84. data/lib/rails_pulse/extensions/active_record.rb +82 -0
  85. data/lib/rails_pulse/job_run_collector.rb +172 -0
  86. data/lib/rails_pulse/middleware/request_collector.rb +20 -43
  87. data/lib/rails_pulse/subscribers/operation_subscriber.rb +11 -5
  88. data/lib/rails_pulse/tracker.rb +82 -0
  89. data/lib/rails_pulse/version.rb +1 -1
  90. data/lib/rails_pulse.rb +2 -0
  91. data/lib/rails_pulse_server.ru +107 -0
  92. data/lib/tasks/rails_pulse_benchmark.rake +382 -0
  93. data/public/rails-pulse-assets/rails-pulse-icons.js +3 -2
  94. data/public/rails-pulse-assets/rails-pulse-icons.js.map +1 -1
  95. data/public/rails-pulse-assets/rails-pulse.css +1 -1
  96. data/public/rails-pulse-assets/rails-pulse.css.map +1 -1
  97. data/public/rails-pulse-assets/rails-pulse.js +1 -1
  98. data/public/rails-pulse-assets/rails-pulse.js.map +3 -3
  99. metadata +35 -7
  100. data/app/models/rails_pulse/requests/charts/operations_chart.rb +0 -35
  101. data/db/migrate/20250930105043_install_rails_pulse_tables.rb +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 943ab1e08e81dbe5ede71b7d3c9ca526a65c9a0ce1767c7f9c37cee8883925cf
4
- data.tar.gz: b3661c728215dc4759e6a3e04dd49dc113d13155972ed30f6ff35d0a2a07b10c
3
+ metadata.gz: 534617e370c257d7fd5947149d59a73a8fd988d3898605a1b8f2a61c38340b10
4
+ data.tar.gz: c82d0e9acbed69e4c8d602c21204da8da165805506c0d57568762883ea2f7aab
5
5
  SHA512:
6
- metadata.gz: db5d711b9d8931c6aa489b9069f05d16514a997b9798c743f2c2998611797dbc3dbdcfdcd12e69f65b2b3459292d1e7558075c02805aa0f49a398b85716736ea
7
- data.tar.gz: 55ccd02b5a9bb13ba46e36e0051c2440d8026956743e02660968209c66e61fe25f4ed8c3dd04a77b08a6209df9e13c922f42e481ade7cf53447d89a93976e680
6
+ metadata.gz: 7dd8507f19548fe4be681e07b5d9128779344528e4d7ebdc5ac4f820e175656568fed1f76255e048657c8da50333a443f257ec5f847c5c56fbab9084d2a02094
7
+ data.tar.gz: 5546a5fd09af8c73a921e784b7e347ec5c1ac6cd1f36b5246290154e315c68f0245bfff5ddb62bbfc1631d248c164dc6122d3634962bc93b36f848f26a1b588a
data/README.md CHANGED
@@ -22,6 +22,11 @@
22
22
  - [Installation](#installation)
23
23
  - [Quick Setup](#quick-setup)
24
24
  - [Basic Configuration](#basic-configuration)
25
+ - [Background Job Monitoring](#background-job-monitoring)
26
+ - [Overview](#overview)
27
+ - [Supported Adapters](#supported-adapters)
28
+ - [Job Tracking Configuration](#job-tracking-configuration)
29
+ - [Privacy & Security](#privacy--security)
25
30
  - [Authentication](#authentication)
26
31
  - [Authentication Setup](#authentication-setup)
27
32
  - [Authentication Examples](#authentication-examples)
@@ -37,6 +42,9 @@
37
42
  - [Configuration](#configuration)
38
43
  - [Database Configuration](#database-configuration)
39
44
  - [Schema Loading](#schema-loading)
45
+ - [Performance Impact](#performance-impact)
46
+ - [Running Performance Benchmarks](#running-performance-benchmarks)
47
+ - [Standalone Dashboard Deployment](#standalone-dashboard-deployment)
40
48
  - [Testing](#testing)
41
49
  - [Technology Stack](#technology-stack)
42
50
  - [Advantages Over Other Solutions](#advantages-over-other-solutions)
@@ -54,8 +62,16 @@ Rails Pulse is a comprehensive performance monitoring and debugging gem that pro
54
62
  - Interactive dashboard with response time charts and request analytics
55
63
  - SQL query performance tracking with slow query identification
56
64
  - Route-specific metrics with configurable performance thresholds
65
+ - **Background job monitoring** with execution tracking and failure analysis
57
66
  - Week-over-week trend analysis with visual indicators
58
67
 
68
+ ### Background Job Tracking
69
+ - **Universal job tracking** compatible with all ActiveJob adapters
70
+ - Monitor job performance, failures, and retries
71
+ - Track individual job executions with detailed metrics
72
+ - View operations and SQL queries executed during jobs
73
+ - Configurable privacy controls for job arguments
74
+
59
75
  ### Developer Experience
60
76
  - Zero configuration setup with sensible defaults
61
77
  - Beautiful responsive interface with dark/light mode
@@ -63,7 +79,7 @@ Rails Pulse is a comprehensive performance monitoring and debugging gem that pro
63
79
  - Multiple database support (SQLite, PostgreSQL, MySQL)
64
80
 
65
81
  ### Organization & Filtering
66
- - Flexible tagging system for routes, requests, and queries
82
+ - Flexible tagging system for routes, requests, queries, and jobs
67
83
  - Filter performance data by custom tags
68
84
  - Organize monitoring data by environment, priority, or custom categories
69
85
 
@@ -172,10 +188,21 @@ RailsPulse.configure do |config|
172
188
  critical: 1000
173
189
  }
174
190
 
191
+ # Set performance thresholds for background jobs (in milliseconds)
192
+ config.job_thresholds = {
193
+ slow: 5_000, # 5 seconds
194
+ very_slow: 30_000, # 30 seconds
195
+ critical: 60_000 # 1 minute
196
+ }
197
+
175
198
  # Asset tracking configuration
176
199
  config.track_assets = false # Ignore asset requests by default
177
200
  config.custom_asset_patterns = [] # Additional asset patterns to ignore
178
201
 
202
+ # Job tracking configuration
203
+ config.track_jobs = true # Enable background job tracking
204
+ config.capture_job_arguments = false # Disable argument capture for privacy
205
+
179
206
  # Rails Pulse mount path (optional)
180
207
  # Specify if Rails Pulse is mounted at a custom path to prevent self-tracking
181
208
  config.mount_path = nil # e.g., "/admin/monitoring"
@@ -184,18 +211,22 @@ RailsPulse.configure do |config|
184
211
  config.ignored_routes = [] # Array of strings or regex patterns
185
212
  config.ignored_requests = [] # Array of request patterns to ignore
186
213
  config.ignored_queries = [] # Array of query patterns to ignore
214
+ config.ignored_jobs = [] # Array of job class names to ignore
215
+ config.ignored_queues = [] # Array of queue names to ignore
187
216
 
188
217
  # Tagging system - define available tags for categorizing performance data
189
218
  config.tags = ["production", "staging", "critical", "needs-optimization"]
190
219
 
191
220
  # Data cleanup
192
- config.archiving_enabled = true # Enable automatic cleanup
193
- config.full_retention_period = 2.weeks # Delete records older than this
194
- config.max_table_records = { # Maximum records per table
195
- rails_pulse_requests: 10000,
196
- rails_pulse_operations: 50000,
197
- rails_pulse_routes: 1000,
198
- rails_pulse_queries: 500
221
+ config.archiving_enabled = true # Enable automatic cleanup
222
+ config.full_retention_period = 30.days # Delete records older than this
223
+ config.max_table_records = { # Maximum records per table
224
+ rails_pulse_operations: 100_000,
225
+ rails_pulse_requests: 50_000,
226
+ rails_pulse_job_runs: 50_000,
227
+ rails_pulse_queries: 10_000,
228
+ rails_pulse_routes: 1_000,
229
+ rails_pulse_jobs: 1_000
199
230
  }
200
231
 
201
232
  # Multiple database support (optional)
@@ -206,6 +237,117 @@ RailsPulse.configure do |config|
206
237
  end
207
238
  ```
208
239
 
240
+ ## Background Job Monitoring
241
+
242
+ Rails Pulse provides comprehensive monitoring for ActiveJob background jobs, tracking performance, failures, and execution details across all major job adapters.
243
+
244
+ ### Overview
245
+
246
+ Background job monitoring is **enabled by default** and works automatically with any ActiveJob adapter. Rails Pulse captures:
247
+
248
+ - **Job execution metrics**: Duration, status, retry attempts
249
+ - **Failure tracking**: Error class, error message, failure rates
250
+ - **Performance analysis**: Slow jobs, aggregate metrics by job class
251
+ - **Operation timeline**: SQL queries and operations during job execution
252
+ - **Job arguments**: Optional capture for debugging (disabled by default for privacy)
253
+
254
+ Access the jobs dashboard at `/rails_pulse/jobs` to view:
255
+ - All job classes with aggregate metrics (total runs, failure rate, average duration)
256
+ - Individual job executions with detailed performance data
257
+ - Filtering by time range, status, queue, and performance thresholds
258
+ - Tagging support for organizing jobs by team, priority, or category
259
+
260
+ ### Supported Adapters
261
+
262
+ Rails Pulse works with all ActiveJob adapters through universal tracking:
263
+
264
+ - **Sidekiq** - Enhanced tracking via custom middleware
265
+ - **Solid Queue** - Universal ActiveJob tracking
266
+ - **Good Job** - Universal ActiveJob tracking
267
+ - **Delayed Job** - Enhanced tracking via custom plugin
268
+ - **Resque** - Universal ActiveJob tracking
269
+ - **Any ActiveJob adapter** - Falls back to universal tracking
270
+
271
+ No additional configuration needed - job tracking works out of the box with your existing setup.
272
+
273
+ ### Job Tracking Configuration
274
+
275
+ Customize job tracking in your Rails Pulse initializer:
276
+
277
+ ```ruby
278
+ RailsPulse.configure do |config|
279
+ # Enable or disable job tracking (default: true)
280
+ config.track_jobs = true
281
+
282
+ # Set performance thresholds for jobs (in milliseconds)
283
+ config.job_thresholds = {
284
+ slow: 5_000, # 5 seconds
285
+ very_slow: 30_000, # 30 seconds
286
+ critical: 60_000 # 1 minute
287
+ }
288
+
289
+ # Ignore specific job classes from tracking
290
+ config.ignored_jobs = [
291
+ "ActiveStorage::AnalyzeJob",
292
+ "ActiveStorage::PurgeJob"
293
+ ]
294
+
295
+ # Ignore specific queues from tracking
296
+ config.ignored_queues = ["low_priority", "mailers"]
297
+
298
+ # Capture job arguments for debugging (default: false)
299
+ # WARNING: May expose sensitive data - use with caution
300
+ config.capture_job_arguments = false
301
+
302
+ # Configure adapter-specific settings
303
+ config.job_adapters = {
304
+ sidekiq: { enabled: true, track_queue_depth: false },
305
+ solid_queue: { enabled: true, track_recurring: false },
306
+ good_job: { enabled: true, track_cron: false },
307
+ delayed_job: { enabled: true },
308
+ resque: { enabled: true }
309
+ }
310
+ end
311
+ ```
312
+
313
+ **Disabling job tracking for specific jobs:**
314
+
315
+ ```ruby
316
+ class MyBackgroundJob < ApplicationJob
317
+ # Skip Rails Pulse tracking for this job
318
+ def perform(*args)
319
+ RailsPulse.with_tracking_disabled do
320
+ # Job logic here
321
+ end
322
+ end
323
+ end
324
+ ```
325
+
326
+ ### Privacy & Security
327
+
328
+ **Job argument capture is disabled by default** to protect sensitive information. Job arguments may contain:
329
+ - User credentials or tokens
330
+ - Personal identifiable information (PII)
331
+ - API keys or secrets
332
+ - Sensitive business data
333
+
334
+ Only enable `capture_job_arguments` in development or when explicitly needed for debugging. Consider using parameter filtering if you need to capture arguments:
335
+
336
+ ```ruby
337
+ # In your job class
338
+ class SensitiveJob < ApplicationJob
339
+ def perform(user_id:, api_key:)
340
+ # Rails Pulse will track execution but not arguments by default
341
+ end
342
+ end
343
+ ```
344
+
345
+ **Performance impact:**
346
+ - Minimal overhead: ~1-2ms per job execution
347
+ - No blocking of job processing
348
+ - Configurable cleanup prevents database growth
349
+ - Can be disabled per-job or globally
350
+
209
351
  ## Authentication
210
352
 
211
353
  Rails Pulse supports flexible authentication to secure access to your monitoring dashboard.
@@ -257,7 +399,7 @@ config.authentication_method = proc {
257
399
 
258
400
  ## Tagging System
259
401
 
260
- Rails Pulse includes a flexible tagging system that allows you to categorize and organize your performance data. Tag routes, requests, and queries with custom labels to better organize and filter your monitoring data.
402
+ Rails Pulse includes a flexible tagging system that allows you to categorize and organize your performance data. Tag routes, requests, queries, jobs, and job runs with custom labels to better organize and filter your monitoring data.
261
403
 
262
404
  ### Configuring Tags
263
405
 
@@ -280,7 +422,7 @@ end
280
422
 
281
423
  **Tag from the UI:**
282
424
 
283
- 1. Navigate to any route, request, or query detail page
425
+ 1. Navigate to any route, request, query, job, or job run detail page
284
426
  2. Click the "+ tag" button next to the record
285
427
  3. Select from your configured tags
286
428
  4. Remove tags by clicking the Ɨ button on any tag badge
@@ -297,6 +439,15 @@ route.add_tag("high-traffic")
297
439
  query = RailsPulse::Query.find_by(normalized_sql: "SELECT * FROM users WHERE id = ?")
298
440
  query.add_tag("needs-optimization")
299
441
 
442
+ # Tag a job
443
+ job = RailsPulse::Job.find_by(name: "UserNotificationJob")
444
+ job.add_tag("high-priority")
445
+ job.add_tag("user-facing")
446
+
447
+ # Tag a specific job run
448
+ job_run = RailsPulse::JobRun.find_by(run_id: "abc123")
449
+ job_run.add_tag("investigated")
450
+
300
451
  # Remove a tag
301
452
  route.remove_tag("critical")
302
453
 
@@ -486,6 +637,113 @@ The schema file `db/rails_pulse_schema.rb` serves as your single source of truth
486
637
  - Should not be deleted or modified
487
638
  - Future updates will provide migrations in `db/rails_pulse_migrate/`
488
639
 
640
+ ## Performance Impact
641
+
642
+ Rails Pulse uses **fiber-based async tracking** for minimal performance overhead:
643
+
644
+ - **Request overhead:** ~0.1ms per request (data collection only)
645
+ - **Database writes:** Non-blocking, handled in background fibers
646
+ - **Memory allocation:** Minimal, ~200 KB per request (temporary)
647
+ - **Thread safety:** Proper connection pooling with isolated database connections
648
+
649
+ This is handled automatically and requires no configuration.
650
+
651
+ ### Running Performance Benchmarks
652
+
653
+ Rails Pulse includes built-in benchmarking tools. To use them:
654
+
655
+ ```ruby
656
+ # Add to your Gemfile (development/test group)
657
+ gem 'benchmark-ips'
658
+ gem 'memory_profiler'
659
+ ```
660
+
661
+ ```bash
662
+ bundle install
663
+
664
+ # Run all benchmarks
665
+ bundle exec rake rails_pulse:benchmark:all
666
+
667
+ # Run specific benchmarks
668
+ bundle exec rake rails_pulse:benchmark:memory
669
+ bundle exec rake rails_pulse:benchmark:request_overhead
670
+ bundle exec rake rails_pulse:benchmark:middleware
671
+ ```
672
+
673
+ ## Standalone Dashboard Deployment
674
+
675
+ For production environments, you can run the Rails Pulse dashboard as a standalone application, separate from your main Rails app. This provides several benefits:
676
+
677
+ - **Dashboard remains accessible when main app is under heavy load**
678
+ - **Separate resource allocation** for monitoring vs application
679
+ - **Enhanced security** - isolate dashboard access from public app
680
+ - **Independent scaling** - dashboard doesn't scale with app instances
681
+
682
+ **Quick Setup:**
683
+
684
+ ```bash
685
+ # Option 1: Set DATABASE_URL environment variable (recommended for production)
686
+ export DATABASE_URL="postgresql://user:pass@host/db"
687
+ bundle exec rackup lib/rails_pulse_server.ru -p 3001
688
+
689
+ # Option 2: Use config/database.yml (recommended for development)
690
+ # Looks for 'rails_pulse' connection, falls back to primary
691
+ # No environment variable needed - automatically reads from config/database.yml
692
+ bundle exec rackup lib/rails_pulse_server.ru -p 3001
693
+ ```
694
+
695
+ **Healthcheck Endpoint:**
696
+
697
+ The standalone server includes a `/health` endpoint that verifies database connectivity:
698
+
699
+ ```bash
700
+ curl http://localhost:3001/health
701
+ # Returns: {"status":"ok","mode":"dashboard","database":"connected","timestamp":"..."}
702
+ ```
703
+
704
+ **Deployment Options:**
705
+
706
+ 1. **Separate subdomain (recommended):**
707
+ ```nginx
708
+ server {
709
+ server_name pulse.myapp.com;
710
+ location / {
711
+ proxy_pass http://localhost:3001;
712
+ proxy_set_header Host $host;
713
+ proxy_set_header X-Real-IP $remote_addr;
714
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
715
+ proxy_set_header X-Forwarded-Proto $scheme;
716
+ }
717
+ }
718
+ ```
719
+
720
+ 2. **Kamal Deployment:**
721
+ Deploy the dashboard as an accessory
722
+
723
+ ```yaml
724
+ # config/deploy.yml
725
+ accessories:
726
+ rails_pulse:
727
+ image: your-app-image # Same image as your main app
728
+ host: your-server
729
+ cmd: bundle exec rackup lib/rails_pulse_server.ru -p 3001
730
+ env:
731
+ clear:
732
+ DATABASE_URL: "postgresql://user:pass@host/db"
733
+ RAILS_ENV: production
734
+ SECRET_KEY_BASE: <%= ENV.fetch("SECRET_KEY_BASE") %>
735
+ port: "3001:3001"
736
+ healthcheck:
737
+ path: /health
738
+ port: 3001
739
+ interval: 10s
740
+ timeout: 5s
741
+ ```
742
+
743
+ **Note:** When running standalone, the dashboard is read-only and doesn't track its own requests (tracking is automatically disabled).
744
+
745
+ For detailed deployment instructions, see [docs/deployment-modes.md](docs/deployment-modes.md).
746
+
489
747
  ## Testing
490
748
 
491
749
  Rails Pulse includes a comprehensive test suite designed for speed and reliability across multiple databases and Rails versions.
@@ -652,7 +910,6 @@ The gem is available as open source under the terms of the [MIT License](https:/
652
910
  <div align="center">
653
911
  <strong>Built with ā¤ļø for the Rails community</strong>
654
912
 
655
- [Documentation](https://github.com/railspulse/rails_pulse/wiki) •
656
913
  [Issues](https://github.com/railspulse/rails_pulse/issues) •
657
- [Contributing](CONTRIBUTING.md)
914
+ [Discussions](https://github.com/railspulse/rails_pulse/discussions)
658
915
  </div>
data/Rakefile CHANGED
@@ -7,6 +7,57 @@ require "dotenv/load" if File.exist?(".env")
7
7
  APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
8
8
  load "rails/tasks/engine.rake"
9
9
 
10
+ desc "Verify dummy app migrations are in sync with gem migrations"
11
+ task :verify_dummy_migrations do
12
+ # Check if db/rails_pulse_migrate directory exists (separate database setup)
13
+ if Dir.exist?("db/rails_pulse_migrate")
14
+ gem_migrations = Dir["db/rails_pulse_migrate/*.rb"].map { |f| File.basename(f) }.sort
15
+
16
+ # Get all RailsPulse migrations from dummy app (exclude dummy app's own migrations)
17
+ all_dummy_migrations = Dir["test/dummy/db/migrate/*.rb"].map { |f| File.basename(f) }
18
+
19
+ # Filter to only RailsPulse migrations (contain "rails_pulse" in name or match known patterns)
20
+ dummy_migrations = all_dummy_migrations.select do |m|
21
+ m.include?("rails_pulse") ||
22
+ m.include?("jobs") ||
23
+ m.include?("query") ||
24
+ m.include?("request_uuid")
25
+ end.sort
26
+
27
+ missing = gem_migrations - dummy_migrations
28
+
29
+ if missing.any?
30
+ puts "\nāŒ Dummy app is missing Rails Pulse migrations:"
31
+ missing.each { |m| puts " • #{m}" }
32
+ puts "\nTo fix this, run:"
33
+ puts " cd test/dummy"
34
+ puts " rails generate rails_pulse:upgrade"
35
+ puts " rails db:migrate RAILS_ENV=test"
36
+ puts "\nThen commit the new migration files."
37
+ exit 1
38
+ else
39
+ puts "āœ… Dummy app migrations are in sync with gem migrations"
40
+ end
41
+ else
42
+ puts "āœ… Dummy app migrations check skipped (single database setup)"
43
+ end
44
+ end
45
+
46
+ desc "Sync Rails Pulse schema to test/dummy app"
47
+ task :sync_test_schema do
48
+ require "fileutils"
49
+
50
+ source = "db/rails_pulse_schema.rb"
51
+ dest = "test/dummy/db/rails_pulse_schema.rb"
52
+
53
+ if File.exist?(source)
54
+ FileUtils.cp(source, dest)
55
+ puts "āœ… Synced schema: #{source} → #{dest}"
56
+ else
57
+ puts "āš ļø Source schema not found: #{source}"
58
+ end
59
+ end
60
+
10
61
  desc "Setup database for testing"
11
62
  task :test_setup do
12
63
  database = ENV['DB'] || 'sqlite3'
@@ -19,6 +70,9 @@ task :test_setup do
19
70
  puts
20
71
 
21
72
  begin
73
+ # Sync schema file from gem to test/dummy
74
+ Rake::Task[:sync_test_schema].invoke
75
+
22
76
  # Remove schema.rb to ensure clean migration
23
77
  schema_file = "test/dummy/db/schema.rb"
24
78
  if File.exist?(schema_file)
@@ -102,6 +156,10 @@ task :test_setup_for_version, [ :database, :rails_version ] do |t, args|
102
156
  puts
103
157
 
104
158
  begin
159
+ # Sync schema file from gem to test/dummy
160
+ Rake::Task[:sync_test_schema].reenable
161
+ Rake::Task[:sync_test_schema].invoke
162
+
105
163
  # Remove schema.rb to ensure clean migration
106
164
  schema_file = "test/dummy/db/schema.rb"
107
165
  if File.exist?(schema_file)
@@ -203,9 +261,33 @@ task :test_release do
203
261
 
204
262
  failed_tasks = []
205
263
  current_step = 0
206
- total_steps = 7
264
+ total_steps = 11
265
+
266
+ # Step 1: Sync test schema
267
+ current_step += 1
268
+ begin
269
+ puts "\n[#{current_step}/#{total_steps}] Syncing test schema..."
270
+ puts "-" * 70
271
+ Rake::Task[:sync_test_schema].invoke
272
+ rescue => e
273
+ puts "āŒ Schema sync failed!"
274
+ puts " Error: #{e.message}"
275
+ failed_tasks << "sync_test_schema"
276
+ end
277
+
278
+ # Step 2: Verify dummy migrations
279
+ current_step += 1
280
+ begin
281
+ puts "\n[#{current_step}/#{total_steps}] Verifying dummy app migrations..."
282
+ puts "-" * 70
283
+ Rake::Task[:verify_dummy_migrations].invoke
284
+ rescue => e
285
+ puts "āŒ Dummy app migration verification failed!"
286
+ puts " Error: #{e.message}"
287
+ failed_tasks << "verify_dummy_migrations"
288
+ end
207
289
 
208
- # Step 1: Git status check
290
+ # Step 3: Git status check
209
291
  current_step += 1
210
292
  begin
211
293
  puts "\n[#{current_step}/#{total_steps}] Checking git status..."
@@ -225,7 +307,7 @@ task :test_release do
225
307
  puts "āš ļø Warning: Could not check git status (#{e.message})"
226
308
  end
227
309
 
228
- # Step 2: RuboCop linting
310
+ # Step 5: RuboCop linting
229
311
  current_step += 1
230
312
  begin
231
313
  puts "\n[#{current_step}/#{total_steps}] Running RuboCop linting..."
@@ -238,7 +320,19 @@ task :test_release do
238
320
  failed_tasks << "rubocop"
239
321
  end
240
322
 
241
- # Step 3: Install Node dependencies
323
+ # Step 6: Brakeman security scan
324
+ current_step += 1
325
+ begin
326
+ puts "\n[#{current_step}/#{total_steps}] Running Brakeman security scanner..."
327
+ puts "-" * 70
328
+ Rake::Task[:brakeman].invoke
329
+ rescue => e
330
+ puts "āŒ Brakeman security scan failed!"
331
+ puts " Error: #{e.message}"
332
+ failed_tasks << "brakeman"
333
+ end
334
+
335
+ # Step 7: Install Node dependencies
242
336
  current_step += 1
243
337
  begin
244
338
  puts "\n[#{current_step}/#{total_steps}] Installing Node dependencies..."
@@ -251,7 +345,7 @@ task :test_release do
251
345
  failed_tasks << "npm_install"
252
346
  end
253
347
 
254
- # Step 4: Build and verify assets
348
+ # Step 8: Build and verify assets
255
349
  current_step += 1
256
350
  begin
257
351
  puts "\n[#{current_step}/#{total_steps}] Building production assets..."
@@ -273,7 +367,7 @@ task :test_release do
273
367
  failed_tasks << "npm_build"
274
368
  end
275
369
 
276
- # Step 5: Verify gem builds
370
+ # Step 9: Verify gem builds
277
371
  current_step += 1
278
372
  begin
279
373
  puts "\n[#{current_step}/#{total_steps}] Verifying gem builds correctly..."
@@ -291,7 +385,7 @@ task :test_release do
291
385
  failed_tasks << "gem_build"
292
386
  end
293
387
 
294
- # Step 6: Run generator tests
388
+ # Step 10: Run generator tests
295
389
  current_step += 1
296
390
  begin
297
391
  puts "\n[#{current_step}/#{total_steps}] Running generator tests..."
@@ -304,7 +398,7 @@ task :test_release do
304
398
  failed_tasks << "test_generators"
305
399
  end
306
400
 
307
- # Step 7: Run full test matrix with system tests
401
+ # Step 11: Run full test matrix with system tests
308
402
  current_step += 1
309
403
  begin
310
404
  puts "\n[#{current_step}/#{total_steps}] Running full test matrix with system tests..."
@@ -337,5 +431,45 @@ task :test_release do
337
431
  end
338
432
  end
339
433
 
434
+ desc "Run Brakeman security scanner"
435
+ task :brakeman do
436
+ require "brakeman"
437
+
438
+ puts "\n" + "=" * 50
439
+ puts "šŸ”’ Running Brakeman Security Scanner"
440
+ puts "=" * 50
441
+ puts
442
+
443
+ begin
444
+ # Run Brakeman with the configuration file
445
+ result = Brakeman.run(
446
+ app_path: ".",
447
+ config_file: "config/brakeman.yml",
448
+ print_report: true,
449
+ pager: false
450
+ )
451
+
452
+ # Check if any unignored warnings were found
453
+ # result.filtered_warnings only includes warnings that aren't ignored
454
+ unignored_warnings = result.filtered_warnings
455
+ total_warnings = result.warnings.count
456
+ ignored_count = total_warnings - unignored_warnings.count
457
+
458
+ if unignored_warnings.any? || result.errors.any?
459
+ puts "\nāŒ Security issues found!"
460
+ puts " Warnings: #{unignored_warnings.count}"
461
+ puts " Ignored: #{ignored_count}" if ignored_count > 0
462
+ puts " Errors: #{result.errors.count}"
463
+ exit 1
464
+ else
465
+ puts "\nāœ… No security issues found!"
466
+ puts " (#{ignored_count} warnings reviewed and ignored)" if ignored_count > 0
467
+ end
468
+ rescue => e
469
+ puts "\nāŒ Brakeman scan failed!"
470
+ puts " Error: #{e.message}"
471
+ exit 1
472
+ end
473
+ end
340
474
 
341
475
  task default: :test
@@ -25,10 +25,25 @@
25
25
  text-align: start;
26
26
  }
27
27
 
28
- th, td {
28
+ th,
29
+ td {
29
30
  padding: var(--size-2);
30
31
  }
31
32
 
33
+ /* Truncated cell styles */
34
+ td.truncate-cell {
35
+ overflow: hidden;
36
+ text-overflow: ellipsis;
37
+ white-space: nowrap;
38
+ }
39
+
40
+ td.truncate-cell a {
41
+ display: block;
42
+ overflow: hidden;
43
+ text-overflow: ellipsis;
44
+ white-space: nowrap;
45
+ }
46
+
32
47
  tfoot {
33
48
  background-color: rgb(from var(--color-border-light) r g b / .5);
34
49
  border-block-start-width: var(--border);
@@ -26,6 +26,10 @@
26
26
  flex-wrap: wrap;
27
27
  }
28
28
 
29
+ .tag-list span {
30
+ padding-right: 3px !important;
31
+ }
32
+
29
33
  /* Individual Tag */
30
34
  .tag {
31
35
  display: inline-flex;
@@ -63,9 +67,10 @@
63
67
  }
64
68
 
65
69
  .tag-remove span {
66
- font-size: 1.25rem;
67
70
  line-height: 1;
68
- font-weight: bold;
71
+ font-weight: inherit;
72
+ margin-left: 6px;
73
+ font-size: 17px;
69
74
  }
70
75
 
71
76
  /* Add Tag Container */
@@ -35,6 +35,9 @@
35
35
  .max-w-lg { max-width: 32rem; }
36
36
  .max-w-xl { max-width: 36rem; }
37
37
 
38
+ /* Table layout utilities */
39
+ .table-fixed { table-layout: fixed; }
40
+
38
41
  /* Global filters active indicator */
39
42
  .global-filters-active {
40
43
  position: relative;
@@ -51,7 +51,8 @@ module ChartTableConcern
51
51
  table_results = build_table_results
52
52
  handle_pagination
53
53
 
54
- @pagy, @table_data = pagy(table_results, items: session_pagination_limit)
54
+ # Use pagy_options for version compatibility
55
+ @pagy, @table_data = pagy(table_results, **pagy_options(session_pagination_limit))
55
56
  end
56
57
 
57
58
  def setup_zoom_range_data