graphql 2.2.5 → 2.2.11

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.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/ast/field_usage.rb +32 -7
  3. data/lib/graphql/analysis/ast.rb +7 -1
  4. data/lib/graphql/coercion_error.rb +1 -9
  5. data/lib/graphql/dataloader/request.rb +5 -0
  6. data/lib/graphql/execution/interpreter/runtime.rb +9 -0
  7. data/lib/graphql/execution/interpreter.rb +90 -150
  8. data/lib/graphql/introspection/entry_points.rb +9 -3
  9. data/lib/graphql/introspection/schema_type.rb +3 -1
  10. data/lib/graphql/language/document_from_schema_definition.rb +1 -2
  11. data/lib/graphql/language/nodes.rb +1 -1
  12. data/lib/graphql/language/parser.rb +11 -1
  13. data/lib/graphql/language/printer.rb +4 -0
  14. data/lib/graphql/pagination/array_connection.rb +3 -3
  15. data/lib/graphql/pagination/relation_connection.rb +3 -3
  16. data/lib/graphql/query/validation_pipeline.rb +2 -2
  17. data/lib/graphql/query/variables.rb +3 -3
  18. data/lib/graphql/query.rb +1 -1
  19. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  20. data/lib/graphql/schema/field.rb +31 -30
  21. data/lib/graphql/schema/interface.rb +5 -1
  22. data/lib/graphql/schema/resolver.rb +9 -5
  23. data/lib/graphql/schema/unique_within_type.rb +1 -1
  24. data/lib/graphql/schema.rb +70 -17
  25. data/lib/graphql/static_validation/literal_validator.rb +1 -2
  26. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
  27. data/lib/graphql/static_validation/validator.rb +3 -0
  28. data/lib/graphql/subscriptions/serialize.rb +2 -0
  29. data/lib/graphql/subscriptions.rb +0 -3
  30. data/lib/graphql/testing/helpers.rb +8 -4
  31. data/lib/graphql/tracing/data_dog_trace.rb +21 -34
  32. data/lib/graphql/tracing/data_dog_tracing.rb +7 -21
  33. data/lib/graphql/tracing/legacy_hooks_trace.rb +74 -0
  34. data/lib/graphql/tracing/platform_tracing.rb +2 -0
  35. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +3 -1
  36. data/lib/graphql/tracing/sentry_trace.rb +112 -0
  37. data/lib/graphql/tracing.rb +3 -1
  38. data/lib/graphql/version.rb +1 -1
  39. data/lib/graphql.rb +3 -2
  40. metadata +24 -23
  41. data/lib/graphql/schema/base_64_bp.rb +0 -26
  42. data/lib/graphql/subscriptions/instrumentation.rb +0 -28
@@ -483,41 +483,24 @@ module GraphQL
483
483
  metadata_complexity = 0
484
484
  lookahead = GraphQL::Execution::Lookahead.new(query: query, field: self, ast_nodes: nodes, owner_type: owner)
485
485
 
486
- if (page_info_lookahead = lookahead.selection(:page_info)).selected?
487
- metadata_complexity += 1 # pageInfo
488
- metadata_complexity += page_info_lookahead.selections.size # subfields
489
- end
490
-
491
- if lookahead.selects?(:total) || lookahead.selects?(:total_count) || lookahead.selects?(:count)
492
- metadata_complexity += 1
486
+ lookahead.selections.each do |next_lookahead|
487
+ # this includes `pageInfo`, `nodes` and `edges` and any custom fields
488
+ # TODO this doesn't support procs yet -- unlikely to need it.
489
+ metadata_complexity += next_lookahead.field.complexity
490
+ if next_lookahead.name != :nodes && next_lookahead.name != :edges
491
+ # subfields, eg, for pageInfo -- assumes no subselections
492
+ metadata_complexity += next_lookahead.selections.size
493
+ end
493
494
  end
494
495
 
495
- nodes_edges_complexity = 0
496
- nodes_edges_complexity += 1 if lookahead.selects?(:edges)
497
- nodes_edges_complexity += 1 if lookahead.selects?(:nodes)
498
-
499
496
  # Possible bug: selections on `edges` and `nodes` are _both_ multiplied here. Should they be?
500
- items_complexity = child_complexity - metadata_complexity - nodes_edges_complexity
501
- # Add 1 for _this_ field
502
- 1 + (max_possible_page_size * items_complexity) + metadata_complexity + nodes_edges_complexity
497
+ items_complexity = child_complexity - metadata_complexity
498
+ subfields_complexity = (max_possible_page_size * items_complexity) + metadata_complexity
499
+ # Apply this field's own complexity
500
+ apply_own_complexity_to(subfields_complexity, query, nodes)
503
501
  end
504
502
  else
505
- defined_complexity = complexity
506
- case defined_complexity
507
- when Proc
508
- arguments = query.arguments_for(nodes.first, self)
509
- if arguments.is_a?(GraphQL::ExecutionError)
510
- return child_complexity
511
- elsif arguments.respond_to?(:keyword_arguments)
512
- arguments = arguments.keyword_arguments
513
- end
514
-
515
- defined_complexity.call(query.context, arguments, child_complexity)
516
- when Numeric
517
- defined_complexity + child_complexity
518
- else
519
- raise("Invalid complexity: #{defined_complexity.inspect} on #{path} (#{inspect})")
520
- end
503
+ apply_own_complexity_to(child_complexity, query, nodes)
521
504
  end
522
505
  end
523
506
 
@@ -882,6 +865,24 @@ ERR
882
865
  yield(obj, args)
883
866
  end
884
867
  end
868
+
869
+ def apply_own_complexity_to(child_complexity, query, nodes)
870
+ case (own_complexity = complexity)
871
+ when Numeric
872
+ own_complexity + child_complexity
873
+ when Proc
874
+ arguments = query.arguments_for(nodes.first, self)
875
+ if arguments.is_a?(GraphQL::ExecutionError)
876
+ return child_complexity
877
+ elsif arguments.respond_to?(:keyword_arguments)
878
+ arguments = arguments.keyword_arguments
879
+ end
880
+
881
+ own_complexity.call(query.context, arguments, child_complexity)
882
+ else
883
+ raise ArgumentError, "Invalid complexity for #{self.path}: #{own_complexity.inspect}"
884
+ end
885
+ end
885
886
  end
886
887
  end
887
888
  end
@@ -69,7 +69,11 @@ module GraphQL
69
69
  end
70
70
  elsif child_class < GraphQL::Schema::Object
71
71
  # This is being included into an object type, make sure it's using `implements(...)`
72
- backtrace_line = caller(0, 10).find { |line| line.include?("schema/member/has_interfaces.rb") && line.include?("in `implements'")}
72
+ backtrace_line = caller_locations(0, 10).find do |location|
73
+ location.base_label == "implements" &&
74
+ location.path.end_with?("schema/member/has_interfaces.rb")
75
+ end
76
+
73
77
  if !backtrace_line
74
78
  raise "Attach interfaces using `implements(#{self})`, not `include(#{self})`"
75
79
  end
@@ -166,11 +166,15 @@ module GraphQL
166
166
  args.each_value do |argument|
167
167
  arg_keyword = argument.keyword
168
168
  if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
169
- arg_auth, err = argument.authorized?(self, arg_value, context)
170
- if !arg_auth
171
- return arg_auth, err
172
- else
173
- true
169
+ auth_result = argument.authorized?(self, arg_value, context)
170
+ if auth_result.is_a?(Array)
171
+ # only return this second value if the application returned a second value
172
+ arg_auth, err = auth_result
173
+ if !arg_auth
174
+ return arg_auth, err
175
+ end
176
+ elsif auth_result == false
177
+ return auth_result
174
178
  end
175
179
  else
176
180
  true
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- require 'graphql/schema/base_64_bp'
2
+ require "base64"
3
3
 
4
4
  module GraphQL
5
5
  class Schema
@@ -167,13 +167,20 @@ module GraphQL
167
167
  backtrace_class.include(GraphQL::Backtrace::Trace)
168
168
  trace_mode(:default_backtrace, backtrace_class)
169
169
  end
170
- trace_class_for(:default)
170
+ trace_class_for(:default, build: true)
171
171
  end
172
172
 
173
173
  # @return [Class] Return the trace class to use for this mode, looking one up on the superclass if this Schema doesn't have one defined.
174
- def trace_class_for(mode, build: true)
175
- own_trace_modes[mode] ||
176
- (superclass.respond_to?(:trace_class_for) ? superclass.trace_class_for(mode, build: build) : (build ? (own_trace_modes[mode] = build_trace_mode(mode)) : nil))
174
+ def trace_class_for(mode, build: false)
175
+ if (trace_class = own_trace_modes[mode])
176
+ trace_class
177
+ elsif superclass.respond_to?(:trace_class_for) && (trace_class = superclass.trace_class_for(mode, build: false))
178
+ trace_class
179
+ elsif build
180
+ own_trace_modes[mode] = build_trace_mode(mode)
181
+ else
182
+ nil
183
+ end
177
184
  end
178
185
 
179
186
  # Configure `trace_class` to be used whenever `context: { trace_mode: mode_name }` is requested.
@@ -211,20 +218,24 @@ module GraphQL
211
218
  include DefaultTraceClass
212
219
  end
213
220
  when :default_backtrace
214
- schema_base_class = trace_class_for(:default)
221
+ schema_base_class = trace_class_for(:default, build: true)
215
222
  Class.new(schema_base_class) do
216
223
  include(GraphQL::Backtrace::Trace)
217
224
  end
218
225
  else
219
226
  # First, see if the superclass has a custom-defined class for this.
220
227
  # Then, if it doesn't, use this class's default trace
221
- base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode, build: false)) || trace_class_for(:default)
228
+ base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode)) || trace_class_for(:default, build: true)
222
229
  # Prepare the default trace class if it hasn't been initialized yet
223
230
  base_class ||= (own_trace_modes[:default] = build_trace_mode(:default))
224
231
  mods = trace_modules_for(mode)
225
232
  if base_class < DefaultTraceClass
226
233
  mods = trace_modules_for(:default) + mods
227
234
  end
235
+ # Copy the existing default options into this mode's options
236
+ default_options = trace_options_for(:default)
237
+ add_trace_options_for(mode, default_options)
238
+
228
239
  Class.new(base_class) do
229
240
  mods.any? && include(*mods)
230
241
  end
@@ -743,6 +754,7 @@ module GraphQL
743
754
 
744
755
  def error_bubbling(new_error_bubbling = nil)
745
756
  if !new_error_bubbling.nil?
757
+ warn("error_bubbling(#{new_error_bubbling.inspect}) is deprecated; the default value of `false` will be the only option in GraphQL-Ruby 3.0")
746
758
  @error_bubbling = new_error_bubbling
747
759
  else
748
760
  @error_bubbling.nil? ? find_inherited_value(:error_bubbling) : @error_bubbling
@@ -814,6 +826,26 @@ module GraphQL
814
826
  end
815
827
  end
816
828
 
829
+ # @param new_extra_types [Module] Type definitions to include in printing and introspection, even though they aren't referenced in the schema
830
+ # @return [Array<Module>] Type definitions added to this schema
831
+ def extra_types(*new_extra_types)
832
+ if new_extra_types.any?
833
+ new_extra_types = new_extra_types.flatten
834
+ @own_extra_types ||= []
835
+ @own_extra_types.concat(new_extra_types)
836
+ end
837
+ inherited_et = find_inherited_value(:extra_types, nil)
838
+ if inherited_et
839
+ if @own_extra_types
840
+ inherited_et + @own_extra_types
841
+ else
842
+ inherited_et
843
+ end
844
+ else
845
+ @own_extra_types || EMPTY_ARRAY
846
+ end
847
+ end
848
+
817
849
  def orphan_types(*new_orphan_types)
818
850
  if new_orphan_types.any?
819
851
  new_orphan_types = new_orphan_types.flatten
@@ -1044,6 +1076,12 @@ module GraphQL
1044
1076
  end
1045
1077
 
1046
1078
  def instrument(instrument_step, instrumenter, options = {})
1079
+ warn <<~WARN
1080
+ Schema.instrument is deprecated, use `trace_with` instead: https://graphql-ruby.org/queries/tracing.html"
1081
+ (From `#{self}.instrument(#{instrument_step}, #{instrumenter})` at #{caller(1, 1).first})
1082
+
1083
+ WARN
1084
+ trace_with(Tracing::LegacyHooksTrace)
1047
1085
  own_instrumenters[instrument_step] << instrumenter
1048
1086
  end
1049
1087
 
@@ -1080,7 +1118,7 @@ module GraphQL
1080
1118
  end
1081
1119
 
1082
1120
  def tracer(new_tracer)
1083
- default_trace = trace_class_for(:default)
1121
+ default_trace = trace_class_for(:default, build: true)
1084
1122
  if default_trace.nil? || !(default_trace < GraphQL::Tracing::CallLegacyTracers)
1085
1123
  trace_with(GraphQL::Tracing::CallLegacyTracers)
1086
1124
  end
@@ -1106,20 +1144,23 @@ module GraphQL
1106
1144
  tc = own_trace_modes[mode] ||= build_trace_mode(mode)
1107
1145
  tc.include(trace_mod)
1108
1146
  own_trace_modules[mode] << trace_mod
1109
-
1147
+ add_trace_options_for(mode, options)
1110
1148
  if mode == :default
1111
1149
  # This module is being added as a default tracer. If any other mode classes
1112
1150
  # have already been created, but get their default behavior from a superclass,
1113
1151
  # Then mix this into this schema's subclass.
1114
1152
  # (But don't mix it into mode classes that aren't default-based.)
1115
1153
  own_trace_modes.each do |other_mode_name, other_mode_class|
1116
- if other_mode_class < DefaultTraceClass && !(other_mode_class < trace_mod)
1117
- other_mode_class.include(trace_mod)
1154
+ if other_mode_class < DefaultTraceClass
1155
+ # Don't add it back to the inheritance tree if it's already there
1156
+ if !(other_mode_class < trace_mod)
1157
+ other_mode_class.include(trace_mod)
1158
+ end
1159
+ # Add any options so they'll be available
1160
+ add_trace_options_for(other_mode_name, options)
1118
1161
  end
1119
1162
  end
1120
1163
  end
1121
- t_opts = trace_options_for(mode)
1122
- t_opts.merge!(options)
1123
1164
  end
1124
1165
  nil
1125
1166
  end
@@ -1129,10 +1170,14 @@ module GraphQL
1129
1170
  def trace_options_for(mode)
1130
1171
  @trace_options_for_mode ||= {}
1131
1172
  @trace_options_for_mode[mode] ||= begin
1173
+ # It may be time to create an options hash for a mode that wasn't registered yet.
1174
+ # Mix in the default options in that case.
1175
+ default_options = mode == :default ? EMPTY_HASH : trace_options_for(:default)
1176
+ # Make sure this returns a new object so that other hashes aren't modified later
1132
1177
  if superclass.respond_to?(:trace_options_for)
1133
- superclass.trace_options_for(mode).dup
1178
+ superclass.trace_options_for(mode).merge(default_options)
1134
1179
  else
1135
- {}
1180
+ default_options.dup
1136
1181
  end
1137
1182
  end
1138
1183
  end
@@ -1155,15 +1200,17 @@ module GraphQL
1155
1200
  raise ArgumentError, "Can't use `context[:backtrace]` with a custom default trace mode (`#{dm.inspect}`)"
1156
1201
  else
1157
1202
  own_trace_modes[:default_backtrace] ||= build_trace_mode(:default_backtrace)
1203
+ options_trace_mode = :default
1158
1204
  :default_backtrace
1159
1205
  end
1160
1206
  else
1161
1207
  default_trace_mode
1162
1208
  end
1163
1209
 
1164
- base_trace_options = trace_options_for(trace_mode)
1210
+ options_trace_mode ||= trace_mode
1211
+ base_trace_options = trace_options_for(options_trace_mode)
1165
1212
  trace_options = base_trace_options.merge(options)
1166
- trace_class_for_mode = trace_class_for(trace_mode) || raise(ArgumentError, "#{self} has no trace class for mode: #{trace_mode.inspect}")
1213
+ trace_class_for_mode = trace_class_for(trace_mode, build: true)
1167
1214
  trace_class_for_mode.new(**trace_options)
1168
1215
  end
1169
1216
 
@@ -1317,6 +1364,12 @@ module GraphQL
1317
1364
 
1318
1365
  private
1319
1366
 
1367
+ def add_trace_options_for(mode, new_options)
1368
+ t_opts = trace_options_for(mode)
1369
+ t_opts.merge!(new_options)
1370
+ nil
1371
+ end
1372
+
1320
1373
  # @param t [Module, Array<Module>]
1321
1374
  # @return [void]
1322
1375
  def add_type_and_traverse(t, root:)
@@ -1378,7 +1431,7 @@ module GraphQL
1378
1431
  else
1379
1432
  @lazy_methods = GraphQL::Execution::Lazy::LazyMethodMap.new
1380
1433
  @lazy_methods.set(GraphQL::Execution::Lazy, :value)
1381
- @lazy_methods.set(GraphQL::Dataloader::Request, :load)
1434
+ @lazy_methods.set(GraphQL::Dataloader::Request, :load_with_deprecation_warning)
1382
1435
  end
1383
1436
  end
1384
1437
  @lazy_methods
@@ -110,7 +110,7 @@ module GraphQL
110
110
  # TODO - would be nice to use these to create an error message so the caller knows
111
111
  # that required fields are missing
112
112
  required_field_names = @warden.arguments(type)
113
- .select { |argument| argument.type.kind.non_null? && @warden.get_argument(type, argument.name) }
113
+ .select { |argument| argument.type.kind.non_null? && !argument.default_value? }
114
114
  .map!(&:name)
115
115
 
116
116
  present_field_names = ast_node.arguments.map(&:name)
@@ -122,7 +122,6 @@ module GraphQL
122
122
  arg_type = @warden.get_argument(type, name).type
123
123
  recursively_validate(GraphQL::Language::Nodes::NullValue.new(name: name), arg_type)
124
124
  end
125
-
126
125
  if type.one_of? && ast_node.arguments.size != 1
127
126
  results << Query::InputValidationResult.from_problem("`#{type.graphql_name}` is a OneOf type, so only one argument may be given (instead of #{ast_node.arguments.size})")
128
127
  end
@@ -35,7 +35,7 @@ module GraphQL
35
35
  return unless parent_type && parent_type.kind.input_object?
36
36
 
37
37
  required_fields = context.warden.arguments(parent_type)
38
- .select{|arg| arg.type.kind.non_null?}
38
+ .select{ |arg| arg.type.kind.non_null? && !arg.default_value? }
39
39
  .map!(&:graphql_name)
40
40
 
41
41
  present_fields = ast_node.arguments.map(&:name)
@@ -28,6 +28,7 @@ module GraphQL
28
28
  # @return [Array<Hash>]
29
29
  def validate(query, validate: true, timeout: nil, max_errors: nil)
30
30
  query.current_trace.validate(validate: validate, query: query) do
31
+ begin_t = Time.now
31
32
  errors = if validate == false
32
33
  []
33
34
  else
@@ -52,11 +53,13 @@ module GraphQL
52
53
  end
53
54
 
54
55
  {
56
+ remaining_timeout: timeout ? (timeout - (Time.now - begin_t)) : nil,
55
57
  errors: errors,
56
58
  }
57
59
  end
58
60
  rescue GraphQL::ExecutionError => e
59
61
  {
62
+ remaining_timeout: nil,
60
63
  errors: [e],
61
64
  }
62
65
  end
@@ -148,6 +148,8 @@ module GraphQL
148
148
  { TIMESTAMP_KEY => [obj.class.name, obj.strftime(TIMESTAMP_FORMAT)] }
149
149
  elsif obj.is_a?(OpenStruct)
150
150
  { OPEN_STRUCT_KEY => dump_value(obj.to_h) }
151
+ elsif defined?(ActiveRecord::Relation) && obj.is_a?(ActiveRecord::Relation)
152
+ dump_value(obj.to_a)
151
153
  else
152
154
  obj
153
155
  end
@@ -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
@@ -5,10 +5,7 @@ module GraphQL
5
5
  # @param schema_class [Class<GraphQL::Schema>]
6
6
  # @return [Module] A helpers module which always uses the given schema
7
7
  def self.for(schema_class)
8
- Module.new do
9
- include SchemaHelpers
10
- @@schema_class_for_helpers = schema_class
11
- end
8
+ SchemaHelpers.for(schema_class)
12
9
  end
13
10
 
14
11
  class Error < GraphQL::Error
@@ -119,6 +116,13 @@ module GraphQL
119
116
  # schema will be added later
120
117
  super(nil, *args, **kwargs, &block)
121
118
  end
119
+
120
+ def self.for(schema_class)
121
+ Module.new do
122
+ include SchemaHelpers
123
+ @@schema_class_for_helpers = schema_class
124
+ end
125
+ end
122
126
  end
123
127
  end
124
128
  end
@@ -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