graphql 2.5.11 → 2.6.10
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/lib/generators/graphql/detailed_trace_generator.rb +77 -0
- data/lib/generators/graphql/field_extractor.rb +17 -1
- data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
- data/lib/generators/graphql/templates/schema.erb +2 -1
- data/lib/graphql/analysis/query_complexity.rb +31 -15
- data/lib/graphql/analysis.rb +20 -13
- data/lib/graphql/backtrace/table.rb +10 -1
- data/lib/graphql/current.rb +7 -1
- data/lib/graphql/dashboard/application_controller.rb +45 -0
- data/lib/graphql/dashboard/landings_controller.rb +9 -0
- data/lib/graphql/dashboard/statics_controller.rb +31 -0
- data/lib/graphql/dashboard/subscriptions.rb +2 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +1 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +2 -2
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +1 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +1 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +1 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +1 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +1 -1
- data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +7 -7
- data/lib/graphql/dashboard.rb +14 -73
- data/lib/graphql/dataloader/async_dataloader.rb +336 -60
- data/lib/graphql/dataloader/null_dataloader.rb +48 -10
- data/lib/graphql/dataloader/source.rb +32 -19
- data/lib/graphql/dataloader.rb +107 -39
- data/lib/graphql/date_encoding_error.rb +1 -1
- data/lib/graphql/execution/directive_checks.rb +2 -0
- data/lib/graphql/execution/field_resolve_step.rb +804 -0
- data/lib/graphql/execution/finalize.rb +229 -0
- data/lib/graphql/execution/input_values.rb +333 -0
- data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -0
- data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
- data/lib/graphql/execution/interpreter/resolve.rb +7 -13
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +13 -0
- data/lib/graphql/execution/interpreter/runtime.rb +60 -40
- data/lib/graphql/execution/interpreter.rb +17 -23
- data/lib/graphql/execution/lazy.rb +1 -1
- data/lib/graphql/execution/load_argument_step.rb +102 -0
- data/lib/graphql/execution/multiplex.rb +1 -1
- data/lib/graphql/execution/next.rb +98 -0
- data/lib/graphql/execution/prepare_object_step.rb +151 -0
- data/lib/graphql/execution/resolve_type_step.rb +27 -0
- data/lib/graphql/execution/runner.rb +457 -0
- data/lib/graphql/execution/selections_step.rb +95 -0
- data/lib/graphql/execution.rb +15 -4
- data/lib/graphql/execution_error.rb +17 -12
- data/lib/graphql/float_decoding_error.rb +13 -0
- data/lib/graphql/float_encoding_error.rb +28 -0
- data/lib/graphql/introspection/directive_type.rb +7 -3
- data/lib/graphql/introspection/dynamic_fields.rb +5 -1
- data/lib/graphql/introspection/entry_points.rb +11 -3
- data/lib/graphql/introspection/enum_value_type.rb +5 -5
- data/lib/graphql/introspection/field_type.rb +13 -5
- data/lib/graphql/introspection/input_value_type.rb +21 -13
- data/lib/graphql/introspection/schema_type.rb +6 -2
- data/lib/graphql/introspection/type_type.rb +64 -28
- data/lib/graphql/invalid_null_error.rb +11 -5
- data/lib/graphql/language/block_string.rb +6 -11
- data/lib/graphql/language/cache.rb +84 -14
- data/lib/graphql/language/document_from_schema_definition.rb +2 -1
- data/lib/graphql/language/lexer.rb +27 -17
- data/lib/graphql/language/nodes.rb +2 -1
- data/lib/graphql/language/parser.rb +1 -1
- data/lib/graphql/language.rb +32 -17
- data/lib/graphql/pagination/connection.rb +2 -0
- data/lib/graphql/pagination/connections.rb +30 -0
- data/lib/graphql/pagination/relation_connection.rb +10 -1
- data/lib/graphql/query/context.rb +10 -3
- data/lib/graphql/query/null_context.rb +9 -3
- data/lib/graphql/query/partial.rb +18 -3
- data/lib/graphql/query/variable_validation_error.rb +16 -1
- data/lib/graphql/query/variables.rb +1 -1
- data/lib/graphql/query.rb +12 -3
- data/lib/graphql/railtie.rb +2 -1
- data/lib/graphql/runtime_error.rb +6 -0
- data/lib/graphql/schema/argument.rb +13 -1
- data/lib/graphql/schema/build_from_definition.rb +28 -1
- data/lib/graphql/schema/directive/feature.rb +4 -0
- data/lib/graphql/schema/directive/transform.rb +20 -0
- data/lib/graphql/schema/directive.rb +48 -13
- data/lib/graphql/schema/field/connection_extension.rb +4 -37
- data/lib/graphql/schema/field/scope_extension.rb +18 -13
- data/lib/graphql/schema/field.rb +79 -48
- data/lib/graphql/schema/field_extension.rb +11 -8
- data/lib/graphql/schema/has_single_input_argument.rb +24 -13
- data/lib/graphql/schema/input_object.rb +6 -2
- data/lib/graphql/schema/interface.rb +26 -0
- data/lib/graphql/schema/introspection_system.rb +6 -21
- data/lib/graphql/schema/list.rb +5 -1
- data/lib/graphql/schema/member/base_dsl_methods.rb +1 -11
- data/lib/graphql/schema/member/has_arguments.rb +43 -14
- data/lib/graphql/schema/member/has_authorization.rb +35 -0
- data/lib/graphql/schema/member/has_dataloader.rb +37 -0
- data/lib/graphql/schema/member/has_fields.rb +86 -5
- data/lib/graphql/schema/member.rb +5 -0
- data/lib/graphql/schema/non_null.rb +1 -1
- data/lib/graphql/schema/object.rb +1 -0
- data/lib/graphql/schema/printer.rb +1 -1
- data/lib/graphql/schema/ractor_shareable.rb +1 -0
- data/lib/graphql/schema/relay_classic_mutation.rb +16 -2
- data/lib/graphql/schema/resolver.rb +76 -1
- data/lib/graphql/schema/subscription.rb +53 -10
- data/lib/graphql/schema/timeout.rb +2 -2
- data/lib/graphql/schema/validator/allow_blank_validator.rb +3 -3
- data/lib/graphql/schema/validator/allow_null_validator.rb +3 -3
- data/lib/graphql/schema/validator/exclusion_validator.rb +2 -2
- data/lib/graphql/schema/validator/format_validator.rb +3 -3
- data/lib/graphql/schema/validator/inclusion_validator.rb +2 -2
- data/lib/graphql/schema/validator/length_validator.rb +6 -6
- data/lib/graphql/schema/validator/numericality_validator.rb +19 -19
- data/lib/graphql/schema/validator/required_validator.rb +38 -5
- data/lib/graphql/schema/validator.rb +9 -0
- data/lib/graphql/schema/visibility/profile.rb +74 -53
- data/lib/graphql/schema/visibility/visit.rb +1 -1
- data/lib/graphql/schema/visibility.rb +32 -24
- data/lib/graphql/schema/wrapper.rb +11 -1
- data/lib/graphql/schema.rb +93 -27
- data/lib/graphql/static_validation/base_visitor.rb +90 -66
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +14 -4
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +351 -248
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
- data/lib/graphql/static_validation/validation_context.rb +1 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
- data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +40 -10
- data/lib/graphql/subscriptions/serialize.rb +10 -4
- data/lib/graphql/subscriptions.rb +36 -1
- data/lib/graphql/testing/helpers.rb +12 -9
- data/lib/graphql/testing/mock_action_cable.rb +111 -0
- data/lib/graphql/testing.rb +1 -0
- data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
- data/lib/graphql/tracing/detailed_trace.rb +70 -7
- data/lib/graphql/tracing/perfetto_trace.rb +213 -81
- data/lib/graphql/tracing/sentry_trace.rb +3 -1
- data/lib/graphql/tracing/trace.rb +6 -0
- data/lib/graphql/types/float.rb +18 -4
- data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
- data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
- data/lib/graphql/types/relay/has_node_field.rb +13 -8
- data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
- data/lib/graphql/types/relay/node_behaviors.rb +13 -2
- data/lib/graphql/unauthorized_error.rb +9 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +8 -2
- metadata +23 -59
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a6c191887acba06a4030f962822e9106e9d3aa9d5550c8cd25ea76f343927f1b
|
|
4
|
+
data.tar.gz: b984ad9a61a391b4b8a42ade3bb9c63dc79ab2b3ee987845d655dc7038df1255
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7deac13ce0bbc579609e28575a309caa46086196f1381cfc5fbd6a5770b2059e123cb047cc835c573ad76be1df264a3b0d52d8692dbf161ad0d0248f8cfed2a3
|
|
7
|
+
data.tar.gz: 1362c0867ad8bbbe086b1999c710f447de6ea16b53b8a05a83dc39cba6c7b7d8d5cccdc388a3dff2f42c1b5135ff59a4c78dd2d53badd34b28718a73aa331b92
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'rails/generators/active_record'
|
|
3
|
+
|
|
4
|
+
module Graphql
|
|
5
|
+
module Generators
|
|
6
|
+
class DetailedTraceGenerator < ::Rails::Generators::Base
|
|
7
|
+
include ::Rails::Generators::Migration
|
|
8
|
+
desc "Install GraphQL::Tracing::DetailedTrace for your schema"
|
|
9
|
+
source_root File.expand_path('../templates', __FILE__)
|
|
10
|
+
|
|
11
|
+
class_option :redis,
|
|
12
|
+
type: :boolean,
|
|
13
|
+
default: false,
|
|
14
|
+
desc: "Use Redis for persistence instead of ActiveRecord"
|
|
15
|
+
|
|
16
|
+
def self.next_migration_number(dirname)
|
|
17
|
+
::ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def install_detailed_traces
|
|
21
|
+
|
|
22
|
+
schema_glob = File.expand_path("app/graphql/*_schema.rb", destination_root)
|
|
23
|
+
schema_file = Dir.glob(schema_glob).first
|
|
24
|
+
if !schema_file
|
|
25
|
+
raise ArgumentError, "Failed to find schema definition file (checked: #{schema_glob.inspect})"
|
|
26
|
+
end
|
|
27
|
+
schema_file_match = /( *)class ([A-Za-z:]+) < GraphQL::Schema/.match(File.read(schema_file))
|
|
28
|
+
schema_name = schema_file_match[2]
|
|
29
|
+
indent = schema_file_match[1] + " "
|
|
30
|
+
|
|
31
|
+
if !options.redis?
|
|
32
|
+
migration_template 'create_graphql_detailed_traces.erb', 'db/migrate/create_graphql_detailed_traces.rb'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
log :add_detailed_traces_plugin
|
|
36
|
+
sentinel = /< GraphQL::Schema\s*\n/m
|
|
37
|
+
code = <<-RUBY
|
|
38
|
+
#{indent}use GraphQL::Tracing::DetailedTrace#{options.redis? ? ", redis: raise(\"TODO: pass a connection to a persistent redis database\")" : ""}, limit: 50
|
|
39
|
+
|
|
40
|
+
#{indent}# When this returns true, DetailedTrace will trace the query
|
|
41
|
+
#{indent}# Could use `query.context`, `query.selected_operation_name`, `query.query_string` here
|
|
42
|
+
#{indent}# Could call out to Flipper, etc
|
|
43
|
+
#{indent}def self.detailed_trace?(query)
|
|
44
|
+
#{indent} rand <= 0.000_1 # one in ten thousand
|
|
45
|
+
#{indent}end
|
|
46
|
+
|
|
47
|
+
RUBY
|
|
48
|
+
|
|
49
|
+
in_root do
|
|
50
|
+
inject_into_file schema_file, code, after: sentinel, force: false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
routes_source = File.read(File.expand_path("config/routes.rb", destination_root))
|
|
54
|
+
already_has_dashboard = routes_source.include?("GraphQL::Dashboard") ||
|
|
55
|
+
routes_source.include?("Schema.dashboard") ||
|
|
56
|
+
routes_source.include?("GraphQL::Pro::Routes::Lazy")
|
|
57
|
+
|
|
58
|
+
if (!already_has_dashboard || behavior == :revoke)
|
|
59
|
+
log :route, "GraphQL::Dashboard"
|
|
60
|
+
shell.mute do
|
|
61
|
+
route <<~RUBY
|
|
62
|
+
# TODO: add authorization to this route and expose it in production
|
|
63
|
+
# See https://graphql-ruby.org/pro/dashboard.html#authorizing-the-dashboard
|
|
64
|
+
if Rails.env.development?
|
|
65
|
+
mount GraphQL::Dashboard, at: "/graphql/dashboard", schema: #{schema_name.inspect}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
RUBY
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
gem("google-protobuf")
|
|
72
|
+
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -6,7 +6,23 @@ module Graphql
|
|
|
6
6
|
module FieldExtractor
|
|
7
7
|
def fields
|
|
8
8
|
columns = []
|
|
9
|
-
|
|
9
|
+
if (model_columns = klass&.columns)
|
|
10
|
+
filter = if defined?(ActiveSupport::ParameterFilter)
|
|
11
|
+
fp = if defined?(Rails) && Rails.application && (app_config = Rails.application.config.filter_parameters).present? && !app_config.empty?
|
|
12
|
+
app_config
|
|
13
|
+
elsif ActiveSupport.respond_to?(:filter_parameters)
|
|
14
|
+
ActiveSupport.filter_parameters
|
|
15
|
+
else
|
|
16
|
+
[]
|
|
17
|
+
end
|
|
18
|
+
ActiveSupport::ParameterFilter.new(fp, mask: nil)
|
|
19
|
+
else
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
columns += model_columns
|
|
23
|
+
.select { |c| filter ? filter.filter_param(c.name, c.name) : true }
|
|
24
|
+
.map { |c| generate_column_string(c) }
|
|
25
|
+
end
|
|
10
26
|
columns + custom_fields
|
|
11
27
|
end
|
|
12
28
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class CreateGraphqlDetailedTraces < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
+
def change
|
|
3
|
+
create_table :graphql_detailed_traces, force: true do |t|
|
|
4
|
+
t.bigint :begin_ms, null: false
|
|
5
|
+
t.float :duration_ms, null: false
|
|
6
|
+
t.binary :trace_data, null: false
|
|
7
|
+
t.string :operation_name, null: false
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -26,7 +26,8 @@ class <%= schema_name %> < GraphQL::Schema
|
|
|
26
26
|
raise(GraphQL::RequiredImplementationMissingError)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
# Limit the size of incoming queries:
|
|
29
|
+
# Limit the depth and size of incoming queries:
|
|
30
|
+
max_depth(15)
|
|
30
31
|
max_query_string_tokens(5000)
|
|
31
32
|
|
|
32
33
|
# Stop validating when it encounters this many errors:
|
|
@@ -9,6 +9,8 @@ module GraphQL
|
|
|
9
9
|
super
|
|
10
10
|
@skip_introspection_fields = !query.schema.max_complexity_count_introspection_fields
|
|
11
11
|
@complexities_on_type_by_query = {}
|
|
12
|
+
@intersect_cache = Hash.new { |h, k| h[k] = {}.compare_by_identity }.compare_by_identity
|
|
13
|
+
@possible_types_cache = {}.compare_by_identity
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
# Override this method to use the complexity result
|
|
@@ -27,14 +29,14 @@ module GraphQL
|
|
|
27
29
|
future_complexity
|
|
28
30
|
end
|
|
29
31
|
when nil
|
|
30
|
-
subject.logger.warn <<~
|
|
32
|
+
subject.logger.warn <<~MESSAGE
|
|
31
33
|
GraphQL-Ruby's complexity cost system is getting some "breaking fixes" in a future version. See the migration notes at https://graphql-ruby.org/api-doc/#{GraphQL::VERSION}/GraphQL/Schema.html#complexity_cost_calculation_mode_for-class_method
|
|
32
34
|
|
|
33
35
|
To opt into the future behavior, configure your schema (#{subject.schema.name ? subject.schema.name : subject.schema.ancestors}) with:
|
|
34
36
|
|
|
35
37
|
complexity_cost_calculation_mode(:future) # or `:legacy`, `:compare`
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
MESSAGE
|
|
38
40
|
max_possible_complexity(mode: :legacy)
|
|
39
41
|
else
|
|
40
42
|
raise ArgumentError, "Expected `:future`, `:legacy`, `:compare`, or `nil` from `#{query.schema}.complexity_cost_calculation_mode_for` but got: #{query.schema.complexity_cost_calculation_mode.inspect}"
|
|
@@ -159,8 +161,22 @@ module GraphQL
|
|
|
159
161
|
|
|
160
162
|
def types_intersect?(query, a, b)
|
|
161
163
|
return true if a == b
|
|
162
|
-
|
|
163
|
-
|
|
164
|
+
|
|
165
|
+
if a.object_id < b.object_id
|
|
166
|
+
first_cache = @intersect_cache[a]
|
|
167
|
+
second_key = b
|
|
168
|
+
else
|
|
169
|
+
first_cache = @intersect_cache[b]
|
|
170
|
+
second_key = a
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
if first_cache.key?(second_key)
|
|
174
|
+
first_cache[second_key]
|
|
175
|
+
else
|
|
176
|
+
a_types = @possible_types_cache[a] ||= query.types.possible_types(a).to_set
|
|
177
|
+
b_types = @possible_types_cache[b] ||= query.types.possible_types(b).to_set
|
|
178
|
+
first_cache[second_key] = a_types.intersect?(b_types)
|
|
179
|
+
end
|
|
164
180
|
end
|
|
165
181
|
|
|
166
182
|
# A hook which is called whenever a field's max complexity is calculated.
|
|
@@ -175,18 +191,16 @@ module GraphQL
|
|
|
175
191
|
# @param inner_selections [Array<Hash<String, ScopedTypeComplexity>>] Field selections for a scope
|
|
176
192
|
# @return [Integer] Total complexity value for all these selections in the parent scope
|
|
177
193
|
def merged_max_complexity(query, inner_selections)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
194
|
+
child_scopes_by_key = {}
|
|
195
|
+
inner_selections.each do |inner_selection|
|
|
196
|
+
inner_selection.each do |k, v|
|
|
197
|
+
scopes = child_scopes_by_key[k] ||= []
|
|
198
|
+
scopes << v
|
|
199
|
+
end
|
|
182
200
|
end
|
|
183
|
-
|
|
184
201
|
# Add up the total cost for each unique field name's coalesced selections
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
# all keys come with at least one scope.
|
|
188
|
-
child_scopes = inner_selections.filter_map { _1[field_key] }
|
|
189
|
-
|
|
202
|
+
total = 0
|
|
203
|
+
child_scopes_by_key.each do |field_key, child_scopes|
|
|
190
204
|
# Compute maximum possible cost of child selections;
|
|
191
205
|
# composites merge their maximums, while leaf scopes are always zero.
|
|
192
206
|
# FieldsWillMerge validation assures all scopes are uniformly composite or leaf.
|
|
@@ -214,8 +228,10 @@ module GraphQL
|
|
|
214
228
|
child_complexity: maximum_children_cost,
|
|
215
229
|
)
|
|
216
230
|
|
|
217
|
-
total
|
|
231
|
+
total += maximum_cost
|
|
218
232
|
end
|
|
233
|
+
|
|
234
|
+
total
|
|
219
235
|
end
|
|
220
236
|
|
|
221
237
|
def legacy_merged_max_complexity(query, inner_selections)
|
data/lib/graphql/analysis.rb
CHANGED
|
@@ -40,13 +40,13 @@ module GraphQL
|
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
multiplex_results = multiplex_analyzers.map(&:result)
|
|
44
|
-
multiplex_errors = analysis_errors(multiplex_results)
|
|
45
43
|
|
|
44
|
+
multiplex_analyzers.map!(&:result)
|
|
45
|
+
multiplex_errors = analysis_errors(EmptyObjects::EMPTY_ARRAY, multiplex_analyzers)
|
|
46
46
|
multiplex.queries.each_with_index do |query, idx|
|
|
47
|
-
query.analysis_errors = multiplex_errors
|
|
47
|
+
query.analysis_errors = analysis_errors(multiplex_errors, query_results[idx])
|
|
48
48
|
end
|
|
49
|
-
|
|
49
|
+
multiplex_analyzers
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
@@ -55,13 +55,11 @@ module GraphQL
|
|
|
55
55
|
# @return [Array<Any>] Results from those analyzers
|
|
56
56
|
def analyze_query(query, analyzers, multiplex_analyzers: [])
|
|
57
57
|
query.current_trace.analyze_query(query: query) do
|
|
58
|
-
query_analyzers = analyzers
|
|
59
|
-
|
|
60
|
-
.tap { _1.select!(&:analyze?) }
|
|
61
|
-
|
|
58
|
+
query_analyzers = analyzers.map { |analyzer| analyzer.new(query) }
|
|
59
|
+
query_analyzers.select!(&:analyze?)
|
|
62
60
|
analyzers_to_run = query_analyzers + multiplex_analyzers
|
|
63
|
-
if !analyzers_to_run.empty?
|
|
64
61
|
|
|
62
|
+
if !analyzers_to_run.empty?
|
|
65
63
|
analyzers_to_run.select!(&:visit?)
|
|
66
64
|
if !analyzers_to_run.empty?
|
|
67
65
|
visitor = GraphQL::Analysis::Visitor.new(
|
|
@@ -79,18 +77,27 @@ module GraphQL
|
|
|
79
77
|
|
|
80
78
|
query_analyzers.map(&:result)
|
|
81
79
|
else
|
|
82
|
-
|
|
80
|
+
EmptyObjects::EMPTY_ARRAY
|
|
83
81
|
end
|
|
84
82
|
end
|
|
85
83
|
rescue TimeoutError => err
|
|
86
84
|
[err]
|
|
87
85
|
rescue GraphQL::UnauthorizedError, GraphQL::ExecutionError
|
|
88
86
|
# This error was raised during analysis and will be returned the client before execution
|
|
89
|
-
|
|
87
|
+
EmptyObjects::EMPTY_ARRAY
|
|
90
88
|
end
|
|
91
89
|
|
|
92
|
-
def analysis_errors(results)
|
|
93
|
-
|
|
90
|
+
def analysis_errors(parent_errors, results)
|
|
91
|
+
if !results.empty?
|
|
92
|
+
results = results.flatten
|
|
93
|
+
results.select! { |r| r.is_a?(GraphQL::AnalysisError) }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
if parent_errors.empty?
|
|
97
|
+
results
|
|
98
|
+
else
|
|
99
|
+
parent_errors + results
|
|
100
|
+
end
|
|
94
101
|
end
|
|
95
102
|
end
|
|
96
103
|
end
|
|
@@ -90,7 +90,16 @@ module GraphQL
|
|
|
90
90
|
|
|
91
91
|
if ast_node
|
|
92
92
|
field_defn = query.get_field(result.graphql_result_type, ast_node.name)
|
|
93
|
-
args =
|
|
93
|
+
args = begin
|
|
94
|
+
if (cached_args = query.arguments_cache.cached_arguments_for(ast_node, field_defn))
|
|
95
|
+
cached_args.to_h
|
|
96
|
+
else
|
|
97
|
+
EmptyObjects::EMPTY_HASH
|
|
98
|
+
end
|
|
99
|
+
rescue StandardError => err
|
|
100
|
+
"Failed to load arguments, #{err.class}: #{err.message}"
|
|
101
|
+
end
|
|
102
|
+
|
|
94
103
|
field_path = field_defn.path
|
|
95
104
|
if ast_node.alias
|
|
96
105
|
field_path += " as #{ast_node.alias}"
|
data/lib/graphql/current.rb
CHANGED
|
@@ -41,7 +41,13 @@ module GraphQL
|
|
|
41
41
|
# @see GraphQL::Field#path for a string identifying this field
|
|
42
42
|
# @return [GraphQL::Field, nil] The currently-running field, if there is one.
|
|
43
43
|
def self.field
|
|
44
|
-
Fiber[:__graphql_runtime_info]
|
|
44
|
+
if (interpreter_info = Fiber[:__graphql_runtime_info])
|
|
45
|
+
interpreter_info.values&.first&.current_field
|
|
46
|
+
elsif (field = Fiber[:__graphql_current_field])
|
|
47
|
+
field
|
|
48
|
+
else
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
45
51
|
end
|
|
46
52
|
|
|
47
53
|
# @return [Class, nil] The currently-running {Dataloader::Source} class, if there is one.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "action_controller"
|
|
3
|
+
|
|
4
|
+
module Graphql
|
|
5
|
+
class Dashboard < Rails::Engine
|
|
6
|
+
class ApplicationController < ActionController::Base
|
|
7
|
+
protect_from_forgery with: :exception
|
|
8
|
+
prepend_view_path(File.expand_path("../views", __FILE__))
|
|
9
|
+
|
|
10
|
+
content_security_policy do |policy|
|
|
11
|
+
policy.default_src(:self) if policy.default_src(*policy.default_src).blank?
|
|
12
|
+
policy.connect_src(:self) if policy.connect_src(*policy.connect_src).blank?
|
|
13
|
+
policy.base_uri(:none) if policy.base_uri(*policy.base_uri).blank?
|
|
14
|
+
policy.font_src(:self) if policy.font_src(*policy.font_src).blank?
|
|
15
|
+
policy.img_src(:self, :data) if policy.img_src(*policy.img_src).blank?
|
|
16
|
+
policy.object_src(:none) if policy.object_src(*policy.object_src).blank?
|
|
17
|
+
policy.script_src(:self) if policy.script_src(*policy.script_src).blank?
|
|
18
|
+
policy.style_src(:self) if policy.style_src(*policy.style_src).blank?
|
|
19
|
+
policy.form_action(:self) if policy.form_action(*policy.form_action).blank?
|
|
20
|
+
policy.frame_ancestors(:none) if policy.frame_ancestors(*policy.frame_ancestors).blank?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def schema_class
|
|
24
|
+
@schema_class ||= begin
|
|
25
|
+
configured_schemas = Array(request.path_parameters[:schema] || request.path_parameters["schema"])
|
|
26
|
+
schema_param = request.query_parameters["schema"]
|
|
27
|
+
schema_param = configured_schemas.find { |schema| schema.to_s == schema_param } if schema_param
|
|
28
|
+
schema_param ||= configured_schemas.first
|
|
29
|
+
|
|
30
|
+
case schema_param
|
|
31
|
+
when Class
|
|
32
|
+
schema_param
|
|
33
|
+
when String
|
|
34
|
+
schema_param.constantize
|
|
35
|
+
else
|
|
36
|
+
raise "Missing `params[:schema]`, please provide a class or string to `mount GraphQL::Dashboard, schema: ...`"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
helper_method :schema_class
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
ActiveSupport.run_load_hooks(:graphql_dashboard_application_controller, GraphQL::Dashboard::ApplicationController)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Graphql
|
|
3
|
+
class Dashboard < Rails::Engine
|
|
4
|
+
class StaticsController < ApplicationController
|
|
5
|
+
skip_forgery_protection
|
|
6
|
+
# Use an explicit list of files to avoid any chance of reading other files from disk
|
|
7
|
+
STATICS = {}
|
|
8
|
+
|
|
9
|
+
[
|
|
10
|
+
"icon.png",
|
|
11
|
+
"header-icon.png",
|
|
12
|
+
"charts.min.css",
|
|
13
|
+
"dashboard.css",
|
|
14
|
+
"dashboard.js",
|
|
15
|
+
"bootstrap-5.3.3.min.css",
|
|
16
|
+
"bootstrap-5.3.3.min.js",
|
|
17
|
+
].each do |static_file|
|
|
18
|
+
STATICS[static_file] = File.expand_path("../statics/#{static_file}", __FILE__)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def show
|
|
22
|
+
expires_in 1.year, public: true
|
|
23
|
+
if (filepath = STATICS[params[:id]])
|
|
24
|
+
render file: filepath
|
|
25
|
+
else
|
|
26
|
+
head :not_found
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
require_relative "./installable"
|
|
2
3
|
module Graphql
|
|
3
4
|
class Dashboard < Rails::Engine
|
|
4
5
|
module Subscriptions
|
|
@@ -6,7 +7,7 @@ module Graphql
|
|
|
6
7
|
include Installable
|
|
7
8
|
|
|
8
9
|
def feature_installed?
|
|
9
|
-
schema_class.subscriptions.is_a?(GraphQL::Pro::Subscriptions)
|
|
10
|
+
defined?(GraphQL::Pro::Subscriptions) && schema_class.subscriptions.is_a?(GraphQL::Pro::Subscriptions)
|
|
10
11
|
end
|
|
11
12
|
|
|
12
13
|
INSTALLABLE_COMPONENT_HEADER_HTML = "GraphQL-Pro Subscriptions aren't installed on this schema yet.".html_safe
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
<div class="col">
|
|
4
4
|
<h1>Edit <%= @client.name %></h1>
|
|
5
5
|
</div>
|
|
6
|
-
|
|
6
|
+
</div>
|
|
7
7
|
<%= render partial: "graphql/dashboard/operation_store/clients/form" %>
|
|
8
8
|
|
|
9
|
-
<hr class="mt-5"
|
|
9
|
+
<hr class="mt-5">
|
|
10
10
|
<div class="row mt-5">
|
|
11
11
|
<div class="col">
|
|
12
12
|
<div class="alert alert-danger">
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
<td><%= link_to(client.name, graphql_dashboard.edit_operation_store_client_path(name: client.name)) %></td>
|
|
34
34
|
<td>
|
|
35
35
|
<%= link_to(graphql_dashboard.operation_store_client_operations_path(client_name: client.name)) do %>
|
|
36
|
-
<%= client.operations_count %><% if client.archived_operations_count > 0 %> <span class="muted">(<%=client.archived_operations_count%> archived)</span><% end %>
|
|
36
|
+
<%= client.operations_count %><% if client.archived_operations_count > 0 %> <span class="muted">(<%= client.archived_operations_count %> archived)</span><% end %>
|
|
37
37
|
<% end %>
|
|
38
38
|
</td>
|
|
39
39
|
<td><%= client.created_at %></td>
|
data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<form method="GET" action="<%= graphql_dashboard.operation_store_index_entries_path %>" style="margin-left: auto; margin-top:-5px;">
|
|
11
11
|
<div class="input-group">
|
|
12
12
|
<%= text_field_tag "q", @search_term, class: "form-control", placeholder: "Find types, fields, arguments, or enum values" %>
|
|
13
|
-
<input type="submit" value="Search" class="btn btn-outline-primary btn-sm"
|
|
13
|
+
<input type="submit" value="Search" class="btn btn-outline-primary btn-sm">
|
|
14
14
|
</div>
|
|
15
15
|
</form>
|
|
16
16
|
</div>
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="en" class="h-100" >
|
|
3
3
|
<head>
|
|
4
|
-
<link rel="icon" type="image/png" href="<%= graphql_dashboard.static_path("icon.png") %>"
|
|
4
|
+
<link rel="icon" type="image/png" href="<%= graphql_dashboard.static_path("icon.png") %>">
|
|
5
5
|
<meta charset="utf-8">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
7
|
<title>GraphQL Dashboard <%= content_for?(:title) ? " · #{content_for(:title)}" : "" %> </title>
|
|
8
|
-
<%=
|
|
9
|
-
<%=
|
|
10
|
-
<%=
|
|
11
|
-
<%=
|
|
12
|
-
<%=
|
|
8
|
+
<link rel="stylesheet" href="<%= graphql_dashboard.static_path("bootstrap-5.3.3.min.css") %>" media="screen">
|
|
9
|
+
<link rel="stylesheet" href="<%= graphql_dashboard.static_path("charts.min.css") %>" media="screen">
|
|
10
|
+
<link rel="stylesheet" href="<%= graphql_dashboard.static_path("dashboard.css") %>" media="screen">
|
|
11
|
+
<script src="<%= graphql_dashboard.static_path("bootstrap-5.3.3.min.js") %>"></script>
|
|
12
|
+
<script src="<%= graphql_dashboard.static_path("dashboard.js") %>"></script>
|
|
13
13
|
<%= csrf_meta_tags %>
|
|
14
14
|
</head>
|
|
15
15
|
<body class="h-100 d-flex flex-column">
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
|
21
21
|
<div class="container-fluid">
|
|
22
22
|
<%= link_to graphql_dashboard.root_path, class: "navbar-brand" do %>
|
|
23
|
-
<img id="header-icon" src="<%= graphql_dashboard.static_path("header-icon.png") %>" alt="GraphQL-Ruby"
|
|
23
|
+
<img id="header-icon" src="<%= graphql_dashboard.static_path("header-icon.png") %>" alt="GraphQL-Ruby">
|
|
24
24
|
<% end %>
|
|
25
25
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
|
26
26
|
<span class="navbar-toggler-icon"></span>
|
data/lib/graphql/dashboard.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require 'rails/engine'
|
|
3
|
+
require 'action_controller'
|
|
3
4
|
module Graphql
|
|
4
5
|
# `GraphQL::Dashboard` is a `Rails::Engine`-based dashboard for viewing metadata about your GraphQL schema.
|
|
5
6
|
#
|
|
@@ -9,6 +10,9 @@ module Graphql
|
|
|
9
10
|
# @example Mounting the Dashboard in your app
|
|
10
11
|
# mount GraphQL::Dashboard, at: "graphql_dashboard", schema: "MySchema"
|
|
11
12
|
#
|
|
13
|
+
# Pass an array to allow selecting from multiple schemas with the `schema` query parameter.
|
|
14
|
+
# mount GraphQL::Dashboard, at: "graphql_dashboard", schema: ["MySchema", "OtherSchema"]
|
|
15
|
+
#
|
|
12
16
|
# @example Authenticating the Dashboard with HTTP Basic Auth
|
|
13
17
|
# # config/initializers/graphql_dashboard.rb
|
|
14
18
|
# GraphQL::Dashboard.middleware.use(Rack::Auth::Basic) do |username, password|
|
|
@@ -34,7 +38,16 @@ module Graphql
|
|
|
34
38
|
class Dashboard < Rails::Engine
|
|
35
39
|
engine_name "graphql_dashboard"
|
|
36
40
|
isolate_namespace(Graphql::Dashboard)
|
|
37
|
-
|
|
41
|
+
|
|
42
|
+
autoload :ApplicationController, "graphql/dashboard/application_controller"
|
|
43
|
+
autoload :LandingsController, "graphql/dashboard/landings_controller"
|
|
44
|
+
autoload :StaticsController, "graphql/dashboard/statics_controller"
|
|
45
|
+
autoload :DetailedTraces, "graphql/dashboard/detailed_traces"
|
|
46
|
+
autoload :Subscriptions, "graphql/dashboard/subscriptions"
|
|
47
|
+
autoload :OperationStore, "graphql/dashboard/operation_store"
|
|
48
|
+
autoload :Limiters, "graphql/dashboard/limiters"
|
|
49
|
+
|
|
50
|
+
routes do
|
|
38
51
|
root "landings#show"
|
|
39
52
|
resources :statics, only: :show, constraints: { id: /[0-9A-Za-z\-.]+/ }
|
|
40
53
|
|
|
@@ -77,82 +90,10 @@ module Graphql
|
|
|
77
90
|
post "/subscriptions/clear_all", to: "subscriptions#clear_all", as: :clear_all
|
|
78
91
|
end
|
|
79
92
|
end
|
|
80
|
-
|
|
81
|
-
class ApplicationController < ActionController::Base
|
|
82
|
-
protect_from_forgery with: :exception
|
|
83
|
-
prepend_view_path(File.join(__FILE__, "../dashboard/views"))
|
|
84
|
-
|
|
85
|
-
content_security_policy do |policy|
|
|
86
|
-
policy.default_src(:self) if policy.default_src(*policy.default_src).blank?
|
|
87
|
-
policy.connect_src(:self) if policy.connect_src(*policy.connect_src).blank?
|
|
88
|
-
policy.base_uri(:none) if policy.base_uri(*policy.base_uri).blank?
|
|
89
|
-
policy.font_src(:self) if policy.font_src(*policy.font_src).blank?
|
|
90
|
-
policy.img_src(:self, :data) if policy.img_src(*policy.img_src).blank?
|
|
91
|
-
policy.object_src(:none) if policy.object_src(*policy.object_src).blank?
|
|
92
|
-
policy.script_src(:self) if policy.script_src(*policy.script_src).blank?
|
|
93
|
-
policy.style_src(:self) if policy.style_src(*policy.style_src).blank?
|
|
94
|
-
policy.form_action(:self) if policy.form_action(*policy.form_action).blank?
|
|
95
|
-
policy.frame_ancestors(:none) if policy.frame_ancestors(*policy.frame_ancestors).blank?
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def schema_class
|
|
99
|
-
@schema_class ||= begin
|
|
100
|
-
schema_param = request.query_parameters["schema"] || params[:schema]
|
|
101
|
-
case schema_param
|
|
102
|
-
when Class
|
|
103
|
-
schema_param
|
|
104
|
-
when String
|
|
105
|
-
schema_param.constantize
|
|
106
|
-
else
|
|
107
|
-
raise "Missing `params[:schema]`, please provide a class or string to `mount GraphQL::Dashboard, schema: ...`"
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
helper_method :schema_class
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
class LandingsController < ApplicationController
|
|
115
|
-
def show
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
class StaticsController < ApplicationController
|
|
120
|
-
skip_after_action :verify_same_origin_request
|
|
121
|
-
# Use an explicit list of files to avoid any chance of reading other files from disk
|
|
122
|
-
STATICS = {}
|
|
123
|
-
|
|
124
|
-
[
|
|
125
|
-
"icon.png",
|
|
126
|
-
"header-icon.png",
|
|
127
|
-
"charts.min.css",
|
|
128
|
-
"dashboard.css",
|
|
129
|
-
"dashboard.js",
|
|
130
|
-
"bootstrap-5.3.3.min.css",
|
|
131
|
-
"bootstrap-5.3.3.min.js",
|
|
132
|
-
].each do |static_file|
|
|
133
|
-
STATICS[static_file] = File.expand_path("../dashboard/statics/#{static_file}", __FILE__)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def show
|
|
137
|
-
expires_in 1.year, public: true
|
|
138
|
-
if (filepath = STATICS[params[:id]])
|
|
139
|
-
render file: filepath
|
|
140
|
-
else
|
|
141
|
-
head :not_found
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
93
|
end
|
|
146
94
|
end
|
|
147
95
|
|
|
148
|
-
require 'graphql/dashboard/detailed_traces'
|
|
149
|
-
require 'graphql/dashboard/limiters'
|
|
150
|
-
require 'graphql/dashboard/operation_store'
|
|
151
|
-
require 'graphql/dashboard/subscriptions'
|
|
152
|
-
|
|
153
96
|
# Rails expects the engine to be called `Graphql::Dashboard`,
|
|
154
97
|
# but `GraphQL::Dashboard` is consistent with this gem's naming.
|
|
155
98
|
# So define both constants to refer to the same class.
|
|
156
99
|
GraphQL::Dashboard = Graphql::Dashboard
|
|
157
|
-
|
|
158
|
-
ActiveSupport.run_load_hooks(:graphql_dashboard_application_controller, GraphQL::Dashboard::ApplicationController)
|