rails_pulse 0.4.0.pre.2 → 0.4.0.pre.6

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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +115 -171
  3. data/README.md +83 -109
  4. data/app/controllers/concerns/session_filters_concern.rb +4 -3
  5. data/app/controllers/concerns/time_range_concern.rb +29 -19
  6. data/app/controllers/rails_pulse/application_controller.rb +67 -4
  7. data/app/controllers/rails_pulse/dashboard_controller.rb +20 -11
  8. data/app/controllers/rails_pulse/deployments_controller.rb +1 -4
  9. data/app/controllers/rails_pulse/routes_controller.rb +46 -0
  10. data/app/helpers/rails_pulse/application_helper.rb +4 -0
  11. data/app/helpers/rails_pulse/breadcrumbs_helper.rb +6 -3
  12. data/app/javascript/rails_pulse/controllers/chart_controller.js +27 -0
  13. data/app/models/rails_pulse/cards/base.rb +61 -25
  14. data/app/models/rails_pulse/dashboard/charts/response_time_percentiles.rb +38 -15
  15. data/app/models/rails_pulse/dashboard/charts/throughput_and_errors.rb +36 -13
  16. data/app/models/rails_pulse/dashboard/concerns/time_range_helper.rb +3 -0
  17. data/app/models/rails_pulse/dashboard/health_summary.rb +3 -1
  18. data/app/models/rails_pulse/dashboard/needs_attention.rb +3 -1
  19. data/app/models/rails_pulse/dashboard/storage_status.rb +38 -68
  20. data/app/models/rails_pulse/deployment.rb +6 -0
  21. data/app/models/rails_pulse/exceptions/cards/exception_rate.rb +1 -5
  22. data/app/models/rails_pulse/exceptions/cards/open_groups.rb +4 -4
  23. data/app/models/rails_pulse/exceptions/cards/total_occurrences.rb +3 -4
  24. data/app/models/rails_pulse/jobs/cards/failure_rate.rb +7 -20
  25. data/app/models/rails_pulse/jobs/cards/p95_duration.rb +5 -8
  26. data/app/models/rails_pulse/jobs/cards/total_runs.rb +4 -4
  27. data/app/models/rails_pulse/queries/cards/average_query_times.rb +6 -9
  28. data/app/models/rails_pulse/queries/cards/database_load.rb +7 -10
  29. data/app/models/rails_pulse/queries/cards/execution_rate.rb +4 -6
  30. data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +5 -8
  31. data/app/models/rails_pulse/route.rb +7 -1
  32. data/app/models/rails_pulse/routes/cards/error_rates.rb +5 -11
  33. data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +3 -1
  34. data/app/models/rails_pulse/routes/cards/request_count_totals.rb +4 -6
  35. data/app/models/rails_pulse/summary.rb +13 -18
  36. data/app/models/rails_pulse/time_range_preference.rb +28 -0
  37. data/app/models/rails_pulse/time_window.rb +61 -0
  38. data/app/services/rails_pulse/exception_capture_service.rb +2 -0
  39. data/app/services/rails_pulse/route_controller_action_backfiller.rb +0 -4
  40. data/app/services/rails_pulse/summary_service.rb +112 -162
  41. data/app/services/rails_pulse/tag_filter_service.rb +10 -1
  42. data/app/views/layouts/rails_pulse/_time_range_selector.html.erb +2 -2
  43. data/app/views/rails_pulse/dashboard/index.html.erb +6 -3
  44. data/app/views/rails_pulse/routes/_archived_summary_table.html.erb +46 -0
  45. data/app/views/rails_pulse/routes/_archived_table_pagination.html.erb +28 -0
  46. data/app/views/rails_pulse/routes/show.html.erb +14 -2
  47. data/app/views/rails_pulse/shared/schema_outdated.html.erb +96 -0
  48. data/exe/rails_pulse_server +9 -0
  49. data/lib/generators/rails_pulse/install_generator.rb +7 -2
  50. data/lib/generators/rails_pulse/templates/rails_pulse.rb +28 -2
  51. data/lib/generators/rails_pulse/upgrade_generator.rb +56 -5
  52. data/lib/rails_pulse/cleanup_service.rb +7 -7
  53. data/lib/rails_pulse/configuration.rb +30 -1
  54. data/lib/rails_pulse/engine.rb +19 -44
  55. data/lib/rails_pulse/installers/config_updater.rb +17 -0
  56. data/lib/rails_pulse/job_run_collector.rb +1 -1
  57. data/lib/rails_pulse/middleware/asset_server.rb +5 -4
  58. data/lib/rails_pulse/middleware/request_collector.rb +2 -17
  59. data/lib/rails_pulse/schema_check.rb +136 -0
  60. data/lib/rails_pulse/standalone.rb +62 -0
  61. data/lib/rails_pulse/subscribers/operation_subscriber.rb +45 -3
  62. data/lib/rails_pulse/tasks/status_reporter.rb +217 -0
  63. data/lib/rails_pulse/tracker.rb +242 -25
  64. data/lib/rails_pulse/version.rb +1 -1
  65. data/lib/rails_pulse.rb +5 -16
  66. data/lib/rails_pulse_server.ru +58 -11
  67. data/lib/tasks/rails_pulse.rake +48 -2
  68. data/lib/tasks/rails_pulse_tasks.rake +5 -0
  69. data/public/rails-pulse-assets/rails-pulse.js +1 -1
  70. metadata +11 -9
  71. data/app/assets/images/rails_pulse/dashboard.png +0 -0
  72. data/app/assets/images/rails_pulse/query-show.png +0 -0
  73. data/app/assets/images/rails_pulse/request-show.png +0 -0
  74. data/app/assets/images/rails_pulse/request.png +0 -0
  75. data/app/assets/images/rails_pulse/route-show.png +0 -0
  76. data/lib/rails_pulse/extensions/active_record.rb +0 -124
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b102fc4776d7fe701b47011e02d682f47a2784500103f3cb7d7598a757b174ff
4
- data.tar.gz: 34f4055047c1bb9151c9e811f14c4e90fe83e52508c61ea4433929b2b431d568
3
+ metadata.gz: b24fa9677dc2c9b2dd4042acadb464247dd64d2136836e2197a1afe5b90f3f21
4
+ data.tar.gz: 2c45d0508fb24753b4d65b87b57afb19382b82277747a532e5182d6a6e2a8bdd
5
5
  SHA512:
6
- metadata.gz: 974fcf20833ab11e46a00db96d21590f32a699a73de8d2af229d1fa5559f0817c107ba684f4ada34cdb067a77f6293ecc11ee3d24f7911f9fe074353a2991d79
7
- data.tar.gz: 507afdae03f11fef23d0bbd7cc5ee8e8123656e41efbc219a10f8b6682602bfb7dcfa91e9ac424352d68ad05b3bceed4070eead220d053713f56aa1ade7bec01
6
+ metadata.gz: 83270431a2752bb64ada4380744b5fc087696c36109bfcdd157412eb9689124e0dc8d1945371978455a8a2baefceced920ff27f82c3d311c4cd297b73f8ae52f
7
+ data.tar.gz: d3c00d842cebe64770e3039e0c50b029bdde9169590857ec17526a8a05bad7abf71eecebe0fe614ebd1f726a5567d8c4070f08831ccc5847b28c0018184c9ced
data/CHANGELOG.md CHANGED
@@ -7,185 +7,132 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0.pre.6] - 2026-09-20
11
+
12
+ ## [0.4.0.pre.5] - 2026-09-20
13
+
14
+ ### Changed
15
+
16
+ - **Services now autoload through Zeitwerk.** `app/services` was hidden from the Rails autoloader and wired up by hand, so services never reloaded in development and every new one had to be registered in the engine. They now load and reload like the rest of `app/`, with the few acronym-prone file names pinned so a host's `inflect.acronym` declarations cannot change the constants the gem expects.
17
+
18
+ ### Removed
19
+
20
+ - `RailsPulse.warm_metric_cache!` (a no-op) and `RailsPulse.clear_metric_cache!` (used `delete_matched`, which some cache stores do not support). Neither was referenced by the dashboard.
21
+
22
+ ### Fixed
23
+
24
+ - **Dashboard pages issue fewer queries.** Tag filtering now runs as subqueries inside each card and chart query instead of plucking every route, query and job id first; the route-backfill check is a single indexed query once every route has an action; and the dashboard's storage headline reuses table sizes for five minutes instead of measuring every table on every load.
25
+ - **Lower per-query capture overhead.** The SQL, template and cache subscribers materialised the whole call stack on every event to find the calling app file; they now walk it lazily and stop at the first app frame, which cuts the per-event cost by half to two thirds on a typical controller stack.
26
+ - **Summary aggregation writes each period in a handful of statements.** Every route, query and job summary used to be found and saved individually, so an hour with a few hundred routes and queries cost over 700 SQL statements; rows are now upserted in bulk against the summaries unique index.
27
+ - **Background tracking no longer spawns a thread per request.** A burst of traffic used to fan out into one writer thread per request, each holding one of the app's database connections, so the app's own request threads could wait seconds for a connection. One writer thread per process now drains a bounded queue on a single connection; when the queue is full the newest request is dropped and counted rather than slowing the app. Adds `config.async_queue_size` (default 1000), `RailsPulse::Tracker.stats`, and an exit hook that drains the queue on restart. SQL normalisation and N+1 detection have moved off the request thread as well.
28
+ - **Metric card sparklines are now correct in time zones east of UTC.** Daily buckets were computed from the stored UTC timestamp, so in zones such as London, Melbourne or Tokyo every card showed each day's value a day early with the latest day at zero, and half-hour zones got empty hourly sparklines. Grouping now follows `config.time_zone`, and the gem no longer adds `group_by_date` / `group_by_hour` to the host's `ActiveRecord::Relation`.
29
+ - **Summary aggregation now runs its transaction on the Rails Pulse connection.** On separate-database installs it was opened on the host's primary database, so a failure part-way through a period could leave partial summaries behind.
30
+ - **The dashboard's own HTTP, mailer, job and storage events are no longer recorded.** These subscribers skipped the recursion guard that SQL and template events already honoured.
31
+ - **Storage page reports real table sizes again.** A leftover screenshot fixture replaced every table's live count, size, and age with hard-coded sample numbers in any environment other than `test`.
32
+ - **Standalone dashboard settings forms no longer fail CSRF verification.** The standalone server (`rails_pulse_server`) used the plain `rack-session` gem's `Rack::Session::Cookie`, which knows nothing about Rails' CSRF handling: a token generated for a form is only written into the session by `commit_csrf_token`, a hook that only Rails' own `ActionDispatch::Session::CookieStore` calls. Every generated token was silently discarded, so every submission failed verification. Switched to `ActionDispatch::Cookies` + `ActionDispatch::Session::CookieStore` (seeding the `action_dispatch.*` env Rails normally sets up before reaching the engine).
33
+ - **Standalone dashboard no longer 404s on the time range and global filters pickers.** Those forms submit `POST` with a hidden `_method=patch` field — the standard verb-override trick — which the mounted engine translates via the host app's default middleware stack. The standalone server (`rails_pulse_server`) builds its own minimal Rack stack and never added `Rack::MethodOverride`, so the request reached routing as a plain `POST` and 404'd against the `PATCH`-only route.
34
+ - **Cleanup no longer risks statement timeouts on large tables.** `CleanupService`'s orphan checks for queries, routes, jobs, and exception groups used a `NOT IN` subquery, which some databases (notably PostgreSQL at scale) execute by materializing and rescanning the full subquery result instead of using an index. Switched to a correlated `NOT EXISTS`, which lets the planner use an index per row. A stalled cleanup stage previously blocked all later stages, including hourly summary pruning. (#253)
35
+ - **Idle periods no longer trigger false "summary job not running" warnings.** `SummaryJob` now records a zero-count overall summary for hours/days with no requests, so the dashboard banner, `rails_pulse:status`, the storage-pressure card, and count-based cleanup no longer mistake "no traffic" for "job stopped running." (#250)
36
+ - **Custom date range charts on the Routes, Queries, and Jobs pages no longer render blank when the server's OS timezone differs from `config.time_zone`.** A custom range was rounded to a day/hour boundary in whatever offset the parsed time happened to carry rather than `Time.zone`, so the boundary could land hours away from where summary data is actually bucketed — every chart series came back all-nil while the metric cards and table (queried differently) kept showing data, which was the visible symptom.
37
+ - **Dashboard charts and metric cards now honor a custom date range instead of always showing the trailing days.** The dashboard collapsed the selected range to a day count and had every chart, card, and sparkline re-derive "the last N days ending now" from it, so a range in the past rendered recent data under the selected range's labels, and the day count itself was truncated by integer division. Charts and cards now bucket the exact selected range.
38
+ - **Custom date range no longer 500s the dashboard on Marshal-backed session stores.** The custom range was written with symbol keys but read back expecting string keys, so stores that preserve symbols (e.g. `activerecord-session_store`) broke every page until the session was cleared; both shapes are now accepted and unreadable preferences fall back to the default range. (#252)
39
+ - **`config.logger` is now honored.** `RailsPulse.logger` previously ignored a custom logger set in the initializer and always wrote to the tagged `Rails.logger`; the configured logger now receives all Rails Pulse log output. (#244)
40
+ - **Cached SQL reads no longer captured as operations.** Query-cache hits were going through the same stack-walk and operation-allocation path as real queries, adding measurable overhead on requests with heavy cache reuse. `config.ignored_queries` now also works — it was previously validated but never consulted when collecting SQL operations.
41
+ - **Dashboard status bar badges are now all clickable.** Routes, Queries, and Jobs badges link to their respective pages, matching Exceptions and Storage.
42
+ - **Standalone auth notice logged once per process.** The "standalone dashboard ignores config.authentication_method / config.authorize" notice kept its once-only flag on each controller class, so it repeated for every engine controller a visitor reached. The flag now lives on `RailsPulse::Standalone` and the notice is logged once per process.
43
+
44
+ ## [0.4.0.pre.4] - 2026-09-07
45
+
46
+ ### Fixed
47
+
48
+ - Dependency and packaging fixes only; no user-facing changes beyond 0.4.0.pre.3.
49
+
50
+ ## [0.4.0.pre.3] - 2026-09-06
51
+
52
+ ### Added
53
+
54
+ - **Shell-based deployment tracking.** `rake rails_pulse:record_deployment` and the new `rake rails_pulse:finish_deployment[revision]` let release scripts record and close deployments without the HTTP API or a token.
55
+ - **Standalone mode improvements.** Dashboard links now resolve correctly when served at `/`, and a new `config.standalone_authentication_method` (with an HTTP Basic fallback) replaces host-app authentication, which the standalone process can't use.
56
+ - **`rails rails_pulse:status`.** Reports schema, migration, and tracking/auth config state in one command, and exits 1 if anything needs action before deploying.
57
+ - **Schema drift guard.** `RailsPulse::SchemaCheck` detects tables or columns the running gem expects but that haven't been migrated yet, and pauses tracking (dashboard returns 503) with a clear warning instead of erroring. Disable with `config.schema_check_enabled = false`.
58
+
59
+ ### Fixed
60
+
61
+ - Fixed breadcrumb links becoming protocol-relative (`//queries`) when the engine is mounted at `/`.
62
+ - Fixed the standalone dashboard server 404ing on its own stylesheets and scripts.
63
+ - Fixed asset responses using mixed-case headers, which `Rack::Lint` rejects under Rack 3.
64
+ - Fixed `rails_pulse_server` ignoring `RAILS_ENV`/`RACK_ENV` and always booting `rackup` in development mode.
65
+ - Fixed the standalone server requiring a literal `SECRET_KEY_BASE` instead of falling back to the host app's.
66
+
67
+ ### Security
68
+
69
+ - Updated development dependencies mail, net-imap, and json for several CVEs (gem dev/CI only).
70
+ - Updated development dependencies nokogiri, loofah, and rails-html-sanitizer for several CVEs (gem dev/CI only).
71
+ - Updated development dependencies Rails, puma, websocket-driver, and concurrent-ruby for Dependabot's critical/high alerts. Host apps should update their own Rails to 8.1.3.1.
72
+
73
+ ### Changed
74
+
75
+ - `rails generate rails_pulse:upgrade` now reports unrun migrations and outstanding route backfill instead of saying everything is up to date.
76
+ - Cleaned up `rake test` output — no more RDoc warnings, stray blank lines, or leaked log noise.
77
+
10
78
  ## [0.4.0.pre.2] - 2026-09-04
11
79
 
12
80
  ### Fixed
13
81
 
14
- - **`rails generate rails_pulse:upgrade` wrote a migration that would not parse.**
15
- The schema parser matched a column comment with `comment: "([^"]*)"`, which stops
16
- at the first escaped quote. The `http_methods` comment embeds a `["GET","POST"]`
17
- example, so it was truncated mid-escape and the trailing backslash escaped the
18
- closing quote of the generated `comment:` string — the copied
19
- `upgrade_rails_pulse_tables.rb` then failed to load with
20
- `unterminated string meets end of file`. Comments are now matched as complete Ruby
21
- string literals, decoded with `String#undump`, and re-escaped with `String#inspect`
22
- when the migration is rendered.
23
-
24
- - **Background tracking writes corrupted the host app's test database connection.**
25
- Under `use_transactional_tests` Rails pins one connection per pool and hands that
26
- same connection to every thread, so the tracker's writer thread interleaved its
27
- INSERTs with the test's own statements on a single socket. PostgreSQL reported
28
- this as `message type 0x5a arrived from server while idle` and
29
- `server sent data ("D" message) without prior row description`, and suites hung
30
- or failed at random. The gem's own dummy app avoided it with
31
- `config.async = false if Rails.env.test?`, but the installed initializer never
32
- shipped that line. The tracker now writes inline whenever the pool's connection
33
- is shared across threads, and `rails generate rails_pulse:install` adds the
34
- test-environment line to the initializer. Existing installs should add
35
- `config.async = false if Rails.env.test?` to `config/initializers/rails_pulse.rb`.
82
+ - Fixed `rails generate rails_pulse:upgrade` writing a migration that failed to parse when a column comment contained an escaped quote.
83
+ - Fixed background tracking writes corrupting the test database connection under transactional tests. Existing installs should add `config.async = false if Rails.env.test?` to their initializer.
36
84
 
37
85
  ## [0.4.0.pre.1] - 2026-09-03
38
86
 
39
- This release contains a **breaking schema change** and requires a one-time data
40
- migration. Read "Upgrading from 0.3.x" below before deploying. Take a database
41
- backup first: the route migration is irreversible.
87
+ This release contains a **breaking schema change** and requires a one-time data migration. Back up your database first — the route migration is irreversible. See "Upgrading from 0.3.x" below.
42
88
 
43
89
  ### Security
44
90
 
45
- - **Job failure messages are redacted and bounded.** `rails_pulse_job_runs.error_message`
46
- used to be stored raw and unbounded. It now goes through the same sanitizer as
47
- exception messages: `key=value` / `key: value` / `"key":"value"` fragments are
48
- masked whenever the key matches `config.filter_parameters` (same rules as logged
49
- params), and MySQL `Duplicate entry '<value>'` values are redacted alongside
50
- PostgreSQL `DETAIL:` clauses.
51
- - **CSRF protection is now declared by the engine.** `RailsPulse::ApplicationController`
52
- calls `protect_from_forgery with: :exception` itself instead of inheriting it from the
53
- host's `default_protect_from_forgery`, which only `load_defaults 5.2+` enables. Hosts
54
- on an older `load_defaults` had no CSRF protection on any engine endpoint (tags,
55
- exception status, query reanalyze, session filters). Brakeman's `CheckForgerySetting`
56
- is no longer skipped.
57
- - **Authentication hooks fail closed.** An `authentication_method` that returned
58
- `false` without rendering or redirecting used to grant access — `proc { current_user&.admin? }`
59
- looked right and let every non-admin in. Literal `false` is now a 403; `nil`
60
- (what the documented `unless … redirect_to … end` style returns on success) still allows.
61
- - **EXPLAIN hardening.** The query analyzer now refuses SQL containing `;`, `--` or `/*`
62
- anywhere (PostgreSQL's `execute` runs every statement in a string, so a captured
63
- `SELECT …; UPDATE …` would have re-run the UPDATE on each page view), runs the
64
- PostgreSQL EXPLAIN inside `SET LOCAL transaction_read_only = on` with a 5s
65
- `statement_timeout` instead of `Timeout.timeout`, and always rolls its savepoint back.
66
- - **Deployments API input is bounded.** `revision` is capped at 255 characters,
67
- serialized `metadata` at 4 KB, and `started_at` may be at most one hour in the future
68
- (a forged future marker would have stamped every later exception with a revision
69
- that never shipped). `rails_pulse_deployments` now has a count cap
70
- (`max_table_records`, default 1 000) enforced by `CleanupService`; deployments are
71
- never pruned by age.
72
- - **Backtrace source snippets are limited to `app/`, `lib/` and `config/routes.rb`**
73
- (`.rb`, `.erb`, `.rake`, `.haml`, `.slim`, `.jbuilder`). Previously any file under
74
- `Rails.root` was readable through a backtrace frame, including initializers with
75
- inline keys, `config/database.yml`, and `.env`.
76
- - **Standalone dashboard session cookie is `Secure` in production** (override with
77
- `RAILS_PULSE_INSECURE_SESSION=1`). The rackup's "no Rails app" branch, which could
78
- never boot (`NameError: uninitialized constant Rails`), is replaced with a clear
79
- error; the deployment docs now give the working invocation.
91
+ - Job failure messages (`rails_pulse_job_runs.error_message`) are now redacted the same way exception messages are.
92
+ - CSRF protection is now declared directly by the engine instead of depending on the host's `load_defaults` version.
93
+ - Fixed `authentication_method` granting access when it returned a falsy-but-not-`false`/redirect value; it's now fail-closed.
94
+ - Hardened EXPLAIN analysis against SQL injection and added a statement timeout.
95
+ - Bounded deployment API input (revision length, metadata size, future timestamps) and capped the `rails_pulse_deployments` row count.
96
+ - Backtrace source snippets are now limited to `app/`, `lib/`, and `config/routes.rb`, instead of any file under `Rails.root`.
97
+ - The standalone dashboard's session cookie is now `Secure` in production.
80
98
 
81
99
  ### Added
82
100
 
83
- - **Exception tracking** — Captures unhandled exceptions from web requests and
84
- background jobs, groups them by class and location, and displays backtraces (first
85
- 50 frames) with filtered request params in a new Exceptions tab. Exception
86
- messages are redacted before storage: `key=value` / `key: value` / `"key":"value"`
87
- fragments are masked whenever the key matches `config.filter_parameters`, and
88
- MySQL `Duplicate entry '<value>'` values are redacted alongside PostgreSQL
89
- `DETAIL:` clauses.
90
- - **`track_exceptions` config option** — Gem default is `false`, so existing installs
91
- do not begin capturing on upgrade; the upgrade generator inserts `false`
92
- explicitly. The install template sets `true` for new apps.
93
- - **`capture_exception_params` config option** — Captures request params alongside
94
- each occurrence, filtered via Rails' `filter_parameters`. Occurrences whose params
95
- exceed 10 KB after filtering are stored without params. Default `true`, and only
96
- consulted while `track_exceptions` is on.
97
- - **`exception_message_filter` config option** — an optional `->(message, exception)`
98
- hook applied after the built-in redaction for app-specific patterns. If the hook
99
- raises, the message is stored as `[FILTERED]` rather than unfiltered.
100
- - **`authorize` config option** — a fail-closed predicate, `->(controller) { … }`
101
- (or a zero-argument proc run in the controller). Anything falsy is a 403. Runs after
102
- `authentication_method` when both are set, and replaces the HTTP Basic fallback on
103
- its own. This is now the recommended way to gate the dashboard.
104
- - **Upgrade generator syncs new initializer settings** — Appends keys (and
105
- `max_table_records` entries) the host file does not already mention, without
106
- rewriting existing values. Review with `git diff`.
101
+ - **Exception tracking.** Captures unhandled exceptions from requests and jobs, with grouping, backtraces, and redacted params in a new Exceptions tab.
102
+ - **`track_exceptions` config option**, off by default for existing installs and on by default for new ones.
103
+ - **`capture_exception_params` config option** to include filtered request params with each exception occurrence.
104
+ - **`exception_message_filter` config option** for app-specific redaction beyond the built-in rules.
105
+ - **`authorize` config option** — a fail-closed predicate for gating dashboard access; now the recommended approach.
106
+ - **The upgrade generator now syncs new initializer settings** into the host's config file without overwriting existing values.
107
107
 
108
108
  ### Changed
109
109
 
110
- - **BREAKING — route identity is now `[controller_action, path]`.** GET `/users`
111
- (`users#index`) and POST `/users` (`users#create`) are distinct routes. Dynamic
112
- paths are normalized at capture time (`/posts/42` → `/posts/:id`).
113
- - **A one-time backfill is mandatory.** `rails rails_pulse:migrate_routes` backfills
114
- `controller_action` (from the live router, then from request history), collapses
115
- historical literal paths, and merges routes sharing an action (GET+POST `/sign_in`
116
- → `sessions#new`). A schema migrate alone leaves the Action column empty. The
117
- upgrade generator and the dashboard both warn when the step is outstanding.
118
- - **The JavaScript bundle is 66% smaller: 2.19 MB → 759 KB (754 KB → 248 KB gzipped).**
119
- ECharts is now tree-shaken — `application.js` imports from `echarts/core` and
120
- registers only the series and components the dashboard uses, instead of the
121
- `echarts` barrel that bundled every chart type including maps, sankey and gauge.
122
- Charts outside that set must now register their module explicitly; see
123
- `docs/stimulus_echarts_usage.md`. The stylesheet is 5% smaller.
110
+ - **BREAKING — route identity is now `[controller_action, path]`**, so different HTTP methods on the same path are tracked as distinct routes.
111
+ - **A one-time `rails rails_pulse:migrate_routes` backfill is required** after migrating; a schema migrate alone leaves the Action column empty.
112
+ - **The JavaScript bundle is 66% smaller** (2.19 MB → 759 KB) after tree-shaking ECharts.
124
113
 
125
114
  ### Removed
126
115
 
127
- - **BREAKING — `rails_pulse_routes.method` is dropped.** The HTTP verb now lives on
128
- each request (`rails_pulse_requests.method`); a route carries an `http_methods`
129
- array instead. All application, worker, and dashboard processes must be restarted
130
- together against the migrated schema. A rolling restart that leaves 0.3.x
131
- processes running will silently drop request tracking and 500 the routes dashboard.
132
- - **BREAKING — the route migration is irreversible.** `db:rollback` raises
133
- `ActiveRecord::IrreversibleMigration` on
134
- `ChangeRailsPulseRoutesToMultiVerbModel`, and routes merged by
135
- `rails_pulse:migrate_routes` are deleted with no audit trail. Recovery from a bad
136
- upgrade is restore-from-backup only.
137
- - **Three unreachable Stimulus controllers** — `form`, `timezone` and
138
- `period_selector` — were bundled and registered despite no view referencing them.
139
- `timezone` also ran a `MutationObserver` over the whole document body.
140
- - **`theme.js`**, which registered a chart theme that `application.js` immediately
141
- overwrote. Its UMD wrapper called `require('echarts')`, which alone would have
142
- defeated the tree-shaking above.
143
- - **Dead CSS**: unused css-zero ports, the removed dashboard period selector's styles,
144
- and the disabled `toolbox` chart option.
145
- - **`csp-test.js` is no longer shipped in the gem.** Its page is mounted only when
146
- `Rails.env.local?`, so it was unreachable in a published gem. It remains in the
147
- repository for development and the CSP compliance system test.
116
+ - **BREAKING — `rails_pulse_routes.method` is dropped**; the HTTP verb now lives on each request. Restart all processes together after migrating.
117
+ - **BREAKING — the route migration is irreversible.** Back up your database first.
118
+ - Removed three unused Stimulus controllers (`form`, `timezone`, `period_selector`).
119
+ - Removed `theme.js`, a chart theme that was immediately overwritten and would have defeated ECharts tree-shaking.
120
+ - Removed dead CSS: unused css-zero ports, old period-selector styles, and a disabled toolbox option.
121
+ - `csp-test.js` is no longer shipped in the published gem.
148
122
 
149
123
  ### Fixed
150
124
 
151
- - **Tag filters work on SQLite for tags containing `_`.** The `LIKE` patterns were
152
- escaped but issued without an `ESCAPE` clause; SQLite has no default escape
153
- character, so `my_tag` never matched and "hide records tagged my_tag" hid nothing.
154
- All three adapters now get an explicit `ESCAPE '!'`.
155
- - **Hand-edited query strings no longer 500.** `?q=string`, unparseable custom date
156
- ranges, unparseable `occurred_at` filters, a non-array `enabled_tags`, and
157
- oversized or unparseable session time filters all fall back to defaults instead of
158
- raising. Session-stored presets and performance thresholds are validated against
159
- the known values.
160
- - **Chart click/zoom got slower the more you switched chart tabs.** The requests,
161
- routes and queries index pages attached a fresh `document` mouseup listener every
162
- time a chart tab was re-selected, and only the most recent one was ever removed.
163
- After N switches a single click anywhere on the page re-ran the zoom handler N
164
- times. The listener is now bound once per controller.
165
- - **Zooming onto the first column of a category chart reset the range.** Visible-range
166
- extraction used `dataZoom.startValue || 0`, so a legitimate index of `0` fell through
167
- to the full-axis fallback.
168
- - **Hover popovers could throw after a table refresh.** `popover_controller` left its
169
- show/hide timers running on disconnect, so a pending 700ms open could fire against
170
- an element a fetch had already replaced.
171
- - **The time range selector's hover border was invisible.** It referenced
172
- `--color-border-emphasis`, which is defined nowhere, so the declaration was invalid
173
- at computed-value time and the border fell back to `currentColor`. Its light-mode
174
- rules also never applied on a first visit, because `data-color-scheme` is only set
175
- after an explicit toggle.
176
- - **`[popover].positioned` was declared twice** at equal specificity, so correct
177
- popover placement depended on CSS file load order. Merged into one rule.
125
+ - Fixed tag filters not matching tags containing `_` on SQLite.
126
+ - Fixed various hand-edited or malformed query strings causing 500s instead of falling back to defaults.
127
+ - Fixed chart click/zoom handlers accumulating on every chart tab switch, slowing clicks down over time.
128
+ - Fixed zooming to the first column of a category chart resetting the range instead of applying it.
129
+ - Fixed hover popovers throwing after a table refresh replaced the underlying element.
130
+ - Fixed the time range selector's hover border being invisible due to an undefined CSS variable.
131
+ - Fixed a duplicate CSS rule that made popover placement depend on file load order.
178
132
  - Fixed upgrading when using a separate database installation.
179
- - Separate-database installs set `schema_dump: false`, so `db:migrate` no longer
180
- dumps or loads `db/rails_pulse_structure.sql` (#189).
181
- - Fixed `assets:precompile` failing on memory-constrained hosts by not registering
182
- dashboard assets with the host Sprockets pipeline. Precompile copies the pre-built
183
- files into `public/assets` (digested, no compressor) so `config.asset_host` and
184
- CDN-only CSP work; development and hosts without a pipeline still use
185
- `/rails-pulse-assets/<gem-version>/...`.
186
- - SQLite `schema.rb` now keeps the partial unique index on unrecognised routes
187
- (`WHERE controller_action IS NULL`). A raw `CREATE INDEX` was dumped without the
188
- predicate, so `db:schema:load` and parallel tests unique-constrained every path.
133
+ - Separate-database installs now set `schema_dump: false` so `db:migrate` doesn't dump or load `db/rails_pulse_structure.sql` (#189).
134
+ - Fixed `assets:precompile` OOMing on memory-constrained hosts by not registering dashboard assets with Sprockets.
135
+ - Fixed SQLite's `schema.rb` dropping the partial unique index on unrecognised routes, which over-constrained paths on `db:schema:load`.
189
136
 
190
137
  ### Upgrading from 0.3.x
191
138
 
@@ -209,21 +156,19 @@ once you have reviewed what is captured.
209
156
 
210
157
  ## [0.3.3] - 2026-06-23
211
158
 
212
- - **Deployment tracking** — Record deployments via `POST /rails_pulse/deployments` or `rake rails_pulse:record_deployment[sha]`. Deployments appear as vertical marker lines on performance charts so you can correlate releases with regressions
213
- - **`deployment_api_token` config option** — Secures the deployments endpoint with a token header for CI/CD use
214
- - All multi-series charts now use a native ECharts time axis (`[timestamp_ms, value]` pairs) instead of a separate labels array, enabling deployment markers and better zoom behaviour
159
+ - **Deployment tracking** — Record deployments via `POST /rails_pulse/deployments` or `rake rails_pulse:record_deployment[sha]`, shown as marker lines on performance charts.
160
+ - **`deployment_api_token` config option** — Secures the deployments endpoint with a token header for CI/CD use.
161
+ - Charts now use a native ECharts time axis instead of a separate labels array, enabling deployment markers and better zoom behaviour.
215
162
 
216
163
  ## [0.3.0] - 2026-04-30
217
164
 
218
- This is the largest release to date — a full UI overhaul across every section of
219
- the dashboard. Charts are now switchable, the dashboard surfaces health status
220
- automatically, and the underlying controller and model code has been substantially
221
- refactored. Existing installs need to run the two new migrations. Hopefully future
222
- releases will be smaller and more incremental than this one.
165
+ This is the largest release to date — a full UI overhaul with switchable charts and
166
+ automatic health status across the dashboard. Existing installs need to run two new
167
+ migrations.
223
168
 
224
169
  ### Added
225
170
 
226
- - **New chart types across all sections** — Routes, queries, and jobs now have dedicated chart panels with switchable chart types (response time percentiles, request volume, error rate, execution volume, duration, failure rate)
171
+ - **New chart types across all sections** — Routes, queries, and jobs now have dedicated, switchable chart panels (percentiles, volume, error/failure rate, duration)
227
172
  - **Dashboard health summary** — New `HealthSummary` model surfaces an overall health status and highlights routes/queries/jobs that need attention
228
173
  - **Dashboard "Needs Attention" section** — Automatically surfaces slow routes, high error rates, and problematic jobs without manual digging
229
174
  - **Storage pressure indicator** — Tracks and displays database storage growth so you know when to adjust retention settings
@@ -249,16 +194,16 @@ releases will be smaller and more incremental than this one.
249
194
 
250
195
  ### Changed
251
196
 
252
- - **Dashboard redesigned** — Full dashboard overhaul with health summary, attention sections, and chart panels replacing the previous card layout
197
+ - **Dashboard redesigned** — Full overhaul with health summary, attention sections, and chart panels replacing the previous card layout
253
198
  - **Routes, queries, and jobs index pages redesigned** — Consistent layout with metric cards, switchable chart tabs, and paginated tables
254
199
  - **Metric card component updated** — Cards now support a richer data structure with trend indicators
255
- - **Application controller refactored** — Controller concerns split into focused modules (`ChartTableConcern`, `MetricCardConcern`, `PaginationConcern`, `SessionFiltersConcern`, `TagFilterConcern`, `TimeRangeConcern`, `ZoomRangeConcern`)
256
- - **`normalized_sql` column expanded to `text`** — Removes the 1000-character limit on PostgreSQL and MySQL (migration provided for existing installs)
200
+ - **Application controller refactored** — Split into focused concerns (`ChartTableConcern`, `MetricCardConcern`, `PaginationConcern`, `SessionFiltersConcern`, `TagFilterConcern`, `TimeRangeConcern`, `ZoomRangeConcern`)
201
+ - **`normalized_sql` column expanded to `text`** — Removes the 1000-character limit on PostgreSQL and MySQL (migration provided)
257
202
  - **Diagnostic fields added to queries table** — New columns captured at instrumentation time
258
203
  - **Summary job and service refactored** — Cleaner separation between job scheduling and summary calculation logic
259
204
  - **Cleanup job refactored** — Now uses `CleanupTaskRunner` and reports statistics via `CleanupStatsReporter`
260
205
  - **Configuration expanded** — New configuration options exposed in the initializer template
261
- - **Upgrade generator simplified** — Generator now delegates to `MigrationInstaller` and `ConfigInstaller`
206
+ - **Upgrade generator simplified** — Now delegates to `MigrationInstaller` and `ConfigInstaller`
262
207
  - **Asset server middleware updated** — Improved asset serving reliability
263
208
  - **Request collector middleware updated** — Performance improvements and cleaner instrumentation
264
209
  - **Operation subscriber refactored** — Cleaner event handling and reduced complexity
@@ -269,10 +214,9 @@ releases will be smaller and more incremental than this one.
269
214
 
270
215
  - `StatusHelper` removed — status rendering consolidated into model concerns and view components
271
216
  - `ChartFormatters` helper removed — chart formatting moved into chart model classes
272
- - `chart_formatters_test.rb` removed alongside the helper
273
217
  - Slow queries and slow routes table models removed — replaced by the Needs Attention system
274
218
  - Unused `average_response_time` and `p95_response_time` dashboard chart classes removed
275
- - `requests/tables/index.rb` and related test removed — requests index now uses a shared approach
219
+ - `requests/tables/index.rb` removed — requests index now uses a shared approach
276
220
  - `routes/cards/average_response_times` and `routes/charts/average_response_times` removed — replaced by percentile-based equivalents
277
221
  - `jobs/cards/average_duration` and `jobs/cards/total_jobs` removed — replaced by updated cards
278
222
  - Timezone controller removed