floatable-rails 0.1.5 → 0.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +1 -1
- data/lib/floatable/rails/engine.rb +53 -9
- data/lib/floatable/rails/erb_view_instrumentation.rb +24 -15
- data/lib/floatable/rails/helpers.rb +6 -0
- data/lib/floatable/rails/response_middleware.rb +28 -35
- data/lib/floatable/rails/slim_view_instrumentation.rb +8 -1
- data/lib/floatable/rails/version.rb +1 -1
- data/lib/floatable/rails.rb +38 -1
- metadata +1 -2
- data/lib/floatable/rails/tag_instrumentation.rb +0 -181
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e24ac3ac7be11f6b3ef371aa01f178361fb4eac3a3826a324f3774ca56005ad0
|
|
4
|
+
data.tar.gz: 9b07e10f8906475a817797eecc6cb961fe90b9938a47f46cfeaf860d03acf005
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 97fb690d9c0081d42401fb42dc5189a281b123a925168563e72a6baf1d2e92d1dc05f3302c8b8e5f8d89b9a3eb7a7a1573916b8279a0271e122186eda60c38c3
|
|
7
|
+
data.tar.gz: 3cd295707a169159a79cb1b576249115c4b2fbd696c5abf473369f027984965e0702d1a6e8ab9118cd844ee3c040b69be20584d4f222c0a1c7b2fb09c29efdf1
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.6
|
|
4
|
+
|
|
5
|
+
- Simplify instrumentation to path-only metadata (`data-floatable-path`).
|
|
6
|
+
- Remove tag instrumentation and rely on view marker + middleware assignment.
|
|
7
|
+
- Harden fail-open behavior so Floatable errors are logged and do not break host app requests.
|
|
8
|
+
|
|
3
9
|
## 0.1.5
|
|
4
10
|
|
|
5
11
|
- Preserve Vue-style directive/event attributes by using the HTML5 parser when available.
|
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Floatable::Rails
|
|
2
2
|
|
|
3
3
|
Rails integration for Floatable Toolbar. It instruments HTML output with
|
|
4
|
-
|
|
4
|
+
`data-floatable-path` attributes and injects the Floatable script so your UI can be inspected and
|
|
5
5
|
annotated in the browser.
|
|
6
6
|
|
|
7
7
|
## Usage
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
require "rails"
|
|
2
2
|
require "floatable/rails/helpers"
|
|
3
|
-
require "floatable/rails/tag_instrumentation"
|
|
4
3
|
require "floatable/rails/erb_view_instrumentation"
|
|
5
4
|
require "floatable/rails/slim_view_instrumentation"
|
|
6
5
|
require "floatable/rails/response_middleware"
|
|
@@ -9,37 +8,82 @@ module Floatable
|
|
|
9
8
|
module Rails
|
|
10
9
|
class Engine < ::Rails::Engine
|
|
11
10
|
isolate_namespace Floatable::Rails
|
|
11
|
+
class << self
|
|
12
|
+
def with_fail_open_init(name)
|
|
13
|
+
yield
|
|
14
|
+
rescue => e
|
|
15
|
+
::Floatable::Rails.log_error("[Floatable::Rails::Engine] #{name} init failed: #{e.class} - #{e.message}")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def apply_erb_patch!
|
|
19
|
+
handler = ::ActionView::Template::Handlers::ERB
|
|
20
|
+
unless handler.ancestors.include?(::Floatable::Rails::ErbHandlerPatch)
|
|
21
|
+
handler.prepend(::Floatable::Rails::ErbHandlerPatch)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
unless handler.erb_implementation == ::Floatable::Rails::ErubiWithFloatableViewMarkers
|
|
25
|
+
handler.erb_implementation = ::Floatable::Rails::ErubiWithFloatableViewMarkers
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def apply_slim_patch!
|
|
30
|
+
return unless defined?(::Slim::RailsTemplate)
|
|
31
|
+
|
|
32
|
+
slim_handler = ::Slim::RailsTemplate
|
|
33
|
+
return if slim_handler.ancestors.include?(::Floatable::Rails::SlimHandlerPatch)
|
|
34
|
+
|
|
35
|
+
slim_handler.prepend(::Floatable::Rails::SlimHandlerPatch)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
12
38
|
|
|
13
39
|
initializer "floatable.rails.view_helpers" do
|
|
14
40
|
ActiveSupport.on_load(:action_view) do
|
|
15
|
-
|
|
16
|
-
|
|
41
|
+
::Floatable::Rails::Engine.with_fail_open_init("view_helpers") do
|
|
42
|
+
include ::Floatable::Rails::Helpers
|
|
43
|
+
end
|
|
17
44
|
end
|
|
18
45
|
end
|
|
19
46
|
|
|
20
47
|
initializer "floatable.rails.erb_view_instrumentation" do
|
|
21
48
|
ActiveSupport.on_load(:action_view) do
|
|
22
|
-
::
|
|
23
|
-
|
|
49
|
+
::Floatable::Rails::Engine.with_fail_open_init("erb_view_instrumentation") do
|
|
50
|
+
::Floatable::Rails::Engine.apply_erb_patch!
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
config.after_initialize do
|
|
55
|
+
::Floatable::Rails::Engine.with_fail_open_init("erb_view_instrumentation(after_initialize)") do
|
|
56
|
+
::Floatable::Rails::Engine.apply_erb_patch!
|
|
57
|
+
end
|
|
24
58
|
end
|
|
25
59
|
end
|
|
26
60
|
|
|
27
61
|
initializer "floatable.rails.slim_view_instrumentation" do
|
|
28
62
|
ActiveSupport.on_load(:action_view) do
|
|
29
|
-
|
|
63
|
+
::Floatable::Rails::Engine.with_fail_open_init("slim_view_instrumentation") do
|
|
64
|
+
::Floatable::Rails::Engine.apply_slim_patch!
|
|
65
|
+
end
|
|
66
|
+
end
|
|
30
67
|
|
|
31
|
-
|
|
68
|
+
config.after_initialize do
|
|
69
|
+
::Floatable::Rails::Engine.with_fail_open_init("slim_view_instrumentation(after_initialize)") do
|
|
70
|
+
::Floatable::Rails::Engine.apply_slim_patch!
|
|
71
|
+
end
|
|
32
72
|
end
|
|
33
73
|
end
|
|
34
74
|
|
|
35
75
|
initializer "floatable.rails.controller_helpers" do
|
|
36
76
|
ActiveSupport.on_load(:action_controller_base) do
|
|
37
|
-
|
|
77
|
+
::Floatable::Rails::Engine.with_fail_open_init("controller_helpers") do
|
|
78
|
+
helper ::Floatable::Rails::Helpers
|
|
79
|
+
end
|
|
38
80
|
end
|
|
39
81
|
end
|
|
40
82
|
|
|
41
83
|
initializer "floatable.rails.middleware" do |app|
|
|
42
|
-
|
|
84
|
+
::Floatable::Rails::Engine.with_fail_open_init("middleware") do
|
|
85
|
+
app.middleware.use ::Floatable::Rails::ResponseMiddleware
|
|
86
|
+
end
|
|
43
87
|
end
|
|
44
88
|
end
|
|
45
89
|
end
|
|
@@ -21,28 +21,37 @@ module Floatable
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
self.class.erb_implementation.new(erb, options).src
|
|
24
|
+
rescue => e
|
|
25
|
+
::Floatable::Rails.log_error("[Floatable::Rails::ErbHandlerPatch] instrumentation failed: #{e.class} - #{e.message}")
|
|
26
|
+
super(template, source)
|
|
24
27
|
end
|
|
25
28
|
end
|
|
26
29
|
|
|
27
30
|
class ErubiWithFloatableViewMarkers < ::ActionView::Template::Handlers::ERB::Erubi
|
|
28
31
|
def initialize(input, properties = {})
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
32
|
+
instrumented_properties = properties
|
|
33
|
+
|
|
34
|
+
begin
|
|
35
|
+
view_path = floatable_views_path(properties[:filename])
|
|
36
|
+
|
|
37
|
+
if view_path.present?
|
|
38
|
+
instrumented_properties = properties.dup
|
|
39
|
+
bufvar = instrumented_properties[:bufvar] || "@output_buffer"
|
|
40
|
+
postamble = instrumented_properties[:postamble] || bufvar
|
|
41
|
+
|
|
42
|
+
instrumented_properties[:preamble] =
|
|
43
|
+
"#{instrumented_properties[:preamble]}" \
|
|
44
|
+
"@output_buffer.safe_append='<!-- #{::Floatable::Rails::VIEW_COMMENT_BEGIN} #{view_path} -->' if Thread.current[:floatable_enabled];"
|
|
45
|
+
|
|
46
|
+
instrumented_properties[:postamble] =
|
|
47
|
+
"@output_buffer.safe_append='<!-- #{::Floatable::Rails::VIEW_COMMENT_END} #{view_path} -->' if Thread.current[:floatable_enabled];" \
|
|
48
|
+
"#{postamble}"
|
|
49
|
+
end
|
|
50
|
+
rescue => e
|
|
51
|
+
::Floatable::Rails.log_error("[Floatable::Rails::ErubiWithFloatableViewMarkers] initialize instrumentation failed: #{e.class} - #{e.message}")
|
|
43
52
|
end
|
|
44
53
|
|
|
45
|
-
super
|
|
54
|
+
super(input, instrumented_properties)
|
|
46
55
|
end
|
|
47
56
|
|
|
48
57
|
private
|
|
@@ -5,6 +5,9 @@ module Floatable
|
|
|
5
5
|
return false unless respond_to?(:request)
|
|
6
6
|
|
|
7
7
|
::Floatable::Rails.enabled_for?(request)
|
|
8
|
+
rescue => e
|
|
9
|
+
::Floatable::Rails.log_error("[Floatable::Rails::Helpers] floatable_enabled? failed: #{e.class} - #{e.message}")
|
|
10
|
+
false
|
|
8
11
|
end
|
|
9
12
|
|
|
10
13
|
def floatable_tag
|
|
@@ -19,6 +22,9 @@ module Floatable
|
|
|
19
22
|
defer: false,
|
|
20
23
|
data: ::Floatable::Rails.script_data_attributes
|
|
21
24
|
)
|
|
25
|
+
rescue => e
|
|
26
|
+
::Floatable::Rails.log_error("[Floatable::Rails::Helpers] floatable_tag failed: #{e.class} - #{e.message}")
|
|
27
|
+
nil
|
|
22
28
|
end
|
|
23
29
|
end
|
|
24
30
|
end
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
require "nokogiri"
|
|
2
|
-
require "base64"
|
|
3
2
|
|
|
4
3
|
module Floatable
|
|
5
4
|
module Rails
|
|
@@ -9,11 +8,13 @@ module Floatable
|
|
|
9
8
|
end
|
|
10
9
|
|
|
11
10
|
def call(env)
|
|
11
|
+
status = headers = body = nil
|
|
12
|
+
raw = nil
|
|
13
|
+
|
|
12
14
|
enabled = floatable_enabled?(env)
|
|
13
15
|
instrument = enabled && ::Floatable::Rails.instrumentation_enabled?
|
|
14
16
|
Thread.current[:floatable_enabled] = instrument
|
|
15
17
|
|
|
16
|
-
status = headers = body = nil
|
|
17
18
|
begin
|
|
18
19
|
status, headers, body = @app.call(env)
|
|
19
20
|
ensure
|
|
@@ -23,9 +24,7 @@ module Floatable
|
|
|
23
24
|
return [ status, headers, body ] unless enabled
|
|
24
25
|
return [ status, headers, body ] unless html_response?(status, headers)
|
|
25
26
|
|
|
26
|
-
raw =
|
|
27
|
-
body.each { |chunk| raw << chunk.to_s }
|
|
28
|
-
body.close if body.respond_to?(:close)
|
|
27
|
+
raw = read_body(body)
|
|
29
28
|
|
|
30
29
|
instrumented = instrument ? instrument_html(raw) : raw
|
|
31
30
|
final_html = inject_script(instrumented)
|
|
@@ -34,23 +33,29 @@ module Floatable
|
|
|
34
33
|
|
|
35
34
|
[ status, headers, [ final_html ] ]
|
|
36
35
|
rescue => e
|
|
37
|
-
::Rails.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
raise if ::Rails.env.development? || ::Rails.env.test?
|
|
36
|
+
::Floatable::Rails.log_error("[Floatable::Rails::ResponseMiddleware] failed: #{e.class} - #{e.message}")
|
|
37
|
+
::Floatable::Rails.log_backtrace(e)
|
|
38
|
+
return [ status, headers, [ raw ] ] if raw
|
|
39
|
+
return [ status, headers, body ] if status && headers && body
|
|
45
40
|
|
|
46
|
-
|
|
47
|
-
headers ||= {}
|
|
48
|
-
body ||= []
|
|
49
|
-
[ status, headers, body ]
|
|
41
|
+
raise
|
|
50
42
|
end
|
|
51
43
|
|
|
52
44
|
private
|
|
53
45
|
|
|
46
|
+
def read_body(body)
|
|
47
|
+
raw = +""
|
|
48
|
+
return raw if body.nil?
|
|
49
|
+
|
|
50
|
+
body.each { |chunk| raw << chunk.to_s }
|
|
51
|
+
raw
|
|
52
|
+
rescue => e
|
|
53
|
+
::Floatable::Rails.log_error("[Floatable::Rails::ResponseMiddleware] read_body failed: #{e.class} - #{e.message}")
|
|
54
|
+
raw
|
|
55
|
+
ensure
|
|
56
|
+
body.close if body.respond_to?(:close)
|
|
57
|
+
end
|
|
58
|
+
|
|
54
59
|
def html_response?(status, headers)
|
|
55
60
|
return false unless status == 200
|
|
56
61
|
ct = headers["Content-Type"] || headers["content-type"]
|
|
@@ -61,7 +66,7 @@ module Floatable
|
|
|
61
66
|
req = ::Rack::Request.new(env)
|
|
62
67
|
::Floatable::Rails.enabled_for?(req)
|
|
63
68
|
rescue => e
|
|
64
|
-
::Rails.
|
|
69
|
+
::Floatable::Rails.log_error("[Floatable::Rails::ResponseMiddleware] enabled? failed: #{e.class} - #{e.message}")
|
|
65
70
|
false
|
|
66
71
|
end
|
|
67
72
|
|
|
@@ -70,26 +75,19 @@ module Floatable
|
|
|
70
75
|
|
|
71
76
|
doctype = html[/\A\s*<!DOCTYPE[^>]*>/i]
|
|
72
77
|
doc = parse_html(html)
|
|
73
|
-
|
|
74
|
-
doc.css("*").each do |node|
|
|
75
|
-
name = node.name.to_s.downcase
|
|
76
|
-
next if %w[script style].include?(name)
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
apply_view_markers_and_assign_ids(doc)
|
|
78
|
+
apply_view_markers_and_assign_paths(doc)
|
|
80
79
|
|
|
81
80
|
rendered = doc.to_html
|
|
82
81
|
return rendered if doctype.nil? || rendered.lstrip.start_with?("<!DOCTYPE")
|
|
83
82
|
|
|
84
83
|
"#{doctype}\n#{rendered}"
|
|
85
84
|
rescue => e
|
|
86
|
-
::Rails.
|
|
85
|
+
::Floatable::Rails.log_debug("[Floatable::Rails] HTML instrumentation failed: #{e.class} - #{e.message}")
|
|
87
86
|
html
|
|
88
87
|
end
|
|
89
88
|
|
|
90
|
-
def
|
|
89
|
+
def apply_view_markers_and_assign_paths(doc)
|
|
91
90
|
view_stack = []
|
|
92
|
-
counters = Hash.new(0)
|
|
93
91
|
|
|
94
92
|
doc.traverse do |node|
|
|
95
93
|
if node.comment?
|
|
@@ -119,17 +117,12 @@ module Floatable
|
|
|
119
117
|
next if current_path.blank?
|
|
120
118
|
|
|
121
119
|
node["data-floatable-path"] ||= current_path
|
|
122
|
-
if node["data-floatable-id"].blank?
|
|
123
|
-
counters[current_path] += 1
|
|
124
|
-
encoded = Base64.urlsafe_encode64(counters[current_path].to_s, padding: false)
|
|
125
|
-
node["data-floatable-id"] = "#{current_path}:#{encoded}"
|
|
126
|
-
end
|
|
127
120
|
end
|
|
128
121
|
|
|
129
122
|
# Remove all comments, including our markers.
|
|
130
123
|
doc.xpath("//comment()").remove
|
|
131
124
|
rescue => e
|
|
132
|
-
::Rails.
|
|
125
|
+
::Floatable::Rails.log_debug("[Floatable::Rails] apply_view_markers_and_assign_paths failed: #{e.class} - #{e.message}")
|
|
133
126
|
end
|
|
134
127
|
|
|
135
128
|
def parse_html(html)
|
|
@@ -162,7 +155,7 @@ module Floatable
|
|
|
162
155
|
html + snippet
|
|
163
156
|
end
|
|
164
157
|
rescue => e
|
|
165
|
-
::Rails.
|
|
158
|
+
::Floatable::Rails.log_error("[Floatable::Rails::ResponseMiddleware] inject_script failed: #{e.class} - #{e.message}")
|
|
166
159
|
html
|
|
167
160
|
end
|
|
168
161
|
end
|
|
@@ -54,7 +54,14 @@ module Floatable
|
|
|
54
54
|
view_path = floatable_views_path(template.identifier)
|
|
55
55
|
return super if view_path.nil?
|
|
56
56
|
|
|
57
|
-
instrumented =
|
|
57
|
+
instrumented =
|
|
58
|
+
begin
|
|
59
|
+
SlimViewMarkerInstrumenter.instrument(source || template.source, view_path)
|
|
60
|
+
rescue => e
|
|
61
|
+
::Floatable::Rails.log_error("[Floatable::Rails::SlimHandlerPatch] instrumentation failed: #{e.class} - #{e.message}")
|
|
62
|
+
return super
|
|
63
|
+
end
|
|
64
|
+
|
|
58
65
|
super(template, instrumented)
|
|
59
66
|
end
|
|
60
67
|
|
data/lib/floatable/rails.rb
CHANGED
|
@@ -46,6 +46,28 @@ module Floatable
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
class << self
|
|
49
|
+
def log_error(message)
|
|
50
|
+
::Rails.logger&.error(message)
|
|
51
|
+
rescue
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def log_debug(message)
|
|
56
|
+
::Rails.logger&.debug(message)
|
|
57
|
+
rescue
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def log_backtrace(error, limit: 30)
|
|
62
|
+
return unless error&.backtrace
|
|
63
|
+
|
|
64
|
+
error.backtrace.first(limit).each do |line|
|
|
65
|
+
log_error(" #{line}")
|
|
66
|
+
end
|
|
67
|
+
rescue
|
|
68
|
+
nil
|
|
69
|
+
end
|
|
70
|
+
|
|
49
71
|
def config
|
|
50
72
|
@config ||= Configuration.new
|
|
51
73
|
end
|
|
@@ -61,16 +83,22 @@ module Floatable
|
|
|
61
83
|
|
|
62
84
|
!!predicate.call(request)
|
|
63
85
|
rescue => e
|
|
64
|
-
|
|
86
|
+
log_error("[Floatable::Rails] enabled_for? failed: #{e.class} - #{e.message}")
|
|
65
87
|
false
|
|
66
88
|
end
|
|
67
89
|
|
|
68
90
|
def instrumentation_enabled?
|
|
69
91
|
config.instrumentation_enabled != false
|
|
92
|
+
rescue => e
|
|
93
|
+
log_error("[Floatable::Rails] instrumentation_enabled? failed: #{e.class} - #{e.message}")
|
|
94
|
+
false
|
|
70
95
|
end
|
|
71
96
|
|
|
72
97
|
def script_src
|
|
73
98
|
config.script_src
|
|
99
|
+
rescue => e
|
|
100
|
+
log_error("[Floatable::Rails] script_src failed: #{e.class} - #{e.message}")
|
|
101
|
+
nil
|
|
74
102
|
end
|
|
75
103
|
|
|
76
104
|
def script_data
|
|
@@ -81,10 +109,16 @@ module Floatable
|
|
|
81
109
|
branch: config.script_branch,
|
|
82
110
|
agent_tools_mode: config.script_agent_tools_mode
|
|
83
111
|
}.reject { |_key, value| value.nil? || (value.respond_to?(:empty?) && value.empty?) }
|
|
112
|
+
rescue => e
|
|
113
|
+
log_error("[Floatable::Rails] script_data failed: #{e.class} - #{e.message}")
|
|
114
|
+
{}
|
|
84
115
|
end
|
|
85
116
|
|
|
86
117
|
def script_data_attributes
|
|
87
118
|
script_data.transform_keys { |key| :"floatable_#{key}" }
|
|
119
|
+
rescue => e
|
|
120
|
+
log_error("[Floatable::Rails] script_data_attributes failed: #{e.class} - #{e.message}")
|
|
121
|
+
{}
|
|
88
122
|
end
|
|
89
123
|
|
|
90
124
|
def script_data_attributes_html
|
|
@@ -94,6 +128,9 @@ module Floatable
|
|
|
94
128
|
data.map do |key, value|
|
|
95
129
|
%( data-floatable-#{key.to_s.tr("_", "-")}="#{ERB::Util.html_escape(value.to_s)}")
|
|
96
130
|
end.join
|
|
131
|
+
rescue => e
|
|
132
|
+
log_error("[Floatable::Rails] script_data_attributes_html failed: #{e.class} - #{e.message}")
|
|
133
|
+
""
|
|
97
134
|
end
|
|
98
135
|
end
|
|
99
136
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: floatable-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tobias Almstrand
|
|
@@ -41,7 +41,6 @@ files:
|
|
|
41
41
|
- lib/floatable/rails/helpers.rb
|
|
42
42
|
- lib/floatable/rails/response_middleware.rb
|
|
43
43
|
- lib/floatable/rails/slim_view_instrumentation.rb
|
|
44
|
-
- lib/floatable/rails/tag_instrumentation.rb
|
|
45
44
|
- lib/floatable/rails/version.rb
|
|
46
45
|
homepage: https://floatable.dev
|
|
47
46
|
licenses:
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
require "pathname"
|
|
2
|
-
require "base64"
|
|
3
|
-
|
|
4
|
-
module Floatable
|
|
5
|
-
module Rails
|
|
6
|
-
module TagInstrumentation
|
|
7
|
-
def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
|
|
8
|
-
if floatable_enabled_for_view?
|
|
9
|
-
options = ensure_options_hash(content_or_options_with_block, options)
|
|
10
|
-
file, line = floatable_callsite
|
|
11
|
-
|
|
12
|
-
if file
|
|
13
|
-
options[:"data-floatable-path"] ||= floatable_relative_path(file)
|
|
14
|
-
if line
|
|
15
|
-
encoded = Base64.urlsafe_encode64(line.to_s, padding: false)
|
|
16
|
-
options[:"data-floatable-id"] ||= "#{options[:"data-floatable-path"]}:#{encoded}"
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
# Re-shape args for super if we mutated them
|
|
21
|
-
if content_or_options_with_block.is_a?(Hash) && options
|
|
22
|
-
content_or_options_with_block = options
|
|
23
|
-
options = nil
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
super
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def tag(name = nil, options = nil, open = false, escape = true)
|
|
31
|
-
if floatable_enabled_for_view? && name
|
|
32
|
-
options = (options || {}).dup
|
|
33
|
-
file, line = floatable_callsite
|
|
34
|
-
|
|
35
|
-
if file
|
|
36
|
-
options[:"data-floatable-path"] ||= floatable_relative_path(file)
|
|
37
|
-
if line
|
|
38
|
-
encoded = Base64.urlsafe_encode64(line.to_s, padding: false)
|
|
39
|
-
options[:"data-floatable-id"] ||= "#{options[:"data-floatable-path"]}:#{encoded}"
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
return super(name, options, open, escape)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
super
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
private
|
|
50
|
-
|
|
51
|
-
def floatable_enabled_for_view?
|
|
52
|
-
return false unless respond_to?(:request)
|
|
53
|
-
|
|
54
|
-
::Floatable::Rails.instrumentation_enabled? && ::Floatable::Rails.enabled_for?(request)
|
|
55
|
-
rescue => e
|
|
56
|
-
::Rails.logger.error("[Floatable::Rails::TagInstrumentation] enabled? failed: #{e.class} - #{e.message}")
|
|
57
|
-
false
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def ensure_options_hash(content_or_options_with_block, options)
|
|
61
|
-
if content_or_options_with_block.is_a?(Hash) && options.nil?
|
|
62
|
-
content_or_options_with_block
|
|
63
|
-
else
|
|
64
|
-
options || {}
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Callsite detection
|
|
69
|
-
#
|
|
70
|
-
# We want the *template* or app file (e.g. app/views/posts/index.html.erb),
|
|
71
|
-
# not ActionView's gem helpers.
|
|
72
|
-
#
|
|
73
|
-
# Strategy:
|
|
74
|
-
# - Walk the call stack.
|
|
75
|
-
# - Pick the first frame whose path starts with Rails.root
|
|
76
|
-
# (so we ignore anything under ~/.rbenv, /gems/, etc).
|
|
77
|
-
# - Prefer files under app/views, but accept any app file as fallback.
|
|
78
|
-
#
|
|
79
|
-
def floatable_callsite
|
|
80
|
-
app_root = ::Rails.root.to_s
|
|
81
|
-
return [ nil, nil ] if app_root.blank?
|
|
82
|
-
|
|
83
|
-
locations = caller_locations(2, 200) || []
|
|
84
|
-
|
|
85
|
-
template_path = floatable_current_template_path
|
|
86
|
-
if template_path
|
|
87
|
-
loc = locations.find { |loc_item| loc_item.path.to_s == template_path }
|
|
88
|
-
return [ template_path, loc.lineno ] if loc
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# First pass: prefer app/views templates, excluding layouts when possible.
|
|
92
|
-
view_locs = locations.select do |loc|
|
|
93
|
-
path = loc.path.to_s
|
|
94
|
-
path.start_with?(app_root) && path.include?("/app/views/") && path.end_with?(".erb")
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
unless view_locs.empty?
|
|
98
|
-
non_layout = view_locs.reverse.find { |loc| !loc.path.to_s.include?("/app/views/layouts/") }
|
|
99
|
-
chosen = non_layout || view_locs.last
|
|
100
|
-
return [ chosen.path, chosen.lineno ]
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# Second pass: any file under Rails.root (app/models, app/controllers, etc)
|
|
104
|
-
app_loc = locations.find do |loc|
|
|
105
|
-
path = loc.path.to_s
|
|
106
|
-
path.start_with?(app_root)
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
if app_loc
|
|
110
|
-
return [ app_loc.path, app_loc.lineno ]
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# If nothing inside the app, we give up and return nil
|
|
114
|
-
[ nil, nil ]
|
|
115
|
-
rescue => e
|
|
116
|
-
::Rails.logger.debug("[Floatable::Rails::TagInstrumentation] floatable_callsite failed: #{e.class} - #{e.message}")
|
|
117
|
-
[ nil, nil ]
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def floatable_current_template_path
|
|
121
|
-
return nil unless respond_to?(:lookup_context)
|
|
122
|
-
|
|
123
|
-
current_template = floatable_current_template_identifier
|
|
124
|
-
return current_template if current_template.present?
|
|
125
|
-
|
|
126
|
-
vpath = floatable_virtual_path
|
|
127
|
-
return nil if vpath.empty?
|
|
128
|
-
|
|
129
|
-
templates = lookup_context.find_all(vpath, [], true)
|
|
130
|
-
template = templates.first || lookup_context.find_all(vpath, [], false).first
|
|
131
|
-
identifier = template&.identifier.to_s
|
|
132
|
-
identifier.presence
|
|
133
|
-
rescue
|
|
134
|
-
nil
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def floatable_current_template_identifier
|
|
138
|
-
return nil unless instance_variable_defined?(:@current_template)
|
|
139
|
-
|
|
140
|
-
template = instance_variable_get(:@current_template)
|
|
141
|
-
identifier = template&.identifier.to_s
|
|
142
|
-
identifier.presence
|
|
143
|
-
rescue
|
|
144
|
-
nil
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def floatable_virtual_path
|
|
148
|
-
vpath = nil
|
|
149
|
-
vpath = virtual_path.to_s if respond_to?(:virtual_path)
|
|
150
|
-
|
|
151
|
-
if vpath.to_s.empty?
|
|
152
|
-
vpath = instance_variable_get(:@virtual_path).to_s if instance_variable_defined?(:@virtual_path)
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
if vpath.to_s.empty?
|
|
156
|
-
vpath = instance_variable_get(:@_virtual_path).to_s if instance_variable_defined?(:@_virtual_path)
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
vpath.to_s
|
|
160
|
-
rescue
|
|
161
|
-
""
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def floatable_relative_path(file)
|
|
165
|
-
return nil if file.blank?
|
|
166
|
-
|
|
167
|
-
app_root = ::Rails.root.to_s
|
|
168
|
-
return file unless app_root.present?
|
|
169
|
-
|
|
170
|
-
# Only record paths inside the app root; ignore gems/rbenv/etc.
|
|
171
|
-
unless file.start_with?(app_root)
|
|
172
|
-
return nil
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
Pathname.new(file).relative_path_from(Pathname.new(app_root)).to_s
|
|
176
|
-
rescue
|
|
177
|
-
file
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
end
|
|
181
|
-
end
|