graphql 2.2.5 → 2.2.11

Sign up to get free protection for your applications and to get access to all the features.
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