graphql 2.4.10 → 2.4.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  3. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  4. data/lib/graphql/dashboard/statics/dashboard.css +3 -0
  5. data/lib/graphql/dashboard/statics/dashboard.js +78 -0
  6. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  7. data/lib/graphql/dashboard/statics/icon.png +0 -0
  8. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  9. data/lib/graphql/dashboard/views/graphql/dashboard/traces/index.html.erb +63 -0
  10. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +60 -0
  11. data/lib/graphql/dashboard.rb +142 -0
  12. data/lib/graphql/execution/interpreter/runtime.rb +1 -1
  13. data/lib/graphql/invalid_name_error.rb +1 -1
  14. data/lib/graphql/invalid_null_error.rb +6 -12
  15. data/lib/graphql/schema/build_from_definition.rb +0 -1
  16. data/lib/graphql/schema/enum.rb +16 -1
  17. data/lib/graphql/schema/input_object.rb +1 -1
  18. data/lib/graphql/schema/loader.rb +0 -1
  19. data/lib/graphql/schema/member/has_dataloader.rb +4 -0
  20. data/lib/graphql/schema.rb +48 -6
  21. data/lib/graphql/tracing/active_support_notifications_trace.rb +6 -2
  22. data/lib/graphql/tracing/appoptics_trace.rb +2 -0
  23. data/lib/graphql/tracing/appsignal_trace.rb +6 -0
  24. data/lib/graphql/tracing/data_dog_trace.rb +5 -0
  25. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  26. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  27. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  28. data/lib/graphql/tracing/new_relic_trace.rb +9 -0
  29. data/lib/graphql/tracing/perfetto_trace.rb +13 -2
  30. data/lib/graphql/tracing/prometheus_trace.rb +22 -0
  31. data/lib/graphql/tracing/scout_trace.rb +6 -0
  32. data/lib/graphql/tracing/sentry_trace.rb +5 -0
  33. data/lib/graphql/tracing/statsd_trace.rb +9 -0
  34. data/lib/graphql/tracing.rb +1 -0
  35. data/lib/graphql/version.rb +1 -1
  36. data/lib/graphql.rb +3 -0
  37. metadata +15 -2
@@ -2,7 +2,7 @@
2
2
  module GraphQL
3
3
  # Raised automatically when a field's resolve function returns `nil`
4
4
  # for a non-null field.
5
- class InvalidNullError < GraphQL::RuntimeTypeError
5
+ class InvalidNullError < GraphQL::Error
6
6
  # @return [GraphQL::BaseType] The owner of {#field}
7
7
  attr_reader :parent_type
8
8
 
@@ -12,23 +12,17 @@ module GraphQL
12
12
  # @return [nil, GraphQL::ExecutionError] The invalid value for this field
13
13
  attr_reader :value
14
14
 
15
- def initialize(parent_type, field, value)
15
+ # @return [GraphQL::Language::Nodes::Field] the field where the error occurred
16
+ attr_reader :ast_node
17
+
18
+ def initialize(parent_type, field, value, ast_node)
16
19
  @parent_type = parent_type
17
20
  @field = field
18
21
  @value = value
22
+ @ast_node = ast_node
19
23
  super("Cannot return null for non-nullable field #{@parent_type.graphql_name}.#{@field.graphql_name}")
20
24
  end
21
25
 
22
- # @return [Hash] An entry for the response's "errors" key
23
- def to_h
24
- { "message" => message }
25
- end
26
-
27
- # @deprecated always false
28
- def parent_error?
29
- false
30
- end
31
-
32
26
  class << self
33
27
  attr_accessor :parent_class
34
28
 
@@ -298,7 +298,6 @@ module GraphQL
298
298
  description: enum_value_definition.description,
299
299
  directives: builder.prepare_directives(enum_value_definition, type_resolver),
300
300
  ast_node: enum_value_definition,
301
- value_method: GraphQL::Schema::Enum.respond_to?(enum_value_definition.name.downcase) ? false : nil,
302
301
  )
303
302
  end
304
303
  end
@@ -70,7 +70,9 @@ module GraphQL
70
70
  kwargs[:owner] = self
71
71
  value = enum_value_class.new(*args, **kwargs, &block)
72
72
 
73
- generate_value_method(value, value_method)
73
+ if value_method || (value_methods && value_method != false)
74
+ generate_value_method(value, value_method)
75
+ end
74
76
 
75
77
  key = value.graphql_name
76
78
  prev_value = own_values[key]
@@ -159,6 +161,18 @@ module GraphQL
159
161
  end
160
162
  end
161
163
 
164
+ def value_methods(new_value = NOT_CONFIGURED)
165
+ if NOT_CONFIGURED.equal?(new_value)
166
+ if @value_methods != nil
167
+ @value_methods
168
+ else
169
+ find_inherited_value(:value_methods, false)
170
+ end
171
+ else
172
+ @value_methods = new_value
173
+ end
174
+ end
175
+
162
176
  def kind
163
177
  GraphQL::TypeKinds::ENUM
164
178
  end
@@ -220,6 +234,7 @@ module GraphQL
220
234
  # because they would end up with names like `#<Class0x1234>::UnresolvedValueError` which messes up bug trackers
221
235
  child_class.const_set(:UnresolvedValueError, Class.new(Schema::Enum::UnresolvedValueError))
222
236
  end
237
+ child_class.class_eval { @value_methods = nil }
223
238
  super
224
239
  end
225
240
 
@@ -156,7 +156,7 @@ module GraphQL
156
156
  def #{method_name}
157
157
  self[#{method_name.inspect}]
158
158
  end
159
- alias_method :#{method_name}, :#{method_name}
159
+ alias_method #{method_name.inspect}, #{method_name.inspect}
160
160
  RUBY
161
161
  end
162
162
  argument_defn
@@ -108,7 +108,6 @@ module GraphQL
108
108
  enum_value["name"],
109
109
  description: enum_value["description"],
110
110
  deprecation_reason: enum_value["deprecationReason"],
111
- value_method: respond_to?(enum_value["name"].downcase) ? false : nil
112
111
  )
113
112
  end
114
113
  end
@@ -23,6 +23,10 @@ module GraphQL
23
23
  # @param find_by_value [Object] Usually an `id`, might be another value if `find_by:` is also provided
24
24
  # @param find_by [Symbol, String] A column name to look the record up by. (Defaults to the model's primary key.)
25
25
  # @return [ActiveRecord::Base, nil]
26
+ # @example Finding a record by ID
27
+ # dataload_record(Post, 5) # Like `Post.find(5)`, but dataloaded
28
+ # @example Finding a record by another attribute
29
+ # dataload_record(User, "matz", find_by: :handle) # Like `User.find_by(handle: "matz")`, but dataloaded
26
30
  def dataload_record(model, find_by_value, find_by: nil)
27
31
  source = if find_by
28
32
  dataloader.with(Dataloader::ActiveRecordSource, model, find_by: find_by)
@@ -1298,7 +1298,10 @@ module GraphQL
1298
1298
  def type_error(type_error, ctx)
1299
1299
  case type_error
1300
1300
  when GraphQL::InvalidNullError
1301
- ctx.errors << type_error
1301
+ execution_error = GraphQL::ExecutionError.new(type_error.message, ast_node: type_error.ast_node)
1302
+ execution_error.path = ctx[:current_path]
1303
+
1304
+ ctx.errors << execution_error
1302
1305
  when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
1303
1306
  raise type_error
1304
1307
  when GraphQL::IntegerDecodingError
@@ -1366,6 +1369,16 @@ module GraphQL
1366
1369
  }.freeze
1367
1370
  end
1368
1371
 
1372
+ # @return [GraphQL::Tracing::DetailedTrace] if it has been configured for this schema
1373
+ attr_accessor :detailed_trace
1374
+
1375
+ # @param query [GraphQL::Query, GraphQL::Execution::Multiplex] Called with a multiplex when multiple queries are executed at once (with {.multiplex})
1376
+ # @return [Boolean] When `true`, save a detailed trace for this query.
1377
+ # @see Tracing::DetailedTrace DetailedTrace saves traces when this method returns true
1378
+ def detailed_trace?(query)
1379
+ raise "#{self} must implement `def.detailed_trace?(query)` to use DetailedTrace. Implement this method in your schema definition."
1380
+ end
1381
+
1369
1382
  def tracer(new_tracer, silence_deprecation_warning: false)
1370
1383
  if !silence_deprecation_warning
1371
1384
  warn("`Schema.tracer(#{new_tracer.inspect})` is deprecated; use module-based `trace_with` instead. See: https://graphql-ruby.org/queries/tracing.html")
@@ -1383,14 +1396,22 @@ module GraphQL
1383
1396
  find_inherited_value(:tracers, EMPTY_ARRAY) + own_tracers
1384
1397
  end
1385
1398
 
1386
- # Mix `trace_mod` into this schema's `Trace` class so that its methods
1387
- # will be called at runtime.
1399
+ # Mix `trace_mod` into this schema's `Trace` class so that its methods will be called at runtime.
1400
+ #
1401
+ # You can attach a module to run in only _some_ circumstances by using `mode:`. When a module is added with `mode:`,
1402
+ # it will only run for queries with a matching `context[:trace_mode]`.
1403
+ #
1404
+ # Any custom trace modes _also_ include the default `trace_with ...` modules (that is, those added _without_ any particular `mode: ...` configuration).
1405
+ #
1406
+ # @example Adding a trace in a special mode
1407
+ # # only runs when `query.context[:trace_mode]` is `:special`
1408
+ # trace_with SpecialTrace, mode: :special
1388
1409
  #
1389
1410
  # @param trace_mod [Module] A module that implements tracing methods
1390
1411
  # @param mode [Symbol] Trace module will only be used for this trade mode
1391
1412
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1392
1413
  # @return [void]
1393
- # @see GraphQL::Tracing::Trace for available tracing methods
1414
+ # @see GraphQL::Tracing::Trace Tracing::Trace for available tracing methods
1394
1415
  def trace_with(trace_mod, mode: :default, **options)
1395
1416
  if mode.is_a?(Array)
1396
1417
  mode.each { |m| trace_with(trace_mod, mode: m, **options) }
@@ -1440,12 +1461,33 @@ module GraphQL
1440
1461
  #
1441
1462
  # If no `mode:` is given, then {default_trace_mode} will be used.
1442
1463
  #
1464
+ # If this schema is using {Tracing::DetailedTrace} and {.detailed_trace?} returns `true`, then
1465
+ # DetailedTrace's mode will override the passed-in `mode`.
1466
+ #
1443
1467
  # @param mode [Symbol] Trace modules for this trade mode will be included
1444
1468
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1445
1469
  # @return [Tracing::Trace]
1446
1470
  def new_trace(mode: nil, **options)
1447
- target = options[:query] || options[:multiplex]
1448
- mode ||= target && target.context[:trace_mode]
1471
+ should_sample = if detailed_trace
1472
+ if (query = options[:query])
1473
+ detailed_trace?(query)
1474
+ elsif (multiplex = options[:multiplex])
1475
+ if multiplex.queries.length == 1
1476
+ detailed_trace?(multiplex.queries.first)
1477
+ else
1478
+ detailed_trace?(multiplex)
1479
+ end
1480
+ end
1481
+ else
1482
+ false
1483
+ end
1484
+
1485
+ if should_sample
1486
+ mode = detailed_trace.trace_mode
1487
+ else
1488
+ target = options[:query] || options[:multiplex]
1489
+ mode ||= target && target.context[:trace_mode]
1490
+ end
1449
1491
 
1450
1492
  trace_mode = mode || default_trace_mode
1451
1493
  base_trace_options = trace_options_for(trace_mode)
@@ -4,8 +4,12 @@ require "graphql/tracing/notifications_trace"
4
4
 
5
5
  module GraphQL
6
6
  module Tracing
7
- # This implementation forwards events to ActiveSupport::Notifications
8
- # with a `graphql` suffix.
7
+ # This implementation forwards events to ActiveSupport::Notifications with a `graphql` suffix.
8
+ #
9
+ # @example Sending execution events to ActiveSupport::Notifications
10
+ # class MySchema < GraphQL::Schema
11
+ # trace_with(GraphQL::Tracing::ActiveSupportNotificationsTrace)
12
+ # end
9
13
  module ActiveSupportNotificationsTrace
10
14
  include NotificationsTrace
11
15
  def initialize(engine: ActiveSupport::Notifications, **rest)
@@ -22,10 +22,12 @@ module GraphQL
22
22
  # These GraphQL events will show up as 'graphql.execute' spans
23
23
  EXEC_KEYS = ['execute_multiplex', 'execute_query', 'execute_query_lazy'].freeze
24
24
 
25
+
25
26
  # During auto-instrumentation this version of AppOpticsTracing is compared
26
27
  # with the version provided in the appoptics_apm gem, so that the newer
27
28
  # version of the class can be used
28
29
 
30
+
29
31
  def self.version
30
32
  Gem::Version.new('1.0.0')
31
33
  end
@@ -4,6 +4,12 @@ require "graphql/tracing/platform_trace"
4
4
 
5
5
  module GraphQL
6
6
  module Tracing
7
+ # Instrumentation for reporting GraphQL-Ruby times to Appsignal.
8
+ #
9
+ # @example Installing the tracer
10
+ # class MySchema < GraphQL::Schema
11
+ # trace_with GraphQL::Tracing::AppsignalTrace
12
+ # end
7
13
  module AppsignalTrace
8
14
  include PlatformTrace
9
15
 
@@ -4,6 +4,11 @@ require "graphql/tracing/platform_trace"
4
4
 
5
5
  module GraphQL
6
6
  module Tracing
7
+ # A tracer for reporting to DataDog
8
+ # @example Adding this tracer to your schema
9
+ # class MySchema < GraphQL::Schema
10
+ # trace_with GraphQL::Tracing::DataDogTrace
11
+ # end
7
12
  module DataDogTrace
8
13
  # @param tracer [#trace] Deprecated
9
14
  # @param analytics_enabled [Boolean] Deprecated
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ class DetailedTrace
6
+ # An in-memory trace storage backend. Suitable for testing and development only.
7
+ # It won't work for multi-process deployments and everything is erased when the app is restarted.
8
+ class MemoryBackend
9
+ def initialize(limit: nil)
10
+ @limit = limit
11
+ @traces = {}
12
+ @next_id = 0
13
+ end
14
+
15
+ def traces(last:, before:)
16
+ page = []
17
+ @traces.values.reverse_each do |trace|
18
+ if page.size == last
19
+ break
20
+ elsif before.nil? || trace.begin_ms < before
21
+ page << trace
22
+ end
23
+ end
24
+ page
25
+ end
26
+
27
+ def find_trace(id)
28
+ @traces[id]
29
+ end
30
+
31
+ def delete_trace(id)
32
+ @traces.delete(id.to_i)
33
+ nil
34
+ end
35
+
36
+ def delete_all_traces
37
+ @traces.clear
38
+ nil
39
+ end
40
+
41
+ def save_trace(operation_name, duration, begin_ms, trace_data)
42
+ id = @next_id
43
+ @next_id += 1
44
+ @traces[id] = DetailedTrace::StoredTrace.new(
45
+ id: id,
46
+ operation_name: operation_name,
47
+ duration_ms: duration,
48
+ begin_ms: begin_ms,
49
+ trace_data: trace_data
50
+ )
51
+ if @limit && @traces.size > @limit
52
+ del_keys = @traces.keys[0...-@limit]
53
+ del_keys.each { |k| @traces.delete(k) }
54
+ end
55
+ id
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ class DetailedTrace
6
+ class RedisBackend
7
+ KEY_PREFIX = "gql:trace:"
8
+ def initialize(redis:, limit: nil)
9
+ @redis = redis
10
+ @key = KEY_PREFIX + "traces"
11
+ @remrangebyrank_limit = limit ? -limit - 1 : nil
12
+ end
13
+
14
+ def traces(last:, before:)
15
+ before = case before
16
+ when Numeric
17
+ "(#{before}"
18
+ when nil
19
+ "+inf"
20
+ end
21
+ str_pairs = @redis.zrange(@key, before, 0, byscore: true, rev: true, limit: [0, last || 100], withscores: true)
22
+ str_pairs.map do |(str_data, score)|
23
+ entry_to_trace(score, str_data)
24
+ end
25
+ end
26
+
27
+ def delete_trace(id)
28
+ @redis.zremrangebyscore(@key, id, id)
29
+ nil
30
+ end
31
+
32
+ def delete_all_traces
33
+ @redis.del(@key)
34
+ end
35
+
36
+ def find_trace(id)
37
+ str_data = @redis.zrange(@key, id, id, byscore: true).first
38
+ if str_data.nil?
39
+ nil
40
+ else
41
+ entry_to_trace(id, str_data)
42
+ end
43
+ end
44
+
45
+ def save_trace(operation_name, duration_ms, begin_ms, trace_data)
46
+ id = begin_ms
47
+ data = JSON.dump({ "o" => operation_name, "d" => duration_ms, "b" => begin_ms, "t" => Base64.encode64(trace_data) })
48
+ @redis.pipelined do |pipeline|
49
+ pipeline.zadd(@key, id, data)
50
+ if @remrangebyrank_limit
51
+ pipeline.zremrangebyrank(@key, 0, @remrangebyrank_limit)
52
+ end
53
+ end
54
+ id
55
+ end
56
+
57
+ private
58
+
59
+ def entry_to_trace(id, json_str)
60
+ data = JSON.parse(json_str)
61
+ StoredTrace.new(
62
+ id: id,
63
+ operation_name: data["o"],
64
+ duration_ms: data["d"].to_f,
65
+ begin_ms: data["b"].to_i,
66
+ trace_data: Base64.decode64(data["t"]),
67
+ )
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+ require "graphql/tracing/detailed_trace/memory_backend"
3
+ require "graphql/tracing/detailed_trace/redis_backend"
4
+
5
+ module GraphQL
6
+ module Tracing
7
+ # `DetailedTrace` can make detailed profiles for a subset of production traffic.
8
+ #
9
+ # When `MySchema.detailed_trace?(query)` returns `true`, a profiler-specific `trace_mode: ...` will be used for the query,
10
+ # overriding the one in `context[:trace_mode]`.
11
+ #
12
+ # __Redis__: The sampler stores its results in a provided Redis database. Depending on your needs,
13
+ # You can configure this database to retail all data (persistent) or to expire data according to your rules.
14
+ # If you need to save traces indefinitely, you can download them from Perfetto after opening them there.
15
+ #
16
+ # @example Adding the sampler to your schema
17
+ # class MySchema < GraphQL::Schema
18
+ # # Add the sampler:
19
+ # use GraphQL::Tracing::DetailedTrace, redis: Redis.new(...), limit: 100
20
+ #
21
+ # # And implement this hook to tell it when to take a sample:
22
+ # def self.detailed_trace?(query)
23
+ # # Could use `query.context`, `query.selected_operation_name`, `query.query_string` here
24
+ # # Could call out to Flipper, etc
25
+ # rand <= 0.000_1 # one in ten thousand
26
+ # end
27
+ # end
28
+ #
29
+ # @see Graphql::Dashboard GraphQL::Dashboard for viewing stored results
30
+ class DetailedTrace
31
+ # @param redis [Redis] If provided, profiles will be stored in Redis for later review
32
+ # @param limit [Integer] A maximum number of profiles to store
33
+ def self.use(schema, trace_mode: :profile_sample, memory: false, redis: nil, limit: nil)
34
+ storage = if redis
35
+ RedisBackend.new(redis: redis, limit: limit)
36
+ elsif memory
37
+ MemoryBackend.new(limit: limit)
38
+ else
39
+ raise ArgumentError, "Pass `redis: ...` to store traces in Redis for later review"
40
+ end
41
+ schema.detailed_trace = self.new(storage: storage, trace_mode: trace_mode)
42
+ schema.trace_with(PerfettoTrace, mode: trace_mode, save_profile: true)
43
+ end
44
+
45
+ def initialize(storage:, trace_mode:)
46
+ @storage = storage
47
+ @trace_mode = trace_mode
48
+ end
49
+
50
+ # @return [Symbol] The trace mode to use when {Schema.detailed_trace?} returns `true`
51
+ attr_reader :trace_mode
52
+
53
+ # @return [String] ID of saved trace
54
+ def save_trace(operation_name, duration_ms, begin_ms, trace_data)
55
+ @storage.save_trace(operation_name, duration_ms, begin_ms, trace_data)
56
+ end
57
+
58
+ # @param last [Integer]
59
+ # @param before [Integer] Timestamp in milliseconds since epoch
60
+ # @return [Enumerable<StoredTrace>]
61
+ def traces(last: nil, before: nil)
62
+ @storage.traces(last: last, before: before)
63
+ end
64
+
65
+ # @return [StoredTrace, nil]
66
+ def find_trace(id)
67
+ @storage.find_trace(id)
68
+ end
69
+
70
+ # @return [void]
71
+ def delete_trace(id)
72
+ @storage.delete_trace(id)
73
+ end
74
+
75
+ # @return [void]
76
+ def delete_all_traces
77
+ @storage.delete_all_traces
78
+ end
79
+
80
+ class StoredTrace
81
+ def initialize(id:, operation_name:, duration_ms:, begin_ms:, trace_data:)
82
+ @id = id
83
+ @operation_name = operation_name
84
+ @duration_ms = duration_ms
85
+ @begin_ms = begin_ms
86
+ @trace_data = trace_data
87
+ end
88
+
89
+ attr_reader :id, :operation_name, :duration_ms, :begin_ms, :trace_data
90
+ end
91
+ end
92
+ end
93
+ end
@@ -4,6 +4,15 @@ require "graphql/tracing/platform_trace"
4
4
 
5
5
  module GraphQL
6
6
  module Tracing
7
+ # A tracer for reporting GraphQL-Ruby time to New Relic
8
+ #
9
+ # @example Installing the tracer
10
+ # class MySchema < GraphQL::Schema
11
+ # trace_with GraphQL::Tracing::NewRelicTrace
12
+ #
13
+ # # Optional, use the operation name to set the new relic transaction name:
14
+ # # trace_with GraphQL::Tracing::NewRelicTrace, set_transaction_name: true
15
+ # end
7
16
  module NewRelicTrace
8
17
  # @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name.
9
18
  # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
@@ -61,8 +61,10 @@ module GraphQL
61
61
  DA_STR_VAL_NIL_IID = 14
62
62
 
63
63
  # @param active_support_notifications_pattern [String, RegExp, false] A filter for `ActiveSupport::Notifications`, if it's present. Or `false` to skip subscribing.
64
- def initialize(active_support_notifications_pattern: nil, **_rest)
64
+ def initialize(active_support_notifications_pattern: nil, save_profile: false, **_rest)
65
65
  super
66
+ @save_profile = save_profile
67
+ Fiber[:graphql_flow_stack] = nil
66
68
  @sequence_id = object_id
67
69
  @pid = Process.pid
68
70
  @flow_ids = Hash.new { |h, source_inst| h[source_inst] = [] }.compare_by_identity
@@ -108,6 +110,7 @@ module GraphQL
108
110
  @fibers_counter_id = :fibers_counter.object_id
109
111
  @fields_counter_id = :fields_counter.object_id
110
112
  @begin_validate = nil
113
+ @begin_time = nil
111
114
  @packets = []
112
115
  @packets << TracePacket.new(
113
116
  track_descriptor: TrackDescriptor.new(
@@ -172,6 +175,8 @@ module GraphQL
172
175
  end
173
176
 
174
177
  def begin_execute_multiplex(m)
178
+ @operation_name = m.queries.map { |q| q.selected_operation_name || "anonymous" }.join(",")
179
+ @begin_time = Time.now
175
180
  @packets << trace_packet(
176
181
  type: TrackEvent::Type::TYPE_SLICE_BEGIN,
177
182
  track_uuid: fid,
@@ -189,6 +194,12 @@ module GraphQL
189
194
  track_uuid: fid,
190
195
  )
191
196
  unsubscribe_from_active_support_notifications
197
+ if @save_profile
198
+ begin_ts = (@begin_time.to_f * 1000).round
199
+ end_ts = (Time.now.to_f * 1000).round
200
+ duration_ms = end_ts - begin_ts
201
+ m.schema.detailed_trace.save_trace(@operation_name, duration_ms, begin_ts, Trace.encode(Trace.new(packet: @packets)))
202
+ end
192
203
  super
193
204
  end
194
205
 
@@ -297,7 +308,7 @@ module GraphQL
297
308
  {
298
309
  debug_annotations: [
299
310
  @begin_validate.track_event.debug_annotations.first,
300
- payload_to_debug("valid?", !validation_errors.empty?)
311
+ payload_to_debug("valid?", validation_errors.empty?)
301
312
  ]
302
313
  }
303
314
  )
@@ -4,6 +4,28 @@ require "graphql/tracing/platform_trace"
4
4
 
5
5
  module GraphQL
6
6
  module Tracing
7
+ # A tracer for reporting GraphQL-Ruby times to Prometheus.
8
+ #
9
+ # The PrometheusExporter server must be run with a custom type collector that extends `GraphQL::Tracing::PrometheusTracing::GraphQLCollector`.
10
+ #
11
+ # @example Adding this trace to your schema
12
+ # require 'prometheus_exporter/client'
13
+ #
14
+ # class MySchema < GraphQL::Schema
15
+ # trace_with GraphQL::Tracing::PrometheusTrace
16
+ # end
17
+ #
18
+ # @example Running a custom type collector
19
+ # # lib/graphql_collector.rb
20
+ # if defined?(PrometheusExporter::Server)
21
+ # require 'graphql/tracing'
22
+ #
23
+ # class GraphQLCollector < GraphQL::Tracing::PrometheusTrace::GraphQLCollector
24
+ # end
25
+ # end
26
+ #
27
+ # # Then run:
28
+ # # bundle exec prometheus_exporter -a lib/graphql_collector.rb
7
29
  module PrometheusTrace
8
30
  if defined?(PrometheusExporter::Server)
9
31
  autoload :GraphQLCollector, "graphql/tracing/prometheus_trace/graphql_collector"
@@ -4,6 +4,12 @@ require "graphql/tracing/platform_trace"
4
4
 
5
5
  module GraphQL
6
6
  module Tracing
7
+ # A tracer for sending GraphQL-Ruby times to Scout
8
+ #
9
+ # @example Adding this tracer to your schema
10
+ # class MySchema < GraphQL::Schema
11
+ # trace_with GraphQL::Tracing::ScoutTrace
12
+ # end
7
13
  module ScoutTrace
8
14
  include PlatformTrace
9
15
 
@@ -4,6 +4,11 @@ require "graphql/tracing/platform_trace"
4
4
 
5
5
  module GraphQL
6
6
  module Tracing
7
+ # A tracer for reporting GraphQL-Ruby times to Sentry.
8
+ # @example Installing the tracer
9
+ # class MySchema < GraphQL::Schema
10
+ # trace_with GraphQL::Tracing::SentryTrace
11
+ # end
7
12
  module SentryTrace
8
13
  include PlatformTrace
9
14
 
@@ -4,6 +4,15 @@ require "graphql/tracing/platform_trace"
4
4
 
5
5
  module GraphQL
6
6
  module Tracing
7
+ # A tracer for reporting GraphQL-Ruby times to Statsd.
8
+ # Passing any Statsd client that implements `.time(name) { ... }` will work.
9
+ #
10
+ # @example Installing this tracer
11
+ # # eg:
12
+ # # $statsd = Statsd.new 'localhost', 9125
13
+ # class MySchema < GraphQL::Schema
14
+ # use GraphQL::Tracing::StatsdTrace, statsd: $statsd
15
+ # end
7
16
  module StatsdTrace
8
17
  include PlatformTrace
9
18
 
@@ -32,6 +32,7 @@ module GraphQL
32
32
  autoload :StatsdTrace, "graphql/tracing/statsd_trace"
33
33
  autoload :PrometheusTrace, "graphql/tracing/prometheus_trace"
34
34
  autoload :PerfettoTrace, "graphql/tracing/perfetto_trace"
35
+ autoload :DetailedTrace, "graphql/tracing/detailed_trace"
35
36
 
36
37
  # Objects may include traceable to gain a `.trace(...)` method.
37
38
  # The object must have a `@tracers` ivar of type `Array<<#trace(k, d, &b)>>`.
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.4.10"
3
+ VERSION = "2.4.11"
4
4
  end
data/lib/graphql.rb CHANGED
@@ -125,6 +125,9 @@ This is probably a bug in GraphQL-Ruby, please report this error on GitHub: http
125
125
  autoload :LoadApplicationObjectFailedError, "graphql/load_application_object_failed_error"
126
126
  autoload :Testing, "graphql/testing"
127
127
  autoload :Current, "graphql/current"
128
+ if defined?(::Rails::Engine)
129
+ autoload :Dashboard, 'graphql/dashboard'
130
+ end
128
131
  end
129
132
 
130
133
  require "graphql/version"