rails_pulse 0.4.0 → 0.4.1
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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f1a0c55a1386d00790c4bada3da5ed8b34c17e670c7bf1a0c8c55a55c662b16c
|
|
4
|
+
data.tar.gz: 6de45806d2c72656dcc2ef06e61e00a7dd013e826a69a3f12f49cecf9efd360e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a946a339e72c604b0202f28f2e5650f9034619c1737f308cc14a640331a9d25ed60cfa86fc03c2c5a5bc9ab0793ab661844c93bcd1d394d0eedf7984e2fca211
|
|
7
|
+
data.tar.gz: ebb59bb9f9c60c0a9b5ce5ab0a778d08396a841cab960b6f4f4a16a41ca86a38e5ab54e3902b949327b84f5bb6618a7de35366993fded467e8a3b6e5e946ddc6
|
data/CHANGELOG.md
CHANGED
|
@@ -7,7 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
## [0.4.
|
|
10
|
+
## [0.4.1] - 2026-09-23
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- A test that fails, naming the key, when a default in `RailsPulse::Configuration` and the install template drift apart. (#272)
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Documented what a tracked request costs, measured against the bounded writer: about 25 microseconds of request-thread overhead per request on SQLite and PostgreSQL. (#275)
|
|
19
|
+
- The release gate and CI now fail if anything under `app/` branches on `Rails.env`, so a code path the test suite cannot reach can no longer ship. (#273)
|
|
20
|
+
- Loosened the `ransack` dependency from `~> 4.0` to `>= 4.0, < 6` so host apps can upgrade to ransack 5 without dropping Rails Pulse.
|
|
21
|
+
- `rake test` now refuses to run when the dummy database was last set up for a different adapter, instead of loading that adapter's `schema.rb` and failing two index tests. (#276)
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- SQL normalization no longer raises `Regexp::TimeoutError` on long queries, which could fail the whole request when N+1 detection ran during collection.
|
|
26
|
+
- **Metric card headline numbers now honour the global tag filter.** Disabling a tag changed each card's sparkline but not the number beside it, so the two disagreed. Both are now computed over the same summaries. (#274)
|
|
11
27
|
|
|
12
28
|
## [0.4.0] - 2026-09-22
|
|
13
29
|
|
|
@@ -62,10 +62,10 @@ module RailsPulse
|
|
|
62
62
|
period_start: range_start..now
|
|
63
63
|
)
|
|
64
64
|
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
# The same tag filter the sparkline applies, so the headline number and
|
|
66
|
+
# the sparkline beside it are computed over the same summaries. Cards
|
|
67
|
+
# receive the filter as instance variables; nil means unfiltered.
|
|
68
|
+
query = query.with_tag_filters(@disabled_tags || [], @show_non_tagged != false)
|
|
69
69
|
|
|
70
70
|
# Filter to specific resource if provided
|
|
71
71
|
query = query.where(summarizable_id: subject_id) if subject_id
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
module RailsPulse
|
|
2
2
|
class SqlQueryNormalizer
|
|
3
|
+
# Above this length, skip the regex-based literal scan in favor of a
|
|
4
|
+
# slower-per-char but backtracking-free manual scan. Regex matching here
|
|
5
|
+
# is linear (benchmarked at ~0.4s for an 8,000,000-char literal under a
|
|
6
|
+
# 1s Regexp.timeout), but a host app can configure Regexp.timeout far
|
|
7
|
+
# below that, and very long queries are rare enough that the fast path
|
|
8
|
+
# isn't worth the risk. 100_000 chars leaves generous headroom below
|
|
9
|
+
# where even an aggressive timeout config could plausibly trip — see
|
|
10
|
+
# issue #286.
|
|
11
|
+
LONG_QUERY_THRESHOLD = 100_000
|
|
12
|
+
|
|
3
13
|
# Smart normalization: preserve table/column names, replace only literal values
|
|
4
14
|
def self.normalize(query_string)
|
|
5
15
|
new(query_string).normalize
|
|
@@ -72,6 +82,14 @@ module RailsPulse
|
|
|
72
82
|
def replace_literal_values(query)
|
|
73
83
|
normalized = query.dup
|
|
74
84
|
|
|
85
|
+
# Replace string literals (single quotes) FIRST so a very long literal
|
|
86
|
+
# collapses to a single "?" before any other regex in this method has
|
|
87
|
+
# to scan across it — see LONG_QUERY_THRESHOLD and issue #286.
|
|
88
|
+
normalized = replace_quoted_literals(normalized, "'")
|
|
89
|
+
|
|
90
|
+
# Replace double-quoted string literals (not protected identifiers)
|
|
91
|
+
normalized = replace_quoted_literals(normalized, '"')
|
|
92
|
+
|
|
75
93
|
# Replace floating-point numbers FIRST (before integers) to avoid double replacement
|
|
76
94
|
normalized = normalized.gsub(/(?<![a-zA-Z_])\b\d+\.\d+\b(?![a-zA-Z_])/, "?")
|
|
77
95
|
|
|
@@ -79,18 +97,78 @@ module RailsPulse
|
|
|
79
97
|
# Negative lookbehind/lookahead prevents replacing numbers in table/column names
|
|
80
98
|
normalized = normalized.gsub(/(?<![a-zA-Z_])\b\d+\b(?![a-zA-Z_])/, "?")
|
|
81
99
|
|
|
82
|
-
# Replace string literals (single quotes)
|
|
83
|
-
normalized = normalized.gsub(/'(?:[^']|'')*'/, "?")
|
|
84
|
-
|
|
85
|
-
# Replace double-quoted string literals (not protected identifiers)
|
|
86
|
-
normalized = normalized.gsub(/"(?:[^"]|"")*"/, "?")
|
|
87
|
-
|
|
88
100
|
# Handle boolean literals
|
|
89
101
|
normalized = normalized.gsub(/\b(true|false)\b/i, "?")
|
|
90
102
|
|
|
91
103
|
normalized
|
|
92
104
|
end
|
|
93
105
|
|
|
106
|
+
# Replaces quote_char-delimited string literals with a single placeholder.
|
|
107
|
+
# Uses a regex for the common case (fast — see benchmarks in
|
|
108
|
+
# LONG_QUERY_THRESHOLD's comment) and falls back to a manual scan for
|
|
109
|
+
# very long queries, where that regex risks Regexp::TimeoutError under
|
|
110
|
+
# an aggressively configured Regexp.timeout — see issue #286.
|
|
111
|
+
def replace_quoted_literals(query, quote_char)
|
|
112
|
+
if query.length < LONG_QUERY_THRESHOLD
|
|
113
|
+
pattern = quote_char == "'" ? /'(?:[^']|'')*'/ : /"(?:[^"]|"")*"/
|
|
114
|
+
query.gsub(pattern, "?")
|
|
115
|
+
else
|
|
116
|
+
scan_quoted_literals(query, quote_char)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Linear, non-regex scan used above LONG_QUERY_THRESHOLD. A quote left
|
|
121
|
+
# unterminated for the rest of the string is left untouched rather than
|
|
122
|
+
# rescanned from every possible start position, which is what would make
|
|
123
|
+
# a naive manual reimplementation quadratic.
|
|
124
|
+
def scan_quoted_literals(query, quote_char)
|
|
125
|
+
result = +""
|
|
126
|
+
i = 0
|
|
127
|
+
len = query.length
|
|
128
|
+
|
|
129
|
+
while i < len
|
|
130
|
+
char = query[i]
|
|
131
|
+
|
|
132
|
+
unless char == quote_char
|
|
133
|
+
result << char
|
|
134
|
+
i += 1
|
|
135
|
+
next
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
close_index = find_closing_quote(query, i + 1, quote_char)
|
|
139
|
+
|
|
140
|
+
if close_index
|
|
141
|
+
result << "?"
|
|
142
|
+
i = close_index + 1
|
|
143
|
+
else
|
|
144
|
+
result << query[i..]
|
|
145
|
+
break
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
result
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Returns the index of the unescaped closing quote_char starting the
|
|
153
|
+
# search at `start`, treating a doubled quote_char (e.g. '') as an
|
|
154
|
+
# escaped literal quote rather than a terminator. Returns nil if the
|
|
155
|
+
# string is never closed.
|
|
156
|
+
def find_closing_quote(query, start, quote_char)
|
|
157
|
+
j = start
|
|
158
|
+
len = query.length
|
|
159
|
+
|
|
160
|
+
while j < len
|
|
161
|
+
if query[j] == quote_char
|
|
162
|
+
return j unless query[j + 1] == quote_char
|
|
163
|
+
j += 2
|
|
164
|
+
else
|
|
165
|
+
j += 1
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
nil
|
|
170
|
+
end
|
|
171
|
+
|
|
94
172
|
def handle_special_constructs(query)
|
|
95
173
|
normalized = normalize_in_clauses(query)
|
|
96
174
|
normalized.gsub(/\bBETWEEN\s+\?\s+AND\s+\?/i, "BETWEEN ? AND ?")
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
</div>
|
|
70
70
|
|
|
71
71
|
<div class="flex items-center gap">
|
|
72
|
-
<%= link_to rails_pulse.root_path, class: "flex items-center gap mie-2" do %>
|
|
72
|
+
<%= link_to rails_pulse.root_path, class: "flex items-center gap mie-2", title: "Rails Pulse v#{RailsPulse::VERSION}" do %>
|
|
73
73
|
<span class="font-bold">Rails Pulse</span>
|
|
74
74
|
<% end %>
|
|
75
75
|
<nav class="flex items-center gap text-sm text-subtle show@md" style="--column-gap: 1rem">
|
data/lib/rails_pulse/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_pulse
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Scott Harvey
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -48,16 +48,22 @@ dependencies:
|
|
|
48
48
|
name: ransack
|
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
|
-
- - "
|
|
51
|
+
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '4.0'
|
|
54
|
+
- - "<"
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: '6'
|
|
54
57
|
type: :runtime
|
|
55
58
|
prerelease: false
|
|
56
59
|
version_requirements: !ruby/object:Gem::Requirement
|
|
57
60
|
requirements:
|
|
58
|
-
- - "
|
|
61
|
+
- - ">="
|
|
59
62
|
- !ruby/object:Gem::Version
|
|
60
63
|
version: '4.0'
|
|
64
|
+
- - "<"
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '6'
|
|
61
67
|
- !ruby/object:Gem::Dependency
|
|
62
68
|
name: sqlite3
|
|
63
69
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -545,7 +551,7 @@ metadata:
|
|
|
545
551
|
bug_tracker_uri: https://github.com/railspulse/rails_pulse/issues
|
|
546
552
|
rubygems_mfa_required: 'true'
|
|
547
553
|
post_install_message: |
|
|
548
|
-
Rails Pulse 0.4.
|
|
554
|
+
Rails Pulse 0.4.1 installed.
|
|
549
555
|
|
|
550
556
|
UPGRADING from any 0.3.x? This release changes route identity and DROPS
|
|
551
557
|
rails_pulse_routes.method. Back up your database first, then run:
|