Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 9 additions & 74 deletions lib/graphql/dashboard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ module Graphql
class Dashboard < Rails::Engine
engine_name "graphql_dashboard"
isolate_namespace(Graphql::Dashboard)

autoload :ApplicationController, "graphql/dashboard/application_controller"
autoload :LandingsController, "graphql/dashboard/landings_controller"
autoload :StaticsController, "graphql/dashboard/statics_controller"
autoload :DetailedTraces, "graphql/dashboard/detailed_traces"
autoload :Subscriptions, "graphql/dashboard/subscriptions"
autoload :OperationStore, "graphql/dashboard/operation_store"
autoload :Limiters, "graphql/dashboard/limiters"

routes do
root "landings#show"
resources :statics, only: :show, constraints: { id: /[0-9A-Za-z\-.]+/ }
Expand Down Expand Up @@ -77,85 +86,11 @@ class Dashboard < Rails::Engine
resources :subscriptions, only: [:show], constraints: { id: /[a-zA-Z0-9\-]+/ }
post "/subscriptions/clear_all", to: "subscriptions#clear_all", as: :clear_all
end

ApplicationController.include(Dashboard.routes.url_helpers)
end

class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
prepend_view_path(File.join(__FILE__, "../dashboard/views"))

content_security_policy do |policy|
policy.default_src(:self) if policy.default_src(*policy.default_src).blank?
policy.connect_src(:self) if policy.connect_src(*policy.connect_src).blank?
policy.base_uri(:none) if policy.base_uri(*policy.base_uri).blank?
policy.font_src(:self) if policy.font_src(*policy.font_src).blank?
policy.img_src(:self, :data) if policy.img_src(*policy.img_src).blank?
policy.object_src(:none) if policy.object_src(*policy.object_src).blank?
policy.script_src(:self) if policy.script_src(*policy.script_src).blank?
policy.style_src(:self) if policy.style_src(*policy.style_src).blank?
policy.form_action(:self) if policy.form_action(*policy.form_action).blank?
policy.frame_ancestors(:none) if policy.frame_ancestors(*policy.frame_ancestors).blank?
end

def schema_class
@schema_class ||= begin
schema_param = request.query_parameters["schema"] || params[:schema]
case schema_param
when Class
schema_param
when String
schema_param.constantize
else
raise "Missing `params[:schema]`, please provide a class or string to `mount GraphQL::Dashboard, schema: ...`"
end
end
end
helper_method :schema_class
end

class LandingsController < ApplicationController
def show
end
end

class StaticsController < ApplicationController
skip_forgery_protection
# Use an explicit list of files to avoid any chance of reading other files from disk
STATICS = {}

[
"icon.png",
"header-icon.png",
"charts.min.css",
"dashboard.css",
"dashboard.js",
"bootstrap-5.3.3.min.css",
"bootstrap-5.3.3.min.js",
].each do |static_file|
STATICS[static_file] = File.expand_path("../dashboard/statics/#{static_file}", __FILE__)
end

def show
expires_in 1.year, public: true
if (filepath = STATICS[params[:id]])
render file: filepath
else
head :not_found
end
end
end
end
end

require 'graphql/dashboard/detailed_traces'
require 'graphql/dashboard/limiters'
require 'graphql/dashboard/operation_store'
require 'graphql/dashboard/subscriptions'

# Rails expects the engine to be called `Graphql::Dashboard`,
# but `GraphQL::Dashboard` is consistent with this gem's naming.
# So define both constants to refer to the same class.
GraphQL::Dashboard = Graphql::Dashboard

ActiveSupport.run_load_hooks(:graphql_dashboard_application_controller, GraphQL::Dashboard::ApplicationController)
41 changes: 41 additions & 0 deletions lib/graphql/dashboard/application_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true
require "action_controller"

module Graphql
class Dashboard < Rails::Engine
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
prepend_view_path(File.expand_path("../views", __FILE__))

content_security_policy do |policy|
policy.default_src(:self) if policy.default_src(*policy.default_src).blank?
policy.connect_src(:self) if policy.connect_src(*policy.connect_src).blank?
policy.base_uri(:none) if policy.base_uri(*policy.base_uri).blank?
policy.font_src(:self) if policy.font_src(*policy.font_src).blank?
policy.img_src(:self, :data) if policy.img_src(*policy.img_src).blank?
policy.object_src(:none) if policy.object_src(*policy.object_src).blank?
policy.script_src(:self) if policy.script_src(*policy.script_src).blank?
policy.style_src(:self) if policy.style_src(*policy.style_src).blank?
policy.form_action(:self) if policy.form_action(*policy.form_action).blank?
policy.frame_ancestors(:none) if policy.frame_ancestors(*policy.frame_ancestors).blank?
end

def schema_class
@schema_class ||= begin
schema_param = request.query_parameters["schema"] || params[:schema]
case schema_param
when Class
schema_param
when String
schema_param.constantize
else
raise "Missing `params[:schema]`, please provide a class or string to `mount GraphQL::Dashboard, schema: ...`"
end
end
end
helper_method :schema_class
end
end
end

ActiveSupport.run_load_hooks(:graphql_dashboard_application_controller, GraphQL::Dashboard::ApplicationController)
9 changes: 9 additions & 0 deletions lib/graphql/dashboard/landings_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true
module Graphql
class Dashboard < Rails::Engine
class LandingsController < ApplicationController
def show
end
end
end
end
31 changes: 31 additions & 0 deletions lib/graphql/dashboard/statics_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true
module Graphql
class Dashboard < Rails::Engine
class StaticsController < ApplicationController
skip_forgery_protection
# Use an explicit list of files to avoid any chance of reading other files from disk
STATICS = {}

[
"icon.png",
"header-icon.png",
"charts.min.css",
"dashboard.css",
"dashboard.js",
"bootstrap-5.3.3.min.css",
"bootstrap-5.3.3.min.js",
].each do |static_file|
STATICS[static_file] = File.expand_path("../statics/#{static_file}", __FILE__)
end

def show
expires_in 1.year, public: true
if (filepath = STATICS[params[:id]])
render file: filepath
else
head :not_found
end
end
end
end
end
3 changes: 2 additions & 1 deletion lib/graphql/dashboard/subscriptions.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# frozen_string_literal: true
require_relative "./installable"
module Graphql
class Dashboard < Rails::Engine
module Subscriptions
class BaseController < Graphql::Dashboard::ApplicationController
include Installable

def feature_installed?
schema_class.subscriptions.is_a?(GraphQL::Pro::Subscriptions)
defined?(GraphQL::Pro::Subscriptions) && schema_class.subscriptions.is_a?(GraphQL::Pro::Subscriptions)
end

INSTALLABLE_COMPONENT_HEADER_HTML = "GraphQL-Pro Subscriptions aren't installed on this schema yet.".html_safe
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
require "test_helper"

class DashboardLandingsControllerTest < ActionDispatch::IntegrationTest
def test_it_doesnt_load_autoloads_files
result = `BUNDLE_GEMFILE=#{ENV["BUNDLE_GEMFILE"]} ruby ./test_autoloads.rb`
assert_includes result, "No autoloaded constants were found during the boot process."
end

def test_it_shows_a_landing_page_with_local_static_asset_links
get graphql_dashboard.root_path
assert_includes response.body, "Welcome to the GraphQL-Ruby Dashboard"
Expand Down
47 changes: 47 additions & 0 deletions spec/dummy/test_autoloads.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

# Extracted and adapted from this talk from Ben Sheldon:
# `An ok compromise. Faster development by designing for the Rails Autoloader`
# Youtube video link: https://youtu.be/9-PWz9nbrT8?si=Lw7qsF2_VmBperId&t=1487

require_relative "./config/application"

autoloaded_constants = []

Rails.autoloaders.each do |loader|
loader.on_load do |cpath, _value, _abspath|
autoloaded_constants << [cpath, caller]
end
end

Rails.application.initialize!

autoloaded_constants.each do |x|
x[1] = Rails.backtrace_cleaner.clean(x[1]).first
end

allow_listed_constants = [
'ActionText::ContentHelper',
'ActionText::TagHelper',
]

puts
if !autoloaded_constants.reject! { _1.first.in?(allow_listed_constants) }.nil?
puts
puts "ERROR: Autoloaded constants were referenced during during boot."
puts
puts "These files/constants were autoloaded during the boot process, which will result in" \
"inconsistent behavior and will slow down and may break development mode. " \
"Remove references to these constants from code loaded at boot. "
puts
puts
w = autoloaded_constants.map { _1.first.length }.max
autoloaded_constants.each do |name, location|
puts "`#{name.ljust(w)}` referenced by #{location}"
end

exit 1
else
puts "SUCCESS! No autoloaded constants were found during the boot process."
exit 0
end
Loading