bullet 5.9.0 → 7.0.4
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 +5 -5
- data/.github/workflows/main.yml +82 -0
- data/CHANGELOG.md +72 -0
- data/Gemfile.rails-4.0 +1 -1
- data/Gemfile.rails-4.1 +1 -1
- data/Gemfile.rails-4.2 +1 -1
- data/Gemfile.rails-5.0 +1 -1
- data/Gemfile.rails-5.1 +1 -1
- data/Gemfile.rails-5.2 +1 -1
- data/Gemfile.rails-6.0 +15 -0
- data/Gemfile.rails-6.1 +15 -0
- data/Gemfile.rails-7.0 +10 -0
- data/MIT-LICENSE +1 -1
- data/README.md +59 -33
- data/lib/bullet/active_job.rb +13 -0
- data/lib/bullet/active_record4.rb +9 -32
- data/lib/bullet/active_record41.rb +8 -27
- data/lib/bullet/active_record42.rb +9 -24
- data/lib/bullet/active_record5.rb +190 -179
- data/lib/bullet/active_record52.rb +184 -169
- data/lib/bullet/active_record60.rb +274 -0
- data/lib/bullet/active_record61.rb +274 -0
- data/lib/bullet/active_record70.rb +277 -0
- data/lib/bullet/bullet_xhr.js +64 -0
- data/lib/bullet/dependency.rb +60 -36
- data/lib/bullet/detector/association.rb +26 -20
- data/lib/bullet/detector/counter_cache.rb +15 -11
- data/lib/bullet/detector/n_plus_one_query.rb +24 -14
- data/lib/bullet/detector/unused_eager_loading.rb +8 -5
- data/lib/bullet/ext/object.rb +4 -2
- data/lib/bullet/mongoid4x.rb +3 -7
- data/lib/bullet/mongoid5x.rb +3 -7
- data/lib/bullet/mongoid6x.rb +3 -7
- data/lib/bullet/mongoid7x.rb +34 -23
- data/lib/bullet/notification/base.rb +14 -18
- data/lib/bullet/notification/n_plus_one_query.rb +2 -4
- data/lib/bullet/notification/unused_eager_loading.rb +2 -4
- data/lib/bullet/notification.rb +2 -1
- data/lib/bullet/rack.rb +55 -27
- data/lib/bullet/stack_trace_filter.rb +11 -19
- data/lib/bullet/version.rb +1 -1
- data/lib/bullet.rb +68 -42
- data/lib/generators/bullet/install_generator.rb +22 -23
- data/perf/benchmark.rb +11 -14
- data/spec/bullet/detector/counter_cache_spec.rb +6 -6
- data/spec/bullet/detector/n_plus_one_query_spec.rb +8 -4
- data/spec/bullet/detector/unused_eager_loading_spec.rb +25 -8
- data/spec/bullet/ext/object_spec.rb +10 -5
- data/spec/bullet/notification/base_spec.rb +5 -7
- data/spec/bullet/notification/n_plus_one_query_spec.rb +16 -3
- data/spec/bullet/notification/unused_eager_loading_spec.rb +5 -1
- data/spec/bullet/rack_spec.rb +161 -11
- data/spec/bullet/registry/association_spec.rb +2 -2
- data/spec/bullet/registry/base_spec.rb +1 -1
- data/spec/bullet_spec.rb +25 -44
- data/spec/integration/active_record/association_spec.rb +115 -144
- data/spec/integration/counter_cache_spec.rb +14 -34
- data/spec/integration/mongoid/association_spec.rb +19 -33
- data/spec/models/attachment.rb +5 -0
- data/spec/models/deal.rb +5 -0
- data/spec/models/post.rb +2 -0
- data/spec/models/role.rb +7 -0
- data/spec/models/submission.rb +1 -0
- data/spec/models/user.rb +2 -0
- data/spec/spec_helper.rb +4 -10
- data/spec/support/bullet_ext.rb +8 -9
- data/spec/support/mongo_seed.rb +3 -16
- data/spec/support/sqlite_seed.rb +38 -0
- data/test.sh +3 -0
- metadata +21 -8
- data/.travis.yml +0 -12
@@ -6,20 +6,23 @@ module Bullet
|
|
6
6
|
extend Dependency
|
7
7
|
extend StackTraceFilter
|
8
8
|
|
9
|
-
class <<self
|
10
|
-
# executed when object.
|
9
|
+
class << self
|
10
|
+
# executed when object.associations is called.
|
11
11
|
# first, it keeps this method call for object.association.
|
12
12
|
# then, it checks if this associations call is unpreload.
|
13
13
|
# if it is, keeps this unpreload associations and caller.
|
14
14
|
def call_association(object, associations)
|
15
15
|
return unless Bullet.start?
|
16
16
|
return unless Bullet.n_plus_one_query_enable?
|
17
|
-
return unless object.
|
17
|
+
return unless object.bullet_primary_key_value
|
18
18
|
return if inversed_objects.include?(object.bullet_key, associations)
|
19
19
|
|
20
20
|
add_call_object_associations(object, associations)
|
21
21
|
|
22
|
-
Bullet.debug(
|
22
|
+
Bullet.debug(
|
23
|
+
'Detector::NPlusOneQuery#call_association',
|
24
|
+
"object: #{object.bullet_key}, associations: #{associations}"
|
25
|
+
)
|
23
26
|
if !excluded_stacktrace_path? && conditions_met?(object, associations)
|
24
27
|
Bullet.debug('detect n + 1 query', "object: #{object.bullet_key}, associations: #{associations}")
|
25
28
|
create_notification caller_in_project, object.class.to_s, associations
|
@@ -30,17 +33,21 @@ module Bullet
|
|
30
33
|
return unless Bullet.start?
|
31
34
|
return unless Bullet.n_plus_one_query_enable?
|
32
35
|
|
33
|
-
objects = Array(object_or_objects)
|
34
|
-
return if objects.map(&:
|
36
|
+
objects = Array.wrap(object_or_objects)
|
37
|
+
return if objects.map(&:bullet_primary_key_value).compact.empty?
|
38
|
+
return if objects.all? { |obj| obj.class.name =~ /^HABTM_/ }
|
35
39
|
|
36
|
-
Bullet.debug(
|
40
|
+
Bullet.debug(
|
41
|
+
'Detector::NPlusOneQuery#add_possible_objects',
|
42
|
+
"objects: #{objects.map(&:bullet_key).join(', ')}"
|
43
|
+
)
|
37
44
|
objects.each { |object| possible_objects.add object.bullet_key }
|
38
45
|
end
|
39
46
|
|
40
47
|
def add_impossible_object(object)
|
41
48
|
return unless Bullet.start?
|
42
49
|
return unless Bullet.n_plus_one_query_enable?
|
43
|
-
return unless object.
|
50
|
+
return unless object.bullet_primary_key_value
|
44
51
|
|
45
52
|
Bullet.debug('Detector::NPlusOneQuery#add_impossible_object', "object: #{object.bullet_key}")
|
46
53
|
impossible_objects.add object.bullet_key
|
@@ -49,9 +56,12 @@ module Bullet
|
|
49
56
|
def add_inversed_object(object, association)
|
50
57
|
return unless Bullet.start?
|
51
58
|
return unless Bullet.n_plus_one_query_enable?
|
52
|
-
return unless object.
|
59
|
+
return unless object.bullet_primary_key_value
|
53
60
|
|
54
|
-
Bullet.debug(
|
61
|
+
Bullet.debug(
|
62
|
+
'Detector::NPlusOneQuery#add_inversed_object',
|
63
|
+
"object: #{object.bullet_key}, association: #{association}"
|
64
|
+
)
|
55
65
|
inversed_objects.add object.bullet_key, association
|
56
66
|
end
|
57
67
|
|
@@ -72,9 +82,9 @@ module Bullet
|
|
72
82
|
def association?(object, associations)
|
73
83
|
value = object_associations[object.bullet_key]
|
74
84
|
value&.each do |v|
|
75
|
-
|
76
|
-
|
77
|
-
|
85
|
+
# associations == v comparison order is important here because
|
86
|
+
# v variable might be a squeel node where :== method is redefined,
|
87
|
+
# so it does not compare values at all and return unexpected results
|
78
88
|
result = v.is_a?(Hash) ? v.key?(associations) : associations == v
|
79
89
|
return true if result
|
80
90
|
end
|
@@ -85,7 +95,7 @@ module Bullet
|
|
85
95
|
private
|
86
96
|
|
87
97
|
def create_notification(callers, klazz, associations)
|
88
|
-
notify_associations = Array(associations) - Bullet.
|
98
|
+
notify_associations = Array.wrap(associations) - Bullet.get_safelist_associations(:n_plus_one_query, klazz)
|
89
99
|
|
90
100
|
if notify_associations.present?
|
91
101
|
notice = Bullet::Notification::NPlusOneQuery.new(callers, klazz, notify_associations)
|
@@ -6,11 +6,11 @@ module Bullet
|
|
6
6
|
extend Dependency
|
7
7
|
extend StackTraceFilter
|
8
8
|
|
9
|
-
class <<self
|
9
|
+
class << self
|
10
10
|
# check if there are unused preload associations.
|
11
11
|
# get related_objects from eager_loadings associated with object and associations
|
12
12
|
# get call_object_association from associations of call_object_associations whose object is in related_objects
|
13
|
-
# if association not in call_object_association, then the object => association - call_object_association is ununsed preload
|
13
|
+
# if association not in call_object_association, then the object => association - call_object_association is ununsed preload associations
|
14
14
|
def check_unused_preload_associations
|
15
15
|
return unless Bullet.start?
|
16
16
|
return unless Bullet.unused_eager_loading_enable?
|
@@ -27,9 +27,12 @@ module Bullet
|
|
27
27
|
def add_eager_loadings(objects, associations)
|
28
28
|
return unless Bullet.start?
|
29
29
|
return unless Bullet.unused_eager_loading_enable?
|
30
|
-
return if objects.map(&:
|
30
|
+
return if objects.map(&:bullet_primary_key_value).compact.empty?
|
31
31
|
|
32
|
-
Bullet.debug(
|
32
|
+
Bullet.debug(
|
33
|
+
'Detector::UnusedEagerLoading#add_eager_loadings',
|
34
|
+
"objects: #{objects.map(&:bullet_key).join(', ')}, associations: #{associations}"
|
35
|
+
)
|
33
36
|
bullet_keys = objects.map(&:bullet_key)
|
34
37
|
|
35
38
|
to_add = []
|
@@ -62,7 +65,7 @@ module Bullet
|
|
62
65
|
private
|
63
66
|
|
64
67
|
def create_notification(callers, klazz, associations)
|
65
|
-
notify_associations = Array(associations) - Bullet.
|
68
|
+
notify_associations = Array.wrap(associations) - Bullet.get_safelist_associations(:unused_eager_loading, klazz)
|
66
69
|
|
67
70
|
if notify_associations.present?
|
68
71
|
notice = Bullet::Notification::UnusedEagerLoading.new(callers, klazz, notify_associations)
|
data/lib/bullet/ext/object.rb
CHANGED
@@ -2,10 +2,12 @@
|
|
2
2
|
|
3
3
|
class Object
|
4
4
|
def bullet_key
|
5
|
-
"#{self.class}:#{
|
5
|
+
"#{self.class}:#{bullet_primary_key_value}"
|
6
6
|
end
|
7
7
|
|
8
|
-
def
|
8
|
+
def bullet_primary_key_value
|
9
|
+
return if respond_to?(:persisted?) && !persisted?
|
10
|
+
|
9
11
|
if self.class.respond_to?(:primary_keys) && self.class.primary_keys
|
10
12
|
self.class.primary_keys.map { |primary_key| send primary_key }.join(',')
|
11
13
|
elsif self.class.respond_to?(:primary_key) && self.class.primary_key
|
data/lib/bullet/mongoid4x.rb
CHANGED
@@ -23,7 +23,7 @@ module Bullet
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def each(&block)
|
26
|
-
return to_enum unless
|
26
|
+
return to_enum unless block
|
27
27
|
|
28
28
|
records = []
|
29
29
|
origin_each { |record| records << record }
|
@@ -37,9 +37,7 @@ module Bullet
|
|
37
37
|
|
38
38
|
def eager_load(docs)
|
39
39
|
associations = criteria.inclusions.map(&:name)
|
40
|
-
docs.each
|
41
|
-
Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations)
|
42
|
-
end
|
40
|
+
docs.each { |doc| Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations) }
|
43
41
|
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)
|
44
42
|
origin_eager_load(docs)
|
45
43
|
end
|
@@ -50,9 +48,7 @@ module Bullet
|
|
50
48
|
|
51
49
|
def get_relation(name, metadata, object, reload = false)
|
52
50
|
result = origin_get_relation(name, metadata, object, reload)
|
53
|
-
if metadata.macro !~ /embed/
|
54
|
-
Bullet::Detector::NPlusOneQuery.call_association(self, name)
|
55
|
-
end
|
51
|
+
Bullet::Detector::NPlusOneQuery.call_association(self, name) if metadata.macro !~ /embed/
|
56
52
|
result
|
57
53
|
end
|
58
54
|
end
|
data/lib/bullet/mongoid5x.rb
CHANGED
@@ -23,7 +23,7 @@ module Bullet
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def each(&block)
|
26
|
-
return to_enum unless
|
26
|
+
return to_enum unless block
|
27
27
|
|
28
28
|
records = []
|
29
29
|
origin_each { |record| records << record }
|
@@ -37,9 +37,7 @@ module Bullet
|
|
37
37
|
|
38
38
|
def eager_load(docs)
|
39
39
|
associations = criteria.inclusions.map(&:name)
|
40
|
-
docs.each
|
41
|
-
Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations)
|
42
|
-
end
|
40
|
+
docs.each { |doc| Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations) }
|
43
41
|
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)
|
44
42
|
origin_eager_load(docs)
|
45
43
|
end
|
@@ -50,9 +48,7 @@ module Bullet
|
|
50
48
|
|
51
49
|
def get_relation(name, metadata, object, reload = false)
|
52
50
|
result = origin_get_relation(name, metadata, object, reload)
|
53
|
-
if metadata.macro !~ /embed/
|
54
|
-
Bullet::Detector::NPlusOneQuery.call_association(self, name)
|
55
|
-
end
|
51
|
+
Bullet::Detector::NPlusOneQuery.call_association(self, name) if metadata.macro !~ /embed/
|
56
52
|
result
|
57
53
|
end
|
58
54
|
end
|
data/lib/bullet/mongoid6x.rb
CHANGED
@@ -23,7 +23,7 @@ module Bullet
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def each(&block)
|
26
|
-
return to_enum unless
|
26
|
+
return to_enum unless block
|
27
27
|
|
28
28
|
records = []
|
29
29
|
origin_each { |record| records << record }
|
@@ -37,9 +37,7 @@ module Bullet
|
|
37
37
|
|
38
38
|
def eager_load(docs)
|
39
39
|
associations = criteria.inclusions.map(&:name)
|
40
|
-
docs.each
|
41
|
-
Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations)
|
42
|
-
end
|
40
|
+
docs.each { |doc| Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations) }
|
43
41
|
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)
|
44
42
|
origin_eager_load(docs)
|
45
43
|
end
|
@@ -50,9 +48,7 @@ module Bullet
|
|
50
48
|
|
51
49
|
def get_relation(name, metadata, object, reload = false)
|
52
50
|
result = origin_get_relation(name, metadata, object, reload)
|
53
|
-
if metadata.macro !~ /embed/
|
54
|
-
Bullet::Detector::NPlusOneQuery.call_association(self, name)
|
55
|
-
end
|
51
|
+
Bullet::Detector::NPlusOneQuery.call_association(self, name) if metadata.macro !~ /embed/
|
56
52
|
result
|
57
53
|
end
|
58
54
|
end
|
data/lib/bullet/mongoid7x.rb
CHANGED
@@ -4,42 +4,55 @@ module Bullet
|
|
4
4
|
module Mongoid
|
5
5
|
def self.enable
|
6
6
|
require 'mongoid'
|
7
|
+
require 'rubygems'
|
7
8
|
::Mongoid::Contextual::Mongo.class_eval do
|
8
9
|
alias_method :origin_first, :first
|
9
10
|
alias_method :origin_last, :last
|
10
11
|
alias_method :origin_each, :each
|
11
12
|
alias_method :origin_eager_load, :eager_load
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
result = origin_last(opts)
|
21
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
22
|
-
result
|
14
|
+
%i[first last].each do |context|
|
15
|
+
default = Gem::Version.new(::Mongoid::VERSION) >= Gem::Version.new('7.5') ? nil : {}
|
16
|
+
define_method(context) do |opts = default|
|
17
|
+
result = send(:"origin_#{context}", opts)
|
18
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
19
|
+
result
|
20
|
+
end
|
23
21
|
end
|
24
22
|
|
25
23
|
def each(&block)
|
26
24
|
return to_enum unless block_given?
|
27
25
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
26
|
+
first_document = nil
|
27
|
+
document_count = 0
|
28
|
+
|
29
|
+
origin_each do |document|
|
30
|
+
document_count += 1
|
31
|
+
|
32
|
+
if document_count == 1
|
33
|
+
first_document = document
|
34
|
+
elsif document_count == 2
|
35
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects([first_document, document])
|
36
|
+
yield(first_document)
|
37
|
+
first_document = nil
|
38
|
+
yield(document)
|
39
|
+
else
|
40
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(document)
|
41
|
+
yield(document)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
if document_count == 1
|
46
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(first_document)
|
47
|
+
yield(first_document)
|
34
48
|
end
|
35
|
-
|
49
|
+
|
50
|
+
self
|
36
51
|
end
|
37
52
|
|
38
53
|
def eager_load(docs)
|
39
54
|
associations = criteria.inclusions.map(&:name)
|
40
|
-
docs.each
|
41
|
-
Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations)
|
42
|
-
end
|
55
|
+
docs.each { |doc| Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations) }
|
43
56
|
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)
|
44
57
|
origin_eager_load(docs)
|
45
58
|
end
|
@@ -50,9 +63,7 @@ module Bullet
|
|
50
63
|
|
51
64
|
def get_relation(name, association, object, reload = false)
|
52
65
|
result = origin_get_relation(name, association, object, reload)
|
53
|
-
unless association.embedded?
|
54
|
-
Bullet::Detector::NPlusOneQuery.call_association(self, name)
|
55
|
-
end
|
66
|
+
Bullet::Detector::NPlusOneQuery.call_association(self, name) unless association.embedded?
|
56
67
|
result
|
57
68
|
end
|
58
69
|
end
|
@@ -8,7 +8,8 @@ module Bullet
|
|
8
8
|
|
9
9
|
def initialize(base_class, association_or_associations, path = nil)
|
10
10
|
@base_class = base_class
|
11
|
-
@associations =
|
11
|
+
@associations =
|
12
|
+
association_or_associations.is_a?(Array) ? association_or_associations : [association_or_associations]
|
12
13
|
@path = path
|
13
14
|
end
|
14
15
|
|
@@ -25,16 +26,16 @@ module Bullet
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def whoami
|
28
|
-
@user ||=
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
29
|
+
@user ||=
|
30
|
+
ENV['USER'].presence ||
|
31
|
+
(
|
32
|
+
begin
|
33
|
+
`whoami`.chomp
|
34
|
+
rescue StandardError
|
35
|
+
''
|
36
|
+
end
|
37
|
+
)
|
38
|
+
@user.present? ? "user: #{@user}" : ''
|
38
39
|
end
|
39
40
|
|
40
41
|
def body_with_caller
|
@@ -54,12 +55,7 @@ module Bullet
|
|
54
55
|
end
|
55
56
|
|
56
57
|
def notification_data
|
57
|
-
{
|
58
|
-
user: whoami,
|
59
|
-
url: url,
|
60
|
-
title: title,
|
61
|
-
body: body_with_caller
|
62
|
-
}
|
58
|
+
{ user: whoami, url: url, title: title, body: body_with_caller }
|
63
59
|
end
|
64
60
|
|
65
61
|
def eql?(other)
|
@@ -77,7 +73,7 @@ module Bullet
|
|
77
73
|
end
|
78
74
|
|
79
75
|
def associations_str
|
80
|
-
"
|
76
|
+
".includes(#{@associations.map { |a| a.to_s.to_sym }.inspect})"
|
81
77
|
end
|
82
78
|
end
|
83
79
|
end
|
@@ -10,7 +10,7 @@ module Bullet
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def body
|
13
|
-
"#{klazz_associations_str}\n Add to your
|
13
|
+
"#{klazz_associations_str}\n Add to your query: #{associations_str}"
|
14
14
|
end
|
15
15
|
|
16
16
|
def title
|
@@ -18,9 +18,7 @@ module Bullet
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def notification_data
|
21
|
-
super.merge(
|
22
|
-
backtrace: []
|
23
|
-
)
|
21
|
+
super.merge(backtrace: [])
|
24
22
|
end
|
25
23
|
|
26
24
|
protected
|
@@ -10,7 +10,7 @@ module Bullet
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def body
|
13
|
-
"#{klazz_associations_str}\n Remove from your
|
13
|
+
"#{klazz_associations_str}\n Remove from your query: #{associations_str}"
|
14
14
|
end
|
15
15
|
|
16
16
|
def title
|
@@ -18,9 +18,7 @@ module Bullet
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def notification_data
|
21
|
-
super.merge(
|
22
|
-
backtrace: []
|
23
|
-
)
|
21
|
+
super.merge(backtrace: [])
|
24
22
|
end
|
25
23
|
|
26
24
|
protected
|
data/lib/bullet/notification.rb
CHANGED
data/lib/bullet/rack.rb
CHANGED
@@ -15,13 +15,21 @@ module Bullet
|
|
15
15
|
status, headers, response = @app.call(env)
|
16
16
|
|
17
17
|
response_body = nil
|
18
|
+
|
18
19
|
if Bullet.notification?
|
19
|
-
if !file?(headers) && !sse?(headers) && !empty?(response) &&
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
if Bullet.inject_into_page? && !file?(headers) && !sse?(headers) && !empty?(response) && status == 200
|
21
|
+
if html_request?(headers, response)
|
22
|
+
response_body = response_body(response)
|
23
|
+
response_body = append_to_html_body(response_body, footer_note) if Bullet.add_footer
|
24
|
+
response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)
|
25
|
+
if Bullet.add_footer && !Bullet.skip_http_headers
|
26
|
+
response_body = append_to_html_body(response_body, xhr_script)
|
27
|
+
end
|
28
|
+
headers['Content-Length'] = response_body.bytesize.to_s
|
29
|
+
elsif !Bullet.skip_http_headers
|
30
|
+
set_header(headers, 'X-bullet-footer-text', Bullet.footer_info.uniq) if Bullet.add_footer
|
31
|
+
set_header(headers, 'X-bullet-console-text', Bullet.text_notifications) if Bullet.console_enabled?
|
32
|
+
end
|
25
33
|
end
|
26
34
|
Bullet.perform_out_of_channel_notifications(env)
|
27
35
|
end
|
@@ -32,20 +40,16 @@ module Bullet
|
|
32
40
|
|
33
41
|
# fix issue if response's body is a Proc
|
34
42
|
def empty?(response)
|
35
|
-
# response may be ["Not Found"], ["Move Permanently"], etc
|
36
|
-
if
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
response_body(response).empty?
|
41
|
-
else
|
42
|
-
body = response_body(response)
|
43
|
-
body.nil? || body.empty?
|
44
|
-
end
|
43
|
+
# response may be ["Not Found"], ["Move Permanently"], etc, but
|
44
|
+
# those should not happen if the status is 200
|
45
|
+
return true if !response.respond_to?(:body) && !response.respond_to?(:first)
|
46
|
+
body = response_body(response)
|
47
|
+
body.nil? || body.empty?
|
45
48
|
end
|
46
49
|
|
47
50
|
def append_to_html_body(response_body, content)
|
48
51
|
body = response_body.dup
|
52
|
+
content = content.html_safe if content.respond_to?(:html_safe)
|
49
53
|
if body.include?('</body>')
|
50
54
|
position = body.rindex('</body>')
|
51
55
|
body.insert(position, content)
|
@@ -55,7 +59,15 @@ module Bullet
|
|
55
59
|
end
|
56
60
|
|
57
61
|
def footer_note
|
58
|
-
"<
|
62
|
+
"<details #{details_attributes}><summary #{summary_attributes}>Bullet Warnings</summary><div #{footer_content_attributes}>#{Bullet.footer_info.uniq.join('<br>')}#{footer_console_message}</div></details>"
|
63
|
+
end
|
64
|
+
|
65
|
+
def set_header(headers, header_name, header_array)
|
66
|
+
# Many proxy applications such as Nginx and AWS ELB limit
|
67
|
+
# the size a header to 8KB, so truncate the list of reports to
|
68
|
+
# be under that limit
|
69
|
+
header_array.pop while header_array.to_json.length > 8 * 1024
|
70
|
+
headers[header_name] = header_array.to_json
|
59
71
|
end
|
60
72
|
|
61
73
|
def file?(headers)
|
@@ -67,31 +79,47 @@ module Bullet
|
|
67
79
|
end
|
68
80
|
|
69
81
|
def html_request?(headers, response)
|
70
|
-
headers['Content-Type']&.include?('text/html')
|
82
|
+
headers['Content-Type']&.include?('text/html')
|
71
83
|
end
|
72
84
|
|
73
85
|
def response_body(response)
|
74
86
|
if response.respond_to?(:body)
|
75
87
|
Array === response.body ? response.body.first : response.body
|
76
|
-
|
88
|
+
elsif response.respond_to?(:first)
|
77
89
|
response.first
|
78
90
|
end
|
79
91
|
end
|
80
92
|
|
81
93
|
private
|
82
94
|
|
83
|
-
def
|
95
|
+
def details_attributes
|
84
96
|
<<~EOF
|
85
|
-
data-is-bullet-footer
|
86
|
-
|
87
|
-
-moz-border-left-colors: none; -moz-border-image: none; border-width: 2pt 2pt 0px 0px;
|
88
|
-
padding: 3px 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
|
89
|
-
color: rgb(119, 119, 119); font-size: 16px; font-family: 'Arial', sans-serif; z-index:9999;"
|
97
|
+
id="bullet-footer" data-is-bullet-footer
|
98
|
+
style="cursor: pointer; position: fixed; left: 0px; bottom: 0px; z-index: 9999; background: #fdf2f2; color: #9b1c1c; font-size: 12px; border-radius: 0px 8px 0px 0px; border: 1px solid #9b1c1c;"
|
90
99
|
EOF
|
91
100
|
end
|
92
101
|
|
93
|
-
def
|
94
|
-
|
102
|
+
def summary_attributes
|
103
|
+
<<~EOF
|
104
|
+
style="font-weight: 600; padding: 2px 8px"
|
105
|
+
EOF
|
106
|
+
end
|
107
|
+
|
108
|
+
def footer_content_attributes
|
109
|
+
<<~EOF
|
110
|
+
style="padding: 8px; border-top: 1px solid #9b1c1c;"
|
111
|
+
EOF
|
112
|
+
end
|
113
|
+
|
114
|
+
def footer_console_message
|
115
|
+
if Bullet.console_enabled?
|
116
|
+
"<br/><span style='font-style: italic;'>See 'Uniform Notifier' in JS Console for Stacktrace</span>"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Make footer work for XHR requests by appending data to the footer
|
121
|
+
def xhr_script
|
122
|
+
"<script type='text/javascript'>#{File.read("#{__dir__}/bullet_xhr.js")}</script>"
|
95
123
|
end
|
96
124
|
end
|
97
125
|
end
|
@@ -1,17 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require "bundler"
|
2
3
|
|
3
4
|
module Bullet
|
4
5
|
module StackTraceFilter
|
5
6
|
VENDOR_PATH = '/vendor'
|
7
|
+
IS_RUBY_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
|
6
8
|
|
7
9
|
def caller_in_project
|
8
|
-
|
9
|
-
vendor_root = app_root + VENDOR_PATH
|
10
|
+
vendor_root = Bullet.app_root + VENDOR_PATH
|
10
11
|
bundler_path = Bundler.bundle_path.to_s
|
11
12
|
select_caller_locations do |location|
|
12
13
|
caller_path = location_as_path(location)
|
13
|
-
caller_path.include?(app_root) && !caller_path.include?(vendor_root) &&
|
14
|
-
Bullet.stacktrace_includes.any? { |include_pattern|
|
14
|
+
caller_path.include?(Bullet.app_root) && !caller_path.include?(vendor_root) &&
|
15
|
+
!caller_path.include?(bundler_path) || Bullet.stacktrace_includes.any? { |include_pattern|
|
16
|
+
pattern_matches?(location, include_pattern)
|
17
|
+
}
|
15
18
|
end
|
16
19
|
end
|
17
20
|
|
@@ -47,26 +50,15 @@ module Bullet
|
|
47
50
|
end
|
48
51
|
|
49
52
|
def location_as_path(location)
|
50
|
-
|
53
|
+
IS_RUBY_19 ? location : location.absolute_path.to_s
|
51
54
|
end
|
52
55
|
|
53
56
|
def select_caller_locations
|
54
|
-
if
|
55
|
-
caller.select
|
56
|
-
yield caller_path
|
57
|
-
end
|
57
|
+
if IS_RUBY_19
|
58
|
+
caller.select { |caller_path| yield caller_path }
|
58
59
|
else
|
59
|
-
caller_locations.select
|
60
|
-
yield location
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def ruby_19?
|
66
|
-
if @ruby_19.nil?
|
67
|
-
@ruby_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
|
60
|
+
caller_locations.select { |location| yield location }
|
68
61
|
end
|
69
|
-
@ruby_19
|
70
62
|
end
|
71
63
|
end
|
72
64
|
end
|
data/lib/bullet/version.rb
CHANGED