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.
Files changed (154) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/field_extractor.rb +17 -1
  4. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  5. data/lib/generators/graphql/templates/schema.erb +2 -1
  6. data/lib/graphql/analysis/query_complexity.rb +31 -15
  7. data/lib/graphql/analysis.rb +20 -13
  8. data/lib/graphql/backtrace/table.rb +10 -1
  9. data/lib/graphql/current.rb +7 -1
  10. data/lib/graphql/dashboard/application_controller.rb +45 -0
  11. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  12. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  13. data/lib/graphql/dashboard/subscriptions.rb +2 -1
  14. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +1 -0
  15. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +2 -2
  16. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +1 -1
  17. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +1 -1
  18. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +1 -1
  19. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +1 -1
  20. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +1 -1
  21. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +7 -7
  22. data/lib/graphql/dashboard.rb +14 -73
  23. data/lib/graphql/dataloader/async_dataloader.rb +336 -60
  24. data/lib/graphql/dataloader/null_dataloader.rb +48 -10
  25. data/lib/graphql/dataloader/source.rb +32 -19
  26. data/lib/graphql/dataloader.rb +107 -39
  27. data/lib/graphql/date_encoding_error.rb +1 -1
  28. data/lib/graphql/execution/directive_checks.rb +2 -0
  29. data/lib/graphql/execution/field_resolve_step.rb +804 -0
  30. data/lib/graphql/execution/finalize.rb +229 -0
  31. data/lib/graphql/execution/input_values.rb +333 -0
  32. data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -0
  33. data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
  34. data/lib/graphql/execution/interpreter/resolve.rb +7 -13
  35. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +13 -0
  36. data/lib/graphql/execution/interpreter/runtime.rb +60 -40
  37. data/lib/graphql/execution/interpreter.rb +17 -23
  38. data/lib/graphql/execution/lazy.rb +1 -1
  39. data/lib/graphql/execution/load_argument_step.rb +102 -0
  40. data/lib/graphql/execution/multiplex.rb +1 -1
  41. data/lib/graphql/execution/next.rb +98 -0
  42. data/lib/graphql/execution/prepare_object_step.rb +151 -0
  43. data/lib/graphql/execution/resolve_type_step.rb +27 -0
  44. data/lib/graphql/execution/runner.rb +457 -0
  45. data/lib/graphql/execution/selections_step.rb +95 -0
  46. data/lib/graphql/execution.rb +15 -4
  47. data/lib/graphql/execution_error.rb +17 -12
  48. data/lib/graphql/float_decoding_error.rb +13 -0
  49. data/lib/graphql/float_encoding_error.rb +28 -0
  50. data/lib/graphql/introspection/directive_type.rb +7 -3
  51. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  52. data/lib/graphql/introspection/entry_points.rb +11 -3
  53. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  54. data/lib/graphql/introspection/field_type.rb +13 -5
  55. data/lib/graphql/introspection/input_value_type.rb +21 -13
  56. data/lib/graphql/introspection/schema_type.rb +6 -2
  57. data/lib/graphql/introspection/type_type.rb +64 -28
  58. data/lib/graphql/invalid_null_error.rb +11 -5
  59. data/lib/graphql/language/block_string.rb +6 -11
  60. data/lib/graphql/language/cache.rb +84 -14
  61. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  62. data/lib/graphql/language/lexer.rb +27 -17
  63. data/lib/graphql/language/nodes.rb +2 -1
  64. data/lib/graphql/language/parser.rb +1 -1
  65. data/lib/graphql/language.rb +32 -17
  66. data/lib/graphql/pagination/connection.rb +2 -0
  67. data/lib/graphql/pagination/connections.rb +30 -0
  68. data/lib/graphql/pagination/relation_connection.rb +10 -1
  69. data/lib/graphql/query/context.rb +10 -3
  70. data/lib/graphql/query/null_context.rb +9 -3
  71. data/lib/graphql/query/partial.rb +18 -3
  72. data/lib/graphql/query/variable_validation_error.rb +16 -1
  73. data/lib/graphql/query/variables.rb +1 -1
  74. data/lib/graphql/query.rb +12 -3
  75. data/lib/graphql/railtie.rb +2 -1
  76. data/lib/graphql/runtime_error.rb +6 -0
  77. data/lib/graphql/schema/argument.rb +13 -1
  78. data/lib/graphql/schema/build_from_definition.rb +28 -1
  79. data/lib/graphql/schema/directive/feature.rb +4 -0
  80. data/lib/graphql/schema/directive/transform.rb +20 -0
  81. data/lib/graphql/schema/directive.rb +48 -13
  82. data/lib/graphql/schema/field/connection_extension.rb +4 -37
  83. data/lib/graphql/schema/field/scope_extension.rb +18 -13
  84. data/lib/graphql/schema/field.rb +79 -48
  85. data/lib/graphql/schema/field_extension.rb +11 -8
  86. data/lib/graphql/schema/has_single_input_argument.rb +24 -13
  87. data/lib/graphql/schema/input_object.rb +6 -2
  88. data/lib/graphql/schema/interface.rb +26 -0
  89. data/lib/graphql/schema/introspection_system.rb +6 -21
  90. data/lib/graphql/schema/list.rb +5 -1
  91. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -11
  92. data/lib/graphql/schema/member/has_arguments.rb +43 -14
  93. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  94. data/lib/graphql/schema/member/has_dataloader.rb +37 -0
  95. data/lib/graphql/schema/member/has_fields.rb +86 -5
  96. data/lib/graphql/schema/member.rb +5 -0
  97. data/lib/graphql/schema/non_null.rb +1 -1
  98. data/lib/graphql/schema/object.rb +1 -0
  99. data/lib/graphql/schema/printer.rb +1 -1
  100. data/lib/graphql/schema/ractor_shareable.rb +1 -0
  101. data/lib/graphql/schema/relay_classic_mutation.rb +16 -2
  102. data/lib/graphql/schema/resolver.rb +76 -1
  103. data/lib/graphql/schema/subscription.rb +53 -10
  104. data/lib/graphql/schema/timeout.rb +2 -2
  105. data/lib/graphql/schema/validator/allow_blank_validator.rb +3 -3
  106. data/lib/graphql/schema/validator/allow_null_validator.rb +3 -3
  107. data/lib/graphql/schema/validator/exclusion_validator.rb +2 -2
  108. data/lib/graphql/schema/validator/format_validator.rb +3 -3
  109. data/lib/graphql/schema/validator/inclusion_validator.rb +2 -2
  110. data/lib/graphql/schema/validator/length_validator.rb +6 -6
  111. data/lib/graphql/schema/validator/numericality_validator.rb +19 -19
  112. data/lib/graphql/schema/validator/required_validator.rb +38 -5
  113. data/lib/graphql/schema/validator.rb +9 -0
  114. data/lib/graphql/schema/visibility/profile.rb +74 -53
  115. data/lib/graphql/schema/visibility/visit.rb +1 -1
  116. data/lib/graphql/schema/visibility.rb +32 -24
  117. data/lib/graphql/schema/wrapper.rb +11 -1
  118. data/lib/graphql/schema.rb +93 -27
  119. data/lib/graphql/static_validation/base_visitor.rb +90 -66
  120. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  121. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
  122. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
  123. data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
  124. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
  125. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +14 -4
  126. data/lib/graphql/static_validation/rules/fields_will_merge.rb +351 -248
  127. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
  128. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  129. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
  130. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
  131. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
  132. data/lib/graphql/static_validation/validation_context.rb +1 -1
  133. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  134. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +40 -10
  135. data/lib/graphql/subscriptions/serialize.rb +10 -4
  136. data/lib/graphql/subscriptions.rb +36 -1
  137. data/lib/graphql/testing/helpers.rb +12 -9
  138. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  139. data/lib/graphql/testing.rb +1 -0
  140. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  141. data/lib/graphql/tracing/detailed_trace.rb +70 -7
  142. data/lib/graphql/tracing/perfetto_trace.rb +213 -81
  143. data/lib/graphql/tracing/sentry_trace.rb +3 -1
  144. data/lib/graphql/tracing/trace.rb +6 -0
  145. data/lib/graphql/types/float.rb +18 -4
  146. data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
  147. data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
  148. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  149. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  150. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  151. data/lib/graphql/unauthorized_error.rb +9 -1
  152. data/lib/graphql/version.rb +1 -1
  153. data/lib/graphql.rb +8 -2
  154. metadata +23 -59
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c2ef756861a779063236ee9f8745d30ae3494138674239c8f638556d7f5105e
4
- data.tar.gz: 23592e61f76acb0be2a4c60d953a829e8f1d46534633513ac44797a5d3386d2a
3
+ metadata.gz: a6c191887acba06a4030f962822e9106e9d3aa9d5550c8cd25ea76f343927f1b
4
+ data.tar.gz: b984ad9a61a391b4b8a42ade3bb9c63dc79ab2b3ee987845d655dc7038df1255
5
5
  SHA512:
6
- metadata.gz: 5c8db1d976343d569aa1fec1cee02de2c72947e0937fcd9917f8f6e078204efd373d244954e9e03b8dc7fd20066dcf7533bf4b52c706dba0f5640b60ae0465f0
7
- data.tar.gz: 38422c543b159cc7243ac4dcc9139ed95b2583cab7942c60e4c557b89d6a4ffaa8fb1df94286fa3fbe4b918f5c116364d5fbe5c2afa611ba9d46d7ac56c18e61
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
- columns += (klass&.columns&.map { |c| generate_column_string(c) } || [])
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 <<~GRAPHQL
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
- GRAPHQL
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
- a_types = query.types.possible_types(a)
163
- query.types.possible_types(b).any? { |t| a_types.include?(t) }
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
- # Aggregate a set of all unique field selection keys across all scopes.
179
- # Use a hash, but ignore the values; it's just a fast way to work with the keys.
180
- unique_field_keys = inner_selections.each_with_object({}) do |inner_selection, memo|
181
- memo.merge!(inner_selection)
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
- unique_field_keys.each_key.reduce(0) do |total, field_key|
186
- # Collect all child scopes for this field key;
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 + maximum_cost
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)
@@ -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 + analysis_errors(query_results[idx])
47
+ query.analysis_errors = analysis_errors(multiplex_errors, query_results[idx])
48
48
  end
49
- multiplex_results
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
- .map { |analyzer| analyzer.new(query) }
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
- results.flatten.tap { _1.select! { |r| r.is_a?(GraphQL::AnalysisError) } }
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 = query.arguments_for(ast_node, field_defn).to_h
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}"
@@ -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]&.values&.first&.current_field
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,9 @@
1
+ # frozen_string_literal: true
2
+ module Graphql
3
+ class Dashboard < Rails::Engine
4
+ class LandingsController < ApplicationController
5
+ def show
6
+ end
7
+ end
8
+ end
9
+ end
@@ -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
@@ -20,4 +20,5 @@
20
20
  <div class="col-auto">
21
21
  <%= link_to "Back", graphql_dashboard.operation_store_clients_path, class: "btn btn-outline-secondary" %>
22
22
  </div>
23
+ </div>
23
24
  <% end %>
@@ -3,10 +3,10 @@
3
3
  <div class="col">
4
4
  <h1>Edit <%= @client.name %></h1>
5
5
  </div>
6
- <div>
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>
@@ -3,5 +3,5 @@
3
3
  <div class="col">
4
4
  <h1>New Client</h1>
5
5
  </div>
6
- <div>
6
+ </div>
7
7
  <%= render partial: "graphql/dashboard/operation_store/clients/form" %>
@@ -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>
@@ -2,7 +2,7 @@
2
2
  <% if @operation.nil? %>
3
3
  <div class="row">
4
4
  <div class="col">
5
- <p>No stored operation found for <code><%= params[:digest] %></code>
5
+ <p>No stored operation found for <code><%= params[:digest] %></code></p>
6
6
  </div>
7
7
  </div>
8
8
  <% else %>
@@ -9,7 +9,7 @@
9
9
  <div class="col">
10
10
  <p>Last triggered: <%= @topic_last_triggered_at || "none" %></p>
11
11
  <p><%= pluralize(@subscriptions_count, "Subscription") %></p>
12
- <div>
12
+ </div>
13
13
  </div>
14
14
 
15
15
  <div class="row">
@@ -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
- <%= stylesheet_link_tag graphql_dashboard.static_path("bootstrap-5.3.3.min.css") %>
9
- <%= stylesheet_link_tag graphql_dashboard.static_path("charts.min.css") %>
10
- <%= stylesheet_link_tag graphql_dashboard.static_path("dashboard.css") %>
11
- <%= javascript_include_tag graphql_dashboard.static_path("bootstrap-5.3.3.min.js") %>
12
- <%= javascript_include_tag graphql_dashboard.static_path("dashboard.js") %>
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>
@@ -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
- routes.draw do
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)