dial 0.5.1 → 0.6.0

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: d4f60345ab5528878505773aa1b5875d4e00164ac9e94968584a40ecbb18b251
4
- data.tar.gz: 5d2d5399d37be584ed2dd17f39cd98ed45af05a66e2eb8609fd340ef54dbeeb9
3
+ metadata.gz: d3b2e0414c826c6c8e7237a5b1033b102ee9079491e1d2863db6ffb961a3fda2
4
+ data.tar.gz: 859585a4e0bcf37eb6c36c2f71e1d16cad9e351c4435cc91981c9254704d21bd
5
5
  SHA512:
6
- metadata.gz: f78cf96c6482b571710c49363e00e7ffb5e65f8e07f180d9e5d99ce862af4d28c2032ff8757b565dc053699e0d4d044dfae26346de297b310c4324c659101216
7
- data.tar.gz: 522ab5c9d3f3e556a71f440256a36d6f2bedc0abb5dccab05c147bc21a50332da581683188fcb1f672c58fc05773782d65fb575995d6adc0f0a856b9e78d6e8c
6
+ metadata.gz: dd66117fe0e8c2fcfac7dacb5348cda5eee119e8f199ead4a5161a5af024e52c50f52765dcdcb0804fcbbd53c061d4f9073287322d0146300f7508d82c9c6823
7
+ data.tar.gz: 6bad07093895c0e2c4d09fd53afecce8d344f189d07eb576d29202ba308009f67eff06a1263ac8d27c2587ca3d3c46f650ffafc499761d56d3f2a092d396d402
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.6.0] - 2025-10-23
4
+
5
+ - Stop suffixing mounted routes with 'dial/'
6
+ - Add keyboard shortcut to toggle panel visibility
7
+
8
+ ## [0.5.2] - 2025-10-04
9
+
10
+ - Preserve existing prosopite custom loggers
11
+
3
12
  ## [0.5.1] - 2025-09-27
4
13
 
5
14
  - Don't clean up stale storage data in railtie
data/README.md CHANGED
@@ -32,7 +32,7 @@ bundle install
32
32
 
33
33
  ```ruby
34
34
  # this will mount the engine at /dial
35
- mount Dial::Engine, at: "/"
35
+ mount Dial::Engine, at: "/dial"
36
36
  ```
37
37
 
38
38
  4. (Optional) Configure the gem in an initializer:
@@ -66,6 +66,7 @@ Option | Description | Default
66
66
  `storage` | Storage adapter class for profile data. | `Dial::Storage::FileAdapter`
67
67
  `storage_options` | Options hash passed to storage adapter. | `{ ttl: 3600 }`
68
68
  `content_security_policy_nonce` | Sets the content security policy nonce to use when inserting Dial's script. Can be a string, or a Proc which receives `env` and response `headers` as arguments and returns the nonce string. | Rails generated nonce or `nil`
69
+ `toggle_shortcut_keys` | Array of keys for keyboard shortcut to toggle panel visibility. | `["Alt", "Shift", "D"]`
69
70
  `vernier_interval` | Sets the `interval` option for vernier. | `200`
70
71
  `vernier_allocation_interval` | Sets the `allocation_interval` option for vernier. | `2_000`
71
72
  `prosopite_ignore_queries` | Sets the `ignore_queries` option for prosopite. | `[/schema_migrations/i]`
@@ -18,6 +18,7 @@ module Dial
18
18
  storage: Storage::FileAdapter,
19
19
  storage_options: { ttl: STORAGE_TTL },
20
20
  content_security_policy_nonce: -> env, _headers { env[NONCE] || EMPTY_NONCE },
21
+ toggle_shortcut_keys: TOGGLE_SHORTCUT_KEYS,
21
22
  vernier_interval: VERNIER_INTERVAL,
22
23
  vernier_allocation_interval: VERNIER_ALLOCATION_INTERVAL,
23
24
  prosopite_ignore_queries: PROSOPITE_IGNORE_QUERIES,
@@ -21,6 +21,7 @@ module Dial
21
21
  SAMPLING_PERCENTAGE_PROD = 1
22
22
  STORAGE_TTL = 60 * 60
23
23
  EMPTY_NONCE = ""
24
+ TOGGLE_SHORTCUT_KEYS = ["Alt", "Shift", "D"].freeze
24
25
 
25
26
  VERNIER_INTERVAL = 200
26
27
  VERNIER_ALLOCATION_INTERVAL = 2_000
@@ -3,41 +3,39 @@
3
3
  require "uri"
4
4
 
5
5
  Dial::Engine.routes.draw do
6
- scope path: "/dial", as: "dial" do
7
- get "profile", to: lambda { |env|
8
- query_params = URI.decode_www_form(env[::Rack::QUERY_STRING]).to_h
9
- profile_key = query_params["key"]
10
- unless profile_key && profile_key.match?(/\A[0-9a-f-]+_vernier\z/i)
11
- return [
12
- 400,
13
- { "Content-Type" => "text/plain" },
14
- ["Bad Request"]
15
- ]
16
- end
6
+ get "/profile", to: lambda { |env|
7
+ query_params = URI.decode_www_form(env[::Rack::QUERY_STRING]).to_h
8
+ profile_key = query_params["key"]
9
+ unless profile_key && profile_key.match?(/\A[0-9a-f-]+_vernier\z/i)
10
+ return [
11
+ 400,
12
+ { "Content-Type" => "text/plain" },
13
+ ["Bad Request"]
14
+ ]
15
+ end
17
16
 
18
- profile_storage_key = Dial::Storage.profile_storage_key profile_key
19
- begin
20
- content = Dial::Storage.fetch profile_storage_key
21
- if content
22
- [
23
- 200,
24
- { "Content-Type" => "application/json", "Access-Control-Allow-Origin" => Dial::VERNIER_VIEWER_URL },
25
- [content]
26
- ]
27
- else
28
- [
29
- 404,
30
- { "Content-Type" => "text/plain" },
31
- ["Not Found"]
32
- ]
33
- end
34
- rescue
17
+ profile_storage_key = Dial::Storage.profile_storage_key profile_key
18
+ begin
19
+ content = Dial::Storage.fetch profile_storage_key
20
+ if content
21
+ [
22
+ 200,
23
+ { "Content-Type" => "application/json", "Access-Control-Allow-Origin" => Dial::VERNIER_VIEWER_URL },
24
+ [content]
25
+ ]
26
+ else
35
27
  [
36
- 500,
28
+ 404,
37
29
  { "Content-Type" => "text/plain" },
38
- ["Internal Server Error"]
30
+ ["Not Found"]
39
31
  ]
40
32
  end
41
- }
42
- end
33
+ rescue
34
+ [
35
+ 500,
36
+ { "Content-Type" => "text/plain" },
37
+ ["Internal Server Error"]
38
+ ]
39
+ end
40
+ }
43
41
  end
@@ -71,6 +71,8 @@ module Dial
71
71
  </div>
72
72
  </div>
73
73
 
74
+ <div id="dial-hidden-indicator">Dial</div>
75
+
74
76
  <script nonce="#{configured_nonce env, headers}">
75
77
  #{script}
76
78
  </script>
@@ -149,19 +151,80 @@ module Dial
149
151
  color: black;
150
152
  }
151
153
  }
154
+
155
+ #dial-hidden-indicator {
156
+ all: initial;
157
+ position: fixed;
158
+ bottom: 0.5rem;
159
+ right: 0.5rem;
160
+ z-index: 9999;
161
+ background-color: white;
162
+ color: black;
163
+ padding: 0.25rem 0.5rem;
164
+ border-radius: 0.25rem;
165
+ font-size: 0.75rem;
166
+ cursor: pointer;
167
+ display: none;
168
+ box-shadow: -0.2rem -0.2rem 0.4rem rgba(0, 0, 0, 0.5);
169
+ }
152
170
  CSS
153
171
  end
154
172
 
155
173
  def script
174
+ shortcut_keys = Dial._configuration.toggle_shortcut_keys
175
+
156
176
  <<~JS
177
+ var dialPanel = document.getElementById("dial");
157
178
  var dialPreview = document.getElementById("dial-preview");
158
179
  var dialDetails = document.getElementById("dial-details");
180
+ var dialHiddenIndicator = document.getElementById("dial-hidden-indicator");
181
+
182
+ function getDialHiddenState() {
183
+ var stored = localStorage.getItem("dial_panel_hidden");
184
+ if (!stored) return false;
185
+
186
+ try {
187
+ var data = JSON.parse(stored);
188
+ var expiresAt = new Date(data.expiresAt);
189
+ if (new Date() > expiresAt) {
190
+ localStorage.removeItem("dial_panel_hidden");
191
+ return false;
192
+ }
193
+ return data.hidden;
194
+ } catch (e) {
195
+ localStorage.removeItem("dial_panel_hidden");
196
+ return false;
197
+ }
198
+ }
199
+
200
+ function setDialHiddenState(hidden) {
201
+ var expiresAt = new Date();
202
+ expiresAt.setTime(expiresAt.getTime() + (24 * 60 * 60 * 1000));
203
+ localStorage.setItem("dial_panel_hidden", JSON.stringify({
204
+ hidden: hidden,
205
+ expiresAt: expiresAt.toISOString()
206
+ }));
207
+ }
208
+
209
+ function toggleDialPanel() {
210
+ var isHidden = dialPanel.style.display === "none";
211
+ dialPanel.style.display = isHidden ? "flex" : "none";
212
+ dialHiddenIndicator.style.display = isHidden ? "none" : "block";
213
+ setDialHiddenState(!isHidden);
214
+ }
215
+
216
+ if (getDialHiddenState()) {
217
+ dialPanel.style.display = "none";
218
+ dialHiddenIndicator.style.display = "block";
219
+ }
159
220
 
160
221
  dialPreview.addEventListener("click", () => {
161
222
  var isCollapsed = ["", "none"].includes(dialDetails.style.display);
162
223
  dialDetails.style.display = isCollapsed ? "block" : "none";
163
224
  });
164
225
 
226
+ dialHiddenIndicator.addEventListener("click", toggleDialPanel);
227
+
165
228
  document.addEventListener("click", (event) => {
166
229
  if (!dialPreview.contains(event.target) && !dialDetails.contains(event.target)) {
167
230
  dialDetails.style.display = "none";
@@ -172,6 +235,26 @@ module Dial
172
235
  });
173
236
  }
174
237
  });
238
+
239
+ document.addEventListener("keydown", (event) => {
240
+ var keys = #{shortcut_keys.to_json};
241
+ var expectedKey = keys[keys.length - 1].toLowerCase();
242
+ var keyPressed = event.code.toLowerCase() === "key" + expectedKey || event.key.toLowerCase() === expectedKey;
243
+ var modifiersMatch = true;
244
+
245
+ for (var i = 0; i < keys.length - 1; i++) {
246
+ var modifier = keys[i].toLowerCase();
247
+ if (modifier === "alt" && !event.altKey) modifiersMatch = false;
248
+ if (modifier === "ctrl" && !event.ctrlKey) modifiersMatch = false;
249
+ if (modifier === "shift" && !event.shiftKey) modifiersMatch = false;
250
+ if (modifier === "meta" && !event.metaKey) modifiersMatch = false;
251
+ }
252
+
253
+ if (keyPressed && modifiersMatch) {
254
+ event.preventDefault();
255
+ toggleDialPanel();
256
+ }
257
+ });
175
258
  JS
176
259
  end
177
260
 
@@ -192,7 +275,7 @@ module Dial
192
275
  def formatted_profile_output env, profile_key
193
276
  url_base = ::Rails.application.routes.url_helpers.dial_url host: env[::Rack::HTTP_HOST]
194
277
  prefix = "/" unless url_base.end_with? "/"
195
- profile_out_url = URI.encode_www_form_component url_base + "#{prefix}dial/profile?key=#{profile_key}"
278
+ profile_out_url = URI.encode_www_form_component url_base + "#{prefix}profile?key=#{profile_key}"
196
279
 
197
280
  "<a href='https://vernier.prof/from-url/#{profile_out_url}' target='_blank'>View profile</a>"
198
281
  end
@@ -2,8 +2,35 @@
2
2
 
3
3
  require "active_support/core_ext/string/filters"
4
4
 
5
+ require_relative "prosopite_composite_logger"
6
+
5
7
  module Dial
6
8
  module Prosopite
9
+ def custom_logger= logger
10
+ return super logger if @setting_dial_logger
11
+
12
+ @original_logger = logger
13
+
14
+ if @dial_logger && @original_logger
15
+ super ProsopiteCompositeLogger.new @dial_logger, @original_logger
16
+ else
17
+ super logger
18
+ end
19
+ end
20
+
21
+ def dial_logger= logger
22
+ @dial_logger = logger
23
+
24
+ @setting_dial_logger = true
25
+ if @original_logger
26
+ self.custom_logger = ProsopiteCompositeLogger.new @dial_logger, @original_logger
27
+ else
28
+ self.custom_logger = @dial_logger
29
+ end
30
+ ensure
31
+ @setting_dial_logger = false
32
+ end
33
+
7
34
  def send_notifications
8
35
  tc[:prosopite_notifications] = tc[:prosopite_notifications].to_h do |queries, kaller|
9
36
  [queries.map { |query| query.squish }, kaller]
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+
5
+ module Dial
6
+ class ProsopiteCompositeLogger
7
+ def initialize dial_logger, existing_logger = nil
8
+ @dial_logger = dial_logger
9
+ @existing_logger = existing_logger
10
+ end
11
+
12
+ def level
13
+ @dial_logger.level
14
+ end
15
+
16
+ def level= value
17
+ @dial_logger.level = value
18
+ @existing_logger.level = value if @existing_logger&.respond_to? :level=
19
+ end
20
+
21
+ def method_missing method, *args, &block
22
+ result = nil
23
+ result = @dial_logger.send method, *args, &block if @dial_logger.respond_to? method
24
+ @existing_logger.send method, *args, &block if @existing_logger&.respond_to? method
25
+ result
26
+ end
27
+
28
+ def respond_to_missing? method, include_private = false
29
+ @dial_logger.respond_to?(method, include_private) ||
30
+ @existing_logger&.respond_to?(method, include_private) ||
31
+ false
32
+ end
33
+ end
34
+ end
data/lib/dial/railtie.rb CHANGED
@@ -15,7 +15,7 @@ module Dial
15
15
  if ::ActiveRecord::Base.configurations.configurations.any? { |config| config.adapter == "postgresql" }
16
16
  require "pg_query"
17
17
  end
18
- ::Prosopite.custom_logger = ProsopiteLogger.new
18
+ ::Prosopite.dial_logger = ProsopiteLogger.new
19
19
 
20
20
  # finalize configuration
21
21
  Dial._configuration.freeze
data/lib/dial/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dial
4
- VERSION = "0.5.1"
4
+ VERSION = "0.6.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dial
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Young
@@ -114,6 +114,7 @@ files:
114
114
  - lib/dial/middleware/rails_stat.rb
115
115
  - lib/dial/middleware/ruby_stat.rb
116
116
  - lib/dial/prosopite.rb
117
+ - lib/dial/prosopite_composite_logger.rb
117
118
  - lib/dial/prosopite_logger.rb
118
119
  - lib/dial/railtie.rb
119
120
  - lib/dial/storage.rb
@@ -142,7 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
142
143
  - !ruby/object:Gem::Version
143
144
  version: '0'
144
145
  requirements: []
145
- rubygems_version: 3.7.2
146
+ rubygems_version: 4.0.0.dev
146
147
  specification_version: 4
147
148
  summary: A modern profiler for your Rails application
148
149
  test_files: []