mirror of https://github.com/docusealco/docuseal
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
290 lines
9.2 KiB
290 lines
9.2 KiB
# frozen_string_literal: true
|
|
|
|
require 'active_support/core_ext/string/inflections'
|
|
require 'active_support/core_ext/module/delegation'
|
|
require 'set'
|
|
require 'uniform_notifier'
|
|
require 'bullet/ext/object'
|
|
require 'bullet/ext/string'
|
|
require 'bullet/dependency'
|
|
require 'bullet/stack_trace_filter'
|
|
|
|
module Bullet
|
|
extend Dependency
|
|
|
|
autoload :ActiveRecord, "bullet/#{active_record_version}" if active_record?
|
|
autoload :Mongoid, "bullet/#{mongoid_version}" if mongoid?
|
|
autoload :Rack, 'bullet/rack'
|
|
autoload :ActiveJob, 'bullet/active_job'
|
|
autoload :Notification, 'bullet/notification'
|
|
autoload :Detector, 'bullet/detector'
|
|
autoload :Registry, 'bullet/registry'
|
|
autoload :NotificationCollector, 'bullet/notification_collector'
|
|
|
|
if defined?(Rails::Railtie)
|
|
class BulletRailtie < Rails::Railtie
|
|
initializer 'bullet.add_middleware', after: :load_config_initializers do |app|
|
|
if defined?(ActionDispatch::ContentSecurityPolicy::Middleware) && Rails.application.config.content_security_policy && !app.config.api_only
|
|
app.middleware.insert_before ActionDispatch::ContentSecurityPolicy::Middleware, Bullet::Rack
|
|
else
|
|
app.middleware.use Bullet::Rack
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
class << self
|
|
attr_writer :n_plus_one_query_enable,
|
|
:unused_eager_loading_enable,
|
|
:counter_cache_enable,
|
|
:stacktrace_includes,
|
|
:stacktrace_excludes,
|
|
:skip_html_injection
|
|
attr_reader :safelist
|
|
attr_accessor :add_footer,
|
|
:orm_patches_applied,
|
|
:skip_http_headers,
|
|
:always_append_html_body,
|
|
:skip_user_in_notification
|
|
|
|
available_notifiers =
|
|
UniformNotifier::AVAILABLE_NOTIFIERS.select { |notifier| notifier != :raise }
|
|
.map { |notifier| "#{notifier}=" }
|
|
available_notifiers_options = { to: UniformNotifier }
|
|
delegate(*available_notifiers, **available_notifiers_options)
|
|
|
|
def raise=(should_raise)
|
|
UniformNotifier.raise = (should_raise ? Notification::UnoptimizedQueryError : false)
|
|
end
|
|
|
|
DETECTORS = [
|
|
Bullet::Detector::NPlusOneQuery,
|
|
Bullet::Detector::UnusedEagerLoading,
|
|
Bullet::Detector::CounterCache
|
|
].freeze
|
|
|
|
def enable=(enable)
|
|
@enable = enable
|
|
|
|
if enable?
|
|
reset_safelist
|
|
unless orm_patches_applied
|
|
self.orm_patches_applied = true
|
|
Bullet::Mongoid.enable if mongoid?
|
|
Bullet::ActiveRecord.enable if active_record?
|
|
end
|
|
end
|
|
end
|
|
|
|
alias enabled= enable=
|
|
|
|
def enable?
|
|
!!@enable
|
|
end
|
|
|
|
alias enabled? enable?
|
|
|
|
# Rails.root might be nil if `railties` is a dependency on a project that does not use Rails
|
|
def app_root
|
|
@app_root ||= (defined?(::Rails.root) && !::Rails.root.nil? ? Rails.root.to_s : Dir.pwd).to_s
|
|
end
|
|
|
|
def n_plus_one_query_enable?
|
|
enable? && (@n_plus_one_query_enable.nil? ? true : @n_plus_one_query_enable)
|
|
end
|
|
|
|
def unused_eager_loading_enable?
|
|
enable? && (@unused_eager_loading_enable.nil? ? true : @unused_eager_loading_enable)
|
|
end
|
|
|
|
def counter_cache_enable?
|
|
enable? && (@counter_cache_enable.nil? ? true : @counter_cache_enable)
|
|
end
|
|
|
|
def stacktrace_includes
|
|
@stacktrace_includes ||= []
|
|
end
|
|
|
|
def stacktrace_excludes
|
|
@stacktrace_excludes ||= []
|
|
end
|
|
|
|
def add_safelist(options)
|
|
reset_safelist
|
|
@safelist[options[:type]][options[:class_name]] ||= []
|
|
@safelist[options[:type]][options[:class_name]] << options[:association].to_sym
|
|
end
|
|
|
|
def delete_safelist(options)
|
|
reset_safelist
|
|
@safelist[options[:type]][options[:class_name]] ||= []
|
|
@safelist[options[:type]][options[:class_name]].delete(options[:association].to_sym)
|
|
@safelist[options[:type]].delete_if { |_key, val| val.empty? }
|
|
end
|
|
|
|
def get_safelist_associations(type, class_name)
|
|
Array.wrap(@safelist[type][class_name])
|
|
end
|
|
|
|
def reset_safelist
|
|
@safelist ||= { n_plus_one_query: {}, unused_eager_loading: {}, counter_cache: {} }
|
|
end
|
|
|
|
def clear_safelist
|
|
@safelist = nil
|
|
end
|
|
|
|
def bullet_logger=(active)
|
|
if active
|
|
require 'fileutils'
|
|
FileUtils.mkdir_p(app_root + '/log')
|
|
bullet_log_file = File.open("#{app_root}/log/bullet.log", 'a+')
|
|
bullet_log_file.sync = true
|
|
UniformNotifier.customized_logger = bullet_log_file
|
|
end
|
|
end
|
|
|
|
def debug(title, message)
|
|
puts "[Bullet][#{title}] #{message}" if ENV['BULLET_DEBUG'] == 'true'
|
|
end
|
|
|
|
def start_request
|
|
Thread.current.thread_variable_set(:bullet_start, true)
|
|
Thread.current.thread_variable_set(:bullet_notification_collector, Bullet::NotificationCollector.new)
|
|
|
|
Thread.current.thread_variable_set(:bullet_object_associations, Bullet::Registry::Base.new)
|
|
Thread.current.thread_variable_set(:bullet_call_object_associations, Bullet::Registry::Base.new)
|
|
Thread.current.thread_variable_set(:bullet_possible_objects, Bullet::Registry::Object.new)
|
|
Thread.current.thread_variable_set(:bullet_impossible_objects, Bullet::Registry::Object.new)
|
|
Thread.current.thread_variable_set(:bullet_inversed_objects, Bullet::Registry::Base.new)
|
|
Thread.current.thread_variable_set(:bullet_eager_loadings, Bullet::Registry::Association.new)
|
|
Thread.current.thread_variable_set(:bullet_call_stacks, Bullet::Registry::CallStack.new)
|
|
|
|
unless Thread.current.thread_variable_get(:bullet_counter_possible_objects)
|
|
Thread.current.thread_variable_set(:bullet_counter_possible_objects, Bullet::Registry::Object.new)
|
|
end
|
|
|
|
unless Thread.current.thread_variable_get(:bullet_counter_impossible_objects)
|
|
Thread.current.thread_variable_set(:bullet_counter_impossible_objects, Bullet::Registry::Object.new)
|
|
end
|
|
end
|
|
|
|
def end_request
|
|
Thread.current.thread_variable_set(:bullet_start, nil)
|
|
Thread.current.thread_variable_set(:bullet_notification_collector, nil)
|
|
|
|
Thread.current.thread_variable_set(:bullet_object_associations, nil)
|
|
Thread.current.thread_variable_set(:bullet_call_object_associations, nil)
|
|
Thread.current.thread_variable_set(:bullet_possible_objects, nil)
|
|
Thread.current.thread_variable_set(:bullet_impossible_objects, nil)
|
|
Thread.current.thread_variable_set(:bullet_inversed_objects, nil)
|
|
Thread.current.thread_variable_set(:bullet_eager_loadings, nil)
|
|
|
|
Thread.current.thread_variable_set(:bullet_counter_possible_objects, nil)
|
|
Thread.current.thread_variable_set(:bullet_counter_impossible_objects, nil)
|
|
end
|
|
|
|
def start?
|
|
enable? && Thread.current.thread_variable_get(:bullet_start)
|
|
end
|
|
|
|
def notification_collector
|
|
Thread.current.thread_variable_get(:bullet_notification_collector)
|
|
end
|
|
|
|
def notification?
|
|
return unless start?
|
|
|
|
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
|
notification_collector.notifications_present?
|
|
end
|
|
|
|
def gather_inline_notifications
|
|
responses = []
|
|
for_each_active_notifier_with_notification { |notification| responses << notification.notify_inline }
|
|
responses.join("\n")
|
|
end
|
|
|
|
def perform_out_of_channel_notifications(env = {})
|
|
request_uri = build_request_uri(env)
|
|
for_each_active_notifier_with_notification do |notification|
|
|
notification.url = request_uri
|
|
notification.notify_out_of_channel
|
|
end
|
|
end
|
|
|
|
def footer_info
|
|
info = []
|
|
notification_collector.collection.each { |notification| info << notification.short_notice }
|
|
info
|
|
end
|
|
|
|
def text_notifications
|
|
info = []
|
|
notification_collector.collection.each do |notification|
|
|
info << notification.notification_data.values.compact.join("\n")
|
|
end
|
|
info
|
|
end
|
|
|
|
def warnings
|
|
notification_collector.collection.each_with_object({}) do |notification, warnings|
|
|
warning_type = notification.class.to_s.split(':').last.tableize
|
|
warnings[warning_type] ||= []
|
|
warnings[warning_type] << notification
|
|
end
|
|
end
|
|
|
|
def profile
|
|
return_value = nil
|
|
|
|
if Bullet.enable?
|
|
begin
|
|
Bullet.start_request
|
|
|
|
return_value = yield
|
|
|
|
Bullet.perform_out_of_channel_notifications if Bullet.notification?
|
|
ensure
|
|
Bullet.end_request
|
|
end
|
|
else
|
|
return_value = yield
|
|
end
|
|
|
|
return_value
|
|
end
|
|
|
|
def console_enabled?
|
|
UniformNotifier.active_notifiers.include?(UniformNotifier::JavascriptConsole)
|
|
end
|
|
|
|
def inject_into_page?
|
|
return false if defined?(@skip_html_injection) && @skip_html_injection
|
|
|
|
console_enabled? || add_footer
|
|
end
|
|
|
|
private
|
|
|
|
def for_each_active_notifier_with_notification
|
|
UniformNotifier.active_notifiers.each do |notifier|
|
|
notification_collector.collection.each do |notification|
|
|
notification.notifier = notifier
|
|
yield notification
|
|
end
|
|
end
|
|
end
|
|
|
|
def build_request_uri(env)
|
|
return "#{env['REQUEST_METHOD']} #{env['REQUEST_URI']}" if env['REQUEST_URI']
|
|
|
|
if env['QUERY_STRING'].present?
|
|
"#{env['REQUEST_METHOD']} #{env['PATH_INFO']}?#{env['QUERY_STRING']}"
|
|
else
|
|
"#{env['REQUEST_METHOD']} #{env['PATH_INFO']}"
|
|
end
|
|
end
|
|
end
|
|
end
|