graphql 2.0.27 → 2.2.6

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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  3. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  4. data/lib/generators/graphql/install_generator.rb +3 -0
  5. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  6. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  7. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  8. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  9. data/lib/generators/graphql/templates/base_field.erb +2 -0
  10. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  11. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  12. data/lib/generators/graphql/templates/base_object.erb +2 -0
  13. data/lib/generators/graphql/templates/base_resolver.erb +6 -0
  14. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  15. data/lib/generators/graphql/templates/base_union.erb +2 -0
  16. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  17. data/lib/generators/graphql/templates/loader.erb +2 -0
  18. data/lib/generators/graphql/templates/mutation.erb +2 -0
  19. data/lib/generators/graphql/templates/node_type.erb +2 -0
  20. data/lib/generators/graphql/templates/query_type.erb +2 -0
  21. data/lib/generators/graphql/templates/schema.erb +2 -0
  22. data/lib/graphql/analysis/ast/analyzer.rb +7 -0
  23. data/lib/graphql/analysis/ast/field_usage.rb +32 -7
  24. data/lib/graphql/analysis/ast/query_complexity.rb +80 -128
  25. data/lib/graphql/analysis/ast/query_depth.rb +7 -2
  26. data/lib/graphql/analysis/ast/visitor.rb +2 -2
  27. data/lib/graphql/analysis/ast.rb +21 -11
  28. data/lib/graphql/backtrace/trace.rb +12 -15
  29. data/lib/graphql/coercion_error.rb +1 -9
  30. data/lib/graphql/dataloader/async_dataloader.rb +85 -0
  31. data/lib/graphql/dataloader/source.rb +11 -3
  32. data/lib/graphql/dataloader.rb +109 -142
  33. data/lib/graphql/duration_encoding_error.rb +16 -0
  34. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +170 -0
  35. data/lib/graphql/execution/interpreter/runtime.rb +70 -248
  36. data/lib/graphql/execution/interpreter.rb +91 -157
  37. data/lib/graphql/execution/lookahead.rb +88 -21
  38. data/lib/graphql/introspection/dynamic_fields.rb +1 -1
  39. data/lib/graphql/introspection/entry_points.rb +11 -5
  40. data/lib/graphql/introspection/schema_type.rb +3 -1
  41. data/lib/graphql/language/block_string.rb +34 -18
  42. data/lib/graphql/language/definition_slice.rb +1 -1
  43. data/lib/graphql/language/document_from_schema_definition.rb +37 -37
  44. data/lib/graphql/language/lexer.rb +271 -177
  45. data/lib/graphql/language/nodes.rb +74 -56
  46. data/lib/graphql/language/parser.rb +697 -1986
  47. data/lib/graphql/language/printer.rb +299 -146
  48. data/lib/graphql/language/sanitized_printer.rb +20 -22
  49. data/lib/graphql/language/static_visitor.rb +167 -0
  50. data/lib/graphql/language/visitor.rb +20 -81
  51. data/lib/graphql/language.rb +1 -0
  52. data/lib/graphql/load_application_object_failed_error.rb +5 -1
  53. data/lib/graphql/pagination/array_connection.rb +3 -3
  54. data/lib/graphql/pagination/connection.rb +28 -1
  55. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
  56. data/lib/graphql/pagination/relation_connection.rb +3 -3
  57. data/lib/graphql/query/context/scoped_context.rb +101 -0
  58. data/lib/graphql/query/context.rb +36 -98
  59. data/lib/graphql/query/null_context.rb +4 -11
  60. data/lib/graphql/query/validation_pipeline.rb +2 -2
  61. data/lib/graphql/query/variables.rb +3 -3
  62. data/lib/graphql/query.rb +13 -22
  63. data/lib/graphql/railtie.rb +9 -6
  64. data/lib/graphql/rake_task.rb +3 -12
  65. data/lib/graphql/schema/argument.rb +6 -1
  66. data/lib/graphql/schema/build_from_definition.rb +0 -11
  67. data/lib/graphql/schema/directive/one_of.rb +12 -0
  68. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  69. data/lib/graphql/schema/directive.rb +1 -1
  70. data/lib/graphql/schema/enum.rb +3 -3
  71. data/lib/graphql/schema/field/connection_extension.rb +1 -15
  72. data/lib/graphql/schema/field/scope_extension.rb +8 -1
  73. data/lib/graphql/schema/field.rb +8 -5
  74. data/lib/graphql/schema/has_single_input_argument.rb +156 -0
  75. data/lib/graphql/schema/input_object.rb +2 -2
  76. data/lib/graphql/schema/interface.rb +10 -10
  77. data/lib/graphql/schema/introspection_system.rb +2 -0
  78. data/lib/graphql/schema/loader.rb +0 -2
  79. data/lib/graphql/schema/member/base_dsl_methods.rb +2 -1
  80. data/lib/graphql/schema/member/has_arguments.rb +61 -38
  81. data/lib/graphql/schema/member/has_fields.rb +8 -5
  82. data/lib/graphql/schema/member/has_interfaces.rb +23 -9
  83. data/lib/graphql/schema/member/scoped.rb +19 -0
  84. data/lib/graphql/schema/member/validates_input.rb +3 -3
  85. data/lib/graphql/schema/object.rb +8 -0
  86. data/lib/graphql/schema/printer.rb +8 -7
  87. data/lib/graphql/schema/relay_classic_mutation.rb +6 -128
  88. data/lib/graphql/schema/resolver.rb +7 -3
  89. data/lib/graphql/schema/scalar.rb +3 -3
  90. data/lib/graphql/schema/subscription.rb +11 -4
  91. data/lib/graphql/schema/union.rb +1 -1
  92. data/lib/graphql/schema/warden.rb +96 -94
  93. data/lib/graphql/schema.rb +219 -72
  94. data/lib/graphql/static_validation/all_rules.rb +1 -1
  95. data/lib/graphql/static_validation/base_visitor.rb +1 -1
  96. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  97. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  98. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  99. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
  100. data/lib/graphql/static_validation/validation_context.rb +5 -5
  101. data/lib/graphql/static_validation/validator.rb +3 -0
  102. data/lib/graphql/static_validation.rb +0 -1
  103. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +3 -2
  104. data/lib/graphql/subscriptions/event.rb +8 -2
  105. data/lib/graphql/subscriptions.rb +14 -12
  106. data/lib/graphql/testing/helpers.rb +125 -0
  107. data/lib/graphql/testing.rb +2 -0
  108. data/lib/graphql/tracing/appoptics_trace.rb +2 -2
  109. data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
  110. data/lib/graphql/tracing/data_dog_trace.rb +21 -34
  111. data/lib/graphql/tracing/data_dog_tracing.rb +7 -21
  112. data/lib/graphql/tracing/legacy_hooks_trace.rb +74 -0
  113. data/lib/graphql/tracing/platform_tracing.rb +2 -0
  114. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +3 -1
  115. data/lib/graphql/tracing/sentry_trace.rb +94 -0
  116. data/lib/graphql/tracing/trace.rb +1 -0
  117. data/lib/graphql/tracing.rb +3 -1
  118. data/lib/graphql/types/iso_8601_duration.rb +77 -0
  119. data/lib/graphql/types/relay/connection_behaviors.rb +32 -2
  120. data/lib/graphql/types/relay/edge_behaviors.rb +7 -0
  121. data/lib/graphql/types.rb +1 -0
  122. data/lib/graphql/version.rb +1 -1
  123. data/lib/graphql.rb +3 -3
  124. data/readme.md +12 -2
  125. metadata +33 -25
  126. data/lib/graphql/deprecation.rb +0 -9
  127. data/lib/graphql/filter.rb +0 -59
  128. data/lib/graphql/language/parser.y +0 -560
  129. data/lib/graphql/static_validation/type_stack.rb +0 -216
  130. data/lib/graphql/subscriptions/instrumentation.rb +0 -28
@@ -37,7 +37,7 @@ module GraphQL
37
37
  end
38
38
 
39
39
  # @return [String] an identifier for this unit of subscription
40
- def self.serialize(_name, arguments, field, scope:, context: GraphQL::Query::NullContext)
40
+ def self.serialize(_name, arguments, field, scope:, context: GraphQL::Query::NullContext.instance)
41
41
  subscription = field.resolver || GraphQL::Schema::Subscription
42
42
  normalized_args = stringify_args(field, arguments.to_h, context)
43
43
  subscription.topic_for(arguments: normalized_args, field: field, scope: scope)
@@ -126,7 +126,13 @@ module GraphQL
126
126
  when GraphQL::Schema::InputObject
127
127
  stringify_args(arg_owner, args.to_h, context)
128
128
  else
129
- args
129
+ if arg_owner.is_a?(Class) && arg_owner < GraphQL::Schema::Enum
130
+ # `prepare:` may have made the value something other than
131
+ # a defined value of this enum -- use _that_ in this case.
132
+ arg_owner.coerce_isolated_input(args) || args
133
+ else
134
+ args
135
+ end
130
136
  end
131
137
  end
132
138
 
@@ -2,7 +2,6 @@
2
2
  require "securerandom"
3
3
  require "graphql/subscriptions/broadcast_analyzer"
4
4
  require "graphql/subscriptions/event"
5
- require "graphql/subscriptions/instrumentation"
6
5
  require "graphql/subscriptions/serialize"
7
6
  require "graphql/subscriptions/action_cable_subscriptions"
8
7
  require "graphql/subscriptions/default_subscription_resolve_extension"
@@ -30,8 +29,6 @@ module GraphQL
30
29
  raise ArgumentError, "Can't reinstall subscriptions. #{schema} is using #{schema.subscriptions}, can't also add #{self}"
31
30
  end
32
31
 
33
- instrumentation = Subscriptions::Instrumentation.new(schema: schema)
34
- defn.instrument(:query, instrumentation)
35
32
  options[:schema] = schema
36
33
  schema.subscriptions = self.new(**options)
37
34
  schema.add_subscription_extension_if_necessary
@@ -62,7 +59,7 @@ module GraphQL
62
59
  # @return [void]
63
60
  def trigger(event_name, args, object, scope: nil, context: {})
64
61
  # Make something as context-like as possible, even though there isn't a current query:
65
- dummy_query = GraphQL::Query.new(@schema, "", validate: false, context: context)
62
+ dummy_query = @schema.query_class.new(@schema, "{ __typename }", validate: false, context: context)
66
63
  context = dummy_query.context
67
64
  event_name = event_name.to_s
68
65
 
@@ -83,7 +80,7 @@ module GraphQL
83
80
 
84
81
  # Normalize symbol-keyed args to strings, try camelizing them
85
82
  # Should this accept a real context somehow?
86
- normalized_args = normalize_arguments(normalized_event_name, field, args, GraphQL::Query::NullContext)
83
+ normalized_args = normalize_arguments(normalized_event_name, field, args, GraphQL::Query::NullContext.instance)
87
84
 
88
85
  event = Subscriptions::Event.new(
89
86
  name: normalized_event_name,
@@ -125,10 +122,10 @@ module GraphQL
125
122
  variables: variables,
126
123
  root_value: object,
127
124
  }
128
-
125
+
129
126
  # merge event's and query's context together
130
127
  context.merge!(event.context) unless event.context.nil? || context.nil?
131
-
128
+
132
129
  execute_options[:validate] = validate_update?(**execute_options)
133
130
  result = @schema.execute(**execute_options)
134
131
  subscriptions_context = result.context.namespace(:subscriptions)
@@ -136,11 +133,9 @@ module GraphQL
136
133
  result = nil
137
134
  end
138
135
 
139
- unsubscribed = subscriptions_context[:unsubscribed]
140
-
141
- if unsubscribed
136
+ if subscriptions_context[:unsubscribed] && !subscriptions_context[:final_update]
142
137
  # `unsubscribe` was called, clean up on our side
143
- # TODO also send `{more: false}` to client?
138
+ # The transport should also send `{more: false}` to client
144
139
  delete_subscription(subscription_id)
145
140
  result = nil
146
141
  end
@@ -164,7 +159,14 @@ module GraphQL
164
159
  res = execute_update(subscription_id, event, object)
165
160
  if !res.nil?
166
161
  deliver(subscription_id, res)
162
+
163
+ if res.context.namespace(:subscriptions)[:unsubscribed]
164
+ # `unsubscribe` was called, clean up on our side
165
+ # The transport should also send `{more: false}` to client
166
+ delete_subscription(subscription_id)
167
+ end
167
168
  end
169
+
168
170
  end
169
171
 
170
172
  # Event `event` occurred on `object`,
@@ -229,7 +231,7 @@ module GraphQL
229
231
 
230
232
  # @return [Boolean] if true, then a query like this one would be broadcasted
231
233
  def broadcastable?(query_str, **query_options)
232
- query = GraphQL::Query.new(@schema, query_str, **query_options)
234
+ query = @schema.query_class.new(@schema, query_str, **query_options)
233
235
  if !query.valid?
234
236
  raise "Invalid query: #{query.validation_errors.map(&:to_h).inspect}"
235
237
  end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Testing
4
+ module Helpers
5
+ # @param schema_class [Class<GraphQL::Schema>]
6
+ # @return [Module] A helpers module which always uses the given schema
7
+ def self.for(schema_class)
8
+ Module.new do
9
+ include SchemaHelpers
10
+ @@schema_class_for_helpers = schema_class
11
+ end
12
+ end
13
+
14
+ class Error < GraphQL::Error
15
+ end
16
+
17
+ class TypeNotVisibleError < Error
18
+ def initialize(type_name:)
19
+ message = "`#{type_name}` should be `visible?` this field resolution and `context`, but it was not"
20
+ super(message)
21
+ end
22
+ end
23
+
24
+ class FieldNotVisibleError < Error
25
+ def initialize(type_name:, field_name:)
26
+ message = "`#{type_name}.#{field_name}` should be `visible?` for this resolution, but it was not"
27
+ super(message)
28
+ end
29
+ end
30
+
31
+ class TypeNotDefinedError < Error
32
+ def initialize(type_name:)
33
+ message = "No type named `#{type_name}` is defined; choose another type name or define this type."
34
+ super(message)
35
+ end
36
+ end
37
+
38
+ class FieldNotDefinedError < Error
39
+ def initialize(type_name:, field_name:)
40
+ message = "`#{type_name}` has no field named `#{field_name}`; pick another name or define this field."
41
+ super(message)
42
+ end
43
+ end
44
+
45
+ def run_graphql_field(schema, field_path, object, arguments: {}, context: {})
46
+ type_name, *field_names = field_path.split(".")
47
+ dummy_query = GraphQL::Query.new(schema, context: context)
48
+ query_context = dummy_query.context
49
+ object_type = dummy_query.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop
50
+ if object_type
51
+ graphql_result = object
52
+ field_names.each do |field_name|
53
+ inner_object = graphql_result
54
+ graphql_result = object_type.wrap(inner_object, query_context)
55
+ if graphql_result.nil?
56
+ return nil
57
+ end
58
+ visible_field = dummy_query.get_field(object_type, field_name)
59
+ if visible_field
60
+ dummy_query.context.dataloader.run_isolated {
61
+ field_args = visible_field.coerce_arguments(graphql_result, arguments, query_context)
62
+ field_args = schema.sync_lazy(field_args)
63
+ graphql_result = visible_field.resolve(graphql_result, field_args.keyword_arguments, query_context)
64
+ graphql_result = schema.sync_lazy(graphql_result)
65
+ }
66
+ object_type = visible_field.type.unwrap
67
+ elsif object_type.all_field_definitions.any? { |f| f.graphql_name == field_name }
68
+ raise FieldNotVisibleError.new(field_name: field_name, type_name: type_name)
69
+ else
70
+ raise FieldNotDefinedError.new(type_name: type_name, field_name: field_name)
71
+ end
72
+ end
73
+ graphql_result
74
+ elsif schema.has_defined_type?(type_name)
75
+ raise TypeNotVisibleError.new(type_name: type_name)
76
+ else
77
+ raise TypeNotDefinedError.new(type_name: type_name)
78
+ end
79
+ end
80
+
81
+ def with_resolution_context(schema, type:, object:, context:{})
82
+ resolution_context = ResolutionAssertionContext.new(
83
+ self,
84
+ schema: schema,
85
+ type_name: type,
86
+ object: object,
87
+ context: context
88
+ )
89
+ yield(resolution_context)
90
+ end
91
+
92
+ class ResolutionAssertionContext
93
+ def initialize(test, type_name:, object:, schema:, context:)
94
+ @test = test
95
+ @type_name = type_name
96
+ @object = object
97
+ @schema = schema
98
+ @context = context
99
+ end
100
+
101
+
102
+ def run_graphql_field(field_name, arguments: {})
103
+ if @schema
104
+ @test.run_graphql_field(@schema, "#{@type_name}.#{field_name}", @object, arguments: arguments, context: @context)
105
+ else
106
+ @test.run_graphql_field("#{@type_name}.#{field_name}", @object, arguments: arguments, context: @context)
107
+ end
108
+ end
109
+ end
110
+
111
+ module SchemaHelpers
112
+ include Helpers
113
+
114
+ def run_graphql_field(field_path, object, arguments: {}, context: {})
115
+ super(@@schema_class_for_helpers, field_path, object, arguments: arguments, context: context)
116
+ end
117
+
118
+ def with_resolution_context(*args, **kwargs, &block)
119
+ # schema will be added later
120
+ super(nil, *args, **kwargs, &block)
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,2 @@
1
+ # frozen_string_literal: true
2
+ require "graphql/testing/helpers"
@@ -195,7 +195,7 @@ module GraphQL
195
195
  else
196
196
  [key, data[key]]
197
197
  end
198
- end.flatten(2).each_slice(2).to_h.merge(Spec: 'graphql')
198
+ end.tap { _1.flatten!(2) }.each_slice(2).to_h.merge(Spec: 'graphql')
199
199
  end
200
200
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
201
201
 
@@ -226,7 +226,7 @@ module GraphQL
226
226
  end
227
227
 
228
228
  def graphql_multiplex(data)
229
- names = data.queries.map(&:operations).map(&:keys).flatten.compact
229
+ names = data.queries.map(&:operations).map!(&:keys).tap(&:flatten!).tap(&:compact!)
230
230
  multiplex_transaction_name(names) if names.size > 1
231
231
 
232
232
  [:Operations, names.join(', ')]
@@ -117,7 +117,7 @@ module GraphQL
117
117
  else
118
118
  [key, data[key]]
119
119
  end
120
- end.flatten(2).each_slice(2).to_h.merge(Spec: 'graphql')
120
+ end.tap { _1.flatten!(2) }.each_slice(2).to_h.merge(Spec: 'graphql')
121
121
  end
122
122
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
123
123
 
@@ -148,7 +148,7 @@ module GraphQL
148
148
  end
149
149
 
150
150
  def graphql_multiplex(data)
151
- names = data.queries.map(&:operations).map(&:keys).flatten.compact
151
+ names = data.queries.map(&:operations).map!(&:keys).tap(&:flatten!).tap(&:compact!)
152
152
  multiplex_transaction_name(names) if names.size > 1
153
153
 
154
154
  [:Operations, names.join(', ')]
@@ -3,20 +3,18 @@
3
3
  module GraphQL
4
4
  module Tracing
5
5
  module DataDogTrace
6
+ # @param tracer [#trace] Deprecated
6
7
  # @param analytics_enabled [Boolean] Deprecated
7
8
  # @param analytics_sample_rate [Float] Deprecated
8
- def initialize(tracer: nil, analytics_enabled: false, analytics_sample_rate: 1.0, service: "ruby-graphql", **rest)
9
+ def initialize(tracer: nil, analytics_enabled: false, analytics_sample_rate: 1.0, service: nil, **rest)
9
10
  if tracer.nil?
10
11
  tracer = defined?(Datadog::Tracing) ? Datadog::Tracing : Datadog.tracer
11
12
  end
12
13
  @tracer = tracer
13
14
 
14
- analytics_available = defined?(Datadog::Contrib::Analytics) \
15
- && Datadog::Contrib::Analytics.respond_to?(:enabled?) \
16
- && Datadog::Contrib::Analytics.respond_to?(:set_sample_rate)
17
-
18
- @analytics_enabled = analytics_available && Datadog::Contrib::Analytics.enabled?(analytics_enabled)
15
+ @analytics_enabled = analytics_enabled
19
16
  @analytics_sample_rate = analytics_sample_rate
17
+
20
18
  @service_name = service
21
19
  @has_prepare_span = respond_to?(:prepare_span)
22
20
  super
@@ -34,12 +32,9 @@ module GraphQL
34
32
  }.each do |trace_method, trace_key|
35
33
  module_eval <<-RUBY, __FILE__, __LINE__
36
34
  def #{trace_method}(**data)
37
- @tracer.trace("#{trace_key}", service: @service_name) do |span|
38
- span.span_type = 'custom'
39
- if defined?(Datadog::Tracing::Metadata::Ext) # Introduced in ddtrace 1.0
40
- span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_COMPONENT, 'graphql')
41
- span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_OPERATION, '#{trace_method}')
42
- end
35
+ @tracer.trace("#{trace_key}", service: @service_name, type: 'custom') do |span|
36
+ span.set_tag('component', 'graphql')
37
+ span.set_tag('operation', '#{trace_method}')
43
38
 
44
39
  #{
45
40
  if trace_method == 'execute_multiplex'
@@ -54,10 +49,8 @@ module GraphQL
54
49
  end
55
50
  span.resource = resource if resource
56
51
 
57
- # For top span of query, set the analytics sample rate tag, if available.
58
- if @analytics_enabled
59
- Datadog::Contrib::Analytics.set_sample_rate(span, @analytics_sample_rate)
60
- end
52
+ # [Deprecated] will be removed in the future
53
+ span.set_metric('_dd1.sr.eausr', @analytics_sample_rate) if @analytics_enabled
61
54
  RUBY
62
55
  elsif trace_method == 'execute_query'
63
56
  <<-RUBY
@@ -89,12 +82,10 @@ module GraphQL
89
82
  nil
90
83
  end
91
84
  if platform_key && trace_field
92
- @tracer.trace(platform_key, service: @service_name) do |span|
93
- span.span_type = 'custom'
94
- if defined?(Datadog::Tracing::Metadata::Ext) # Introduced in ddtrace 1.0
95
- span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_COMPONENT, 'graphql')
96
- span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_OPERATION, span_key)
97
- end
85
+ @tracer.trace(platform_key, service: @service_name, type: 'custom') do |span|
86
+ span.set_tag('component', 'graphql')
87
+ span.set_tag('operation', span_key)
88
+
98
89
  if @has_prepare_span
99
90
  prepare_span_data = { query: query, field: field, ast_node: ast_node, arguments: arguments, object: object }
100
91
  prepare_span(span_key, prepare_span_data, span)
@@ -125,12 +116,10 @@ module GraphQL
125
116
 
126
117
  def authorized_span(span_key, object, type, query)
127
118
  platform_key = @platform_key_cache[DataDogTrace].platform_authorized_key_cache[type]
128
- @tracer.trace(platform_key, service: @service_name) do |span|
129
- span.span_type = 'custom'
130
- if defined?(Datadog::Tracing::Metadata::Ext) # Introduced in ddtrace 1.0
131
- span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_COMPONENT, 'graphql')
132
- span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_OPERATION, span_key)
133
- end
119
+ @tracer.trace(platform_key, service: @service_name, type: 'custom') do |span|
120
+ span.set_tag('component', 'graphql')
121
+ span.set_tag('operation', span_key)
122
+
134
123
  if @has_prepare_span
135
124
  prepare_span(span_key, {object: object, type: type, query: query}, span)
136
125
  end
@@ -158,12 +147,10 @@ module GraphQL
158
147
 
159
148
  def resolve_type_span(span_key, object, type, query)
160
149
  platform_key = @platform_key_cache[DataDogTrace].platform_resolve_type_key_cache[type]
161
- @tracer.trace(platform_key, service: @service_name) do |span|
162
- span.span_type = 'custom'
163
- if defined?(Datadog::Tracing::Metadata::Ext) # Introduced in ddtrace 1.0
164
- span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_COMPONENT, 'graphql')
165
- span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_OPERATION, span_key)
166
- end
150
+ @tracer.trace(platform_key, service: @service_name, type: 'custom') do |span|
151
+ span.set_tag('component', 'graphql')
152
+ span.set_tag('operation', span_key)
153
+
167
154
  if @has_prepare_span
168
155
  prepare_span(span_key, {object: object, type: type, query: query}, span)
169
156
  end
@@ -15,12 +15,9 @@ module GraphQL
15
15
  }
16
16
 
17
17
  def platform_trace(platform_key, key, data)
18
- tracer.trace(platform_key, service: service_name) do |span|
19
- span.span_type = 'custom'
20
- if defined?(Datadog::Tracing::Metadata::Ext) # Introduced in ddtrace 1.0
21
- span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_COMPONENT, 'graphql')
22
- span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_OPERATION, key)
23
- end
18
+ tracer.trace(platform_key, service: options[:service], type: 'custom') do |span|
19
+ span.set_tag('component', 'graphql')
20
+ span.set_tag('operation', key)
24
21
 
25
22
  if key == 'execute_multiplex'
26
23
  operations = data[:multiplex].queries.map(&:selected_operation_name).join(', ')
@@ -33,10 +30,8 @@ module GraphQL
33
30
  end
34
31
  span.resource = resource if resource
35
32
 
36
- # For top span of query, set the analytics sample rate tag, if available.
37
- if analytics_enabled?
38
- Datadog::Contrib::Analytics.set_sample_rate(span, analytics_sample_rate)
39
- end
33
+ # [Deprecated] will be removed in the future
34
+ span.set_metric('_dd1.sr.eausr', analytics_sample_rate) if analytics_enabled?
40
35
  end
41
36
 
42
37
  if key == 'execute_query'
@@ -51,10 +46,6 @@ module GraphQL
51
46
  end
52
47
  end
53
48
 
54
- def service_name
55
- options.fetch(:service, 'ruby-graphql')
56
- end
57
-
58
49
  # Implement this method in a subclass to apply custom tags to datadog spans
59
50
  # @param key [String] The event being traced
60
51
  # @param data [Hash] The runtime data for this event (@see GraphQL::Tracing for keys for each event)
@@ -65,18 +56,13 @@ module GraphQL
65
56
  def tracer
66
57
  default_tracer = defined?(Datadog::Tracing) ? Datadog::Tracing : Datadog.tracer
67
58
 
59
+ # [Deprecated] options[:tracer] will be removed in the future
68
60
  options.fetch(:tracer, default_tracer)
69
61
  end
70
62
 
71
- def analytics_available?
72
- defined?(Datadog::Contrib::Analytics) \
73
- && Datadog::Contrib::Analytics.respond_to?(:enabled?) \
74
- && Datadog::Contrib::Analytics.respond_to?(:set_sample_rate)
75
- end
76
-
77
63
  def analytics_enabled?
78
64
  # [Deprecated] options[:analytics_enabled] will be removed in the future
79
- analytics_available? && Datadog::Contrib::Analytics.enabled?(options.fetch(:analytics_enabled, false))
65
+ options.fetch(:analytics_enabled, false)
80
66
  end
81
67
 
82
68
  def analytics_sample_rate
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Tracing
4
+ module LegacyHooksTrace
5
+ def execute_multiplex(multiplex:)
6
+ multiplex_instrumenters = multiplex.schema.instrumenters[:multiplex]
7
+ query_instrumenters = multiplex.schema.instrumenters[:query]
8
+ # First, run multiplex instrumentation, then query instrumentation for each query
9
+ RunHooks.call_hooks(multiplex_instrumenters, multiplex, :before_multiplex, :after_multiplex) do
10
+ RunHooks.each_query_call_hooks(query_instrumenters, multiplex.queries) do
11
+ super
12
+ end
13
+ end
14
+ end
15
+
16
+ module RunHooks
17
+ module_function
18
+ # Call the before_ hooks of each query,
19
+ # Then yield if no errors.
20
+ # `call_hooks` takes care of appropriate cleanup.
21
+ def each_query_call_hooks(instrumenters, queries, i = 0)
22
+ if i >= queries.length
23
+ yield
24
+ else
25
+ query = queries[i]
26
+ call_hooks(instrumenters, query, :before_query, :after_query) {
27
+ each_query_call_hooks(instrumenters, queries, i + 1) {
28
+ yield
29
+ }
30
+ }
31
+ end
32
+ end
33
+
34
+ # Call each before hook, and if they all succeed, yield.
35
+ # If they don't all succeed, call after_ for each one that succeeded.
36
+ def call_hooks(instrumenters, object, before_hook_name, after_hook_name)
37
+ begin
38
+ successful = []
39
+ instrumenters.each do |instrumenter|
40
+ instrumenter.public_send(before_hook_name, object)
41
+ successful << instrumenter
42
+ end
43
+
44
+ # if any before hooks raise an exception, quit calling before hooks,
45
+ # but call the after hooks on anything that succeeded but also
46
+ # raise the exception that came from the before hook.
47
+ rescue GraphQL::ExecutionError => err
48
+ object.context.errors << err
49
+ rescue => e
50
+ raise call_after_hooks(successful, object, after_hook_name, e)
51
+ end
52
+
53
+ begin
54
+ yield # Call the user code
55
+ ensure
56
+ ex = call_after_hooks(successful, object, after_hook_name, nil)
57
+ raise ex if ex
58
+ end
59
+ end
60
+
61
+ def call_after_hooks(instrumenters, object, after_hook_name, ex)
62
+ instrumenters.reverse_each do |instrumenter|
63
+ begin
64
+ instrumenter.public_send(after_hook_name, object)
65
+ rescue => e
66
+ ex = e
67
+ end
68
+ end
69
+ ex
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -81,8 +81,10 @@ module GraphQL
81
81
  trace_name = tracing_name.sub("Tracing", "Trace")
82
82
  if GraphQL::Tracing.const_defined?(trace_name, false)
83
83
  trace_module = GraphQL::Tracing.const_get(trace_name)
84
+ warn("`use(#{self.name})` is deprecated, use the equivalent `trace_with(#{trace_module.name})` instead. More info: https://graphql-ruby.org/queries/tracing.html")
84
85
  schema_defn.trace_with(trace_module, **options)
85
86
  else
87
+ warn("`use(#{self.name})` and `Tracing::PlatformTracing` are deprecated. Use a `trace_with(...)` module instead. More info: https://graphql-ruby.org/queries/tracing.html. Please open an issue on the GraphQL-Ruby repo if you want to discuss further!")
86
88
  tracer = self.new(**options)
87
89
  schema_defn.tracer(tracer)
88
90
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module GraphQL
4
4
  module Tracing
5
- class PrometheusTracing < PlatformTracing
5
+ module PrometheusTrace
6
6
  class GraphQLCollector < ::PrometheusExporter::Server::TypeCollector
7
7
  def initialize
8
8
  @graphql_gauge = PrometheusExporter::Metric::Base.default_aggregation.new(
@@ -28,5 +28,7 @@ module GraphQL
28
28
  end
29
29
  end
30
30
  end
31
+ # Backwards-compat:
32
+ PrometheusTracing::GraphQLCollector = PrometheusTrace::GraphQLCollector
31
33
  end
32
34
  end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ module SentryTrace
6
+ include PlatformTrace
7
+
8
+ {
9
+ "lex" => "graphql.lex",
10
+ "parse" => "graphql.parse",
11
+ "validate" => "graphql.validate",
12
+ "analyze_query" => "graphql.analyze",
13
+ "analyze_multiplex" => "graphql.analyze_multiplex",
14
+ "execute_multiplex" => "graphql.execute_multiplex",
15
+ "execute_query" => "graphql.execute",
16
+ "execute_query_lazy" => "graphql.execute"
17
+ }.each do |trace_method, platform_key|
18
+ module_eval <<-RUBY, __FILE__, __LINE__
19
+ def #{trace_method}(**data, &block)
20
+ instrument_execution("#{platform_key}", "#{trace_method}", data, &block)
21
+ end
22
+ RUBY
23
+ end
24
+
25
+ def platform_execute_field(platform_key, &block)
26
+ instrument_execution(platform_key, "execute_field", &block)
27
+ end
28
+
29
+ def platform_execute_field_lazy(platform_key, &block)
30
+ instrument_execution(platform_key, "execute_field_lazy", &block)
31
+ end
32
+
33
+ def platform_authorized(platform_key, &block)
34
+ instrument_execution(platform_key, "authorized", &block)
35
+ end
36
+
37
+ def platform_authorized_lazy(platform_key, &block)
38
+ instrument_execution(platform_key, "authorized_lazy", &block)
39
+ end
40
+
41
+ def platform_resolve_type(platform_key, &block)
42
+ instrument_execution(platform_key, "resolve_type", &block)
43
+ end
44
+
45
+ def platform_resolve_type_lazy(platform_key, &block)
46
+ instrument_execution(platform_key, "resolve_type_lazy", &block)
47
+ end
48
+
49
+ def platform_field_key(field)
50
+ "graphql.field.#{field.path}"
51
+ end
52
+
53
+ def platform_authorized_key(type)
54
+ "graphql.authorized.#{type.graphql_name}"
55
+ end
56
+
57
+ def platform_resolve_type_key(type)
58
+ "graphql.resolve_type.#{type.graphql_name}"
59
+ end
60
+
61
+ private
62
+
63
+ def instrument_execution(platform_key, trace_method, data=nil, &block)
64
+ return yield unless Sentry.initialized?
65
+
66
+ Sentry.with_child_span(op: platform_key, start_timestamp: Sentry.utc_now.to_f) do |span|
67
+ result = block.call
68
+ span.finish
69
+
70
+ if trace_method == "execute_multiplex" && data.key?(:multiplex)
71
+ operation_names = data[:multiplex].queries.map{|q| operation_name(q) }
72
+ span.set_description(operation_names.join(", "))
73
+ elsif trace_method == "execute_query" && data.key?(:query)
74
+ span.set_description(operation_name(data[:query]))
75
+ span.set_data('graphql.document', data[:query].query_string)
76
+ span.set_data('graphql.operation.name', data[:query].selected_operation_name) if data[:query].selected_operation_name
77
+ span.set_data('graphql.operation.type', data[:query].selected_operation.operation_type)
78
+ end
79
+
80
+ result
81
+ end
82
+ end
83
+
84
+ def operation_name(query)
85
+ selected_op = query.selected_operation
86
+ if selected_op
87
+ [selected_op.operation_type, selected_op.name].compact.join(' ')
88
+ else
89
+ 'GraphQL Operation'
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -15,6 +15,7 @@ module GraphQL
15
15
  @query = query
16
16
  end
17
17
 
18
+ # The Ruby parser doesn't call this method (`graphql/c_parser` does.)
18
19
  def lex(query_string:)
19
20
  yield
20
21
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql/tracing/trace"
3
3
  require "graphql/tracing/legacy_trace"
4
+ require "graphql/tracing/legacy_hooks_trace"
4
5
 
5
6
  # Legacy tracing:
6
7
  require "graphql/tracing/active_support_notifications_tracing"
@@ -21,11 +22,12 @@ require "graphql/tracing/appsignal_trace"
21
22
  require "graphql/tracing/data_dog_trace"
22
23
  require "graphql/tracing/new_relic_trace"
23
24
  require "graphql/tracing/notifications_trace"
25
+ require "graphql/tracing/sentry_trace"
24
26
  require "graphql/tracing/scout_trace"
25
27
  require "graphql/tracing/statsd_trace"
26
28
  require "graphql/tracing/prometheus_trace"
27
29
  if defined?(PrometheusExporter::Server)
28
- require "graphql/tracing/prometheus_tracing/graphql_collector"
30
+ require "graphql/tracing/prometheus_trace/graphql_collector"
29
31
  end
30
32
 
31
33
  module GraphQL