graphql 2.4.10 → 2.4.12

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/visitor.rb +35 -40
  3. data/lib/graphql/analysis.rb +12 -9
  4. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  5. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  6. data/lib/graphql/dashboard/statics/dashboard.css +3 -0
  7. data/lib/graphql/dashboard/statics/dashboard.js +78 -0
  8. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  9. data/lib/graphql/dashboard/statics/icon.png +0 -0
  10. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  11. data/lib/graphql/dashboard/views/graphql/dashboard/traces/index.html.erb +63 -0
  12. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +60 -0
  13. data/lib/graphql/dashboard.rb +142 -0
  14. data/lib/graphql/execution/interpreter/runtime.rb +1 -1
  15. data/lib/graphql/invalid_name_error.rb +1 -1
  16. data/lib/graphql/invalid_null_error.rb +5 -15
  17. data/lib/graphql/language/lexer.rb +7 -3
  18. data/lib/graphql/language/parser.rb +1 -1
  19. data/lib/graphql/schema/build_from_definition.rb +0 -1
  20. data/lib/graphql/schema/enum.rb +16 -1
  21. data/lib/graphql/schema/input_object.rb +1 -1
  22. data/lib/graphql/schema/loader.rb +0 -1
  23. data/lib/graphql/schema/member/has_dataloader.rb +4 -0
  24. data/lib/graphql/schema/resolver.rb +5 -1
  25. data/lib/graphql/schema.rb +51 -9
  26. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  27. data/lib/graphql/tracing/active_support_notifications_trace.rb +6 -2
  28. data/lib/graphql/tracing/appoptics_trace.rb +2 -0
  29. data/lib/graphql/tracing/appsignal_trace.rb +6 -0
  30. data/lib/graphql/tracing/data_dog_trace.rb +5 -0
  31. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  32. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  33. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  34. data/lib/graphql/tracing/new_relic_trace.rb +47 -23
  35. data/lib/graphql/tracing/perfetto_trace.rb +13 -2
  36. data/lib/graphql/tracing/prometheus_trace.rb +22 -0
  37. data/lib/graphql/tracing/scout_trace.rb +6 -0
  38. data/lib/graphql/tracing/sentry_trace.rb +5 -0
  39. data/lib/graphql/tracing/statsd_trace.rb +9 -0
  40. data/lib/graphql/tracing.rb +1 -0
  41. data/lib/graphql/version.rb +1 -1
  42. data/lib/graphql.rb +3 -0
  43. metadata +15 -2
@@ -4,16 +4,30 @@ 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
16
+ #
17
+ # @example Installing without trace events for `authorized?` or `resolve_type` calls
18
+ # trace_with GraphQL::Tracing::NewRelicTrace, trace_authorized: false, trace_resolve_type: false
7
19
  module NewRelicTrace
8
20
  # @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name.
9
21
  # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
10
22
  # It can also be specified per-query with `context[:set_new_relic_transaction_name]`.
11
23
  # @param trace_authorized [Boolean] If `false`, skip tracing `authorized?` calls
12
24
  # @param trace_resolve_type [Boolean] If `false`, skip tracing `resolve_type?` calls
13
- def initialize(set_transaction_name: false, trace_authorized: true, trace_resolve_type: true, **_rest)
25
+ # @param trace_scalars [Boolean] If `true`, Enum and Scalar fields will be traced by default
26
+ def initialize(set_transaction_name: false, trace_authorized: true, trace_resolve_type: true, trace_scalars: false, **_rest)
14
27
  @set_transaction_name = set_transaction_name
15
28
  @trace_authorized = trace_authorized
16
29
  @trace_resolve_type = trace_resolve_type
30
+ @trace_scalars = trace_scalars
17
31
  @nr_field_names = Hash.new do |h, field|
18
32
  h[field] = "GraphQL/#{field.owner.graphql_name}/#{field.graphql_name}"
19
33
  end.compare_by_identity
@@ -80,78 +94,88 @@ module GraphQL
80
94
  end
81
95
 
82
96
  def begin_execute_field(field, object, arguments, query)
83
- nr_segment_stack << NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: @nr_field_names[field], category: :web)
97
+ return_type = field.type.unwrap
98
+ trace_field = if return_type.kind.scalar? || return_type.kind.enum?
99
+ (field.trace.nil? && @trace_scalars) || field.trace
100
+ else
101
+ true
102
+ end
103
+ if trace_field
104
+ start_segment(partial_name: @nr_field_names[field], category: :web)
105
+ end
84
106
  super
85
107
  end
86
108
 
87
109
  def end_execute_field(field, objects, arguments, query, result)
88
- nr_segment_stack.pop.finish
110
+ finish_segment
89
111
  super
90
112
  end
91
113
 
92
114
  def begin_authorized(type, obj, ctx)
93
115
  if @trace_authorized
94
- nr_segment_stack << NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: @nr_authorized_names[type], category: :web)
116
+ start_segment(partial_name: @nr_authorized_names[type], category: :web)
95
117
  end
96
118
  super
97
119
  end
98
120
 
99
121
  def end_authorized(type, obj, ctx, is_authed)
100
122
  if @trace_authorized
101
- nr_segment_stack.pop.finish
123
+ finish_segment
102
124
  end
103
125
  super
104
126
  end
105
127
 
106
128
  def begin_resolve_type(type, value, context)
107
129
  if @trace_resolve_type
108
- nr_segment_stack << NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: @nr_resolve_type_names[type], category: :web)
130
+ start_segment(partial_name: @nr_resolve_type_names[type], category: :web)
109
131
  end
110
132
  super
111
133
  end
112
134
 
113
135
  def end_resolve_type(type, value, context, resolved_type)
114
136
  if @trace_resolve_type
115
- nr_segment_stack.pop.finish
137
+ finish_segment
116
138
  end
117
139
  super
118
140
  end
119
141
 
120
- def begin_dataloader(dl)
121
- super
122
- end
123
-
124
- def end_dataloader(dl)
125
- super
126
- end
127
-
128
142
  def begin_dataloader_source(source)
129
- nr_segment_stack << NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: @nr_source_names[source], category: :web)
143
+ start_segment(partial_name: @nr_source_names[source], category: :web)
130
144
  super
131
145
  end
132
146
 
133
147
  def end_dataloader_source(source)
134
- nr_segment_stack.pop.finish
148
+ finish_segment
135
149
  super
136
150
  end
137
151
 
138
152
  def dataloader_fiber_yield(source)
139
- current_segment = nr_segment_stack.last
140
- current_segment.finish
153
+ prev_segment = finish_segment
154
+ Fiber[:graphql_nr_previous_segment] = prev_segment
141
155
  super
142
156
  end
143
157
 
144
158
  def dataloader_fiber_resume(source)
145
- prev_segment = nr_segment_stack.pop
159
+ prev_segment = Fiber[:graphql_nr_previous_segment]
160
+ Fiber[:graphql_nr_previous_segment] = nil
146
161
  seg_partial_name = prev_segment.name.sub(/^.*(GraphQL.*)$/, '\1')
147
- nr_segment_stack << NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: seg_partial_name, category: :web)
162
+ start_segment(partial_name: seg_partial_name, category: :web)
148
163
  super
149
164
  end
150
165
 
151
166
  private
152
167
 
153
- def nr_segment_stack
154
- Fiber[:graphql_nr_segment_stack] ||= []
168
+ def start_segment(...)
169
+ Fiber[:graphql_nr_segment] = NewRelic::Agent::Tracer.start_transaction_or_segment(...)
170
+ end
171
+
172
+ def finish_segment
173
+ segment = Fiber[:graphql_nr_segment]
174
+ if segment
175
+ segment.finish
176
+ Fiber[:graphql_nr_segment] = nil
177
+ segment
178
+ end
155
179
  end
156
180
 
157
181
  def transaction_name(query)
@@ -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.12"
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"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.10
4
+ version: 2.4.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-02-18 00:00:00.000000000 Z
10
+ date: 2025-03-11 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64
@@ -459,6 +459,16 @@ files:
459
459
  - lib/graphql/backtrace/traced_error.rb
460
460
  - lib/graphql/coercion_error.rb
461
461
  - lib/graphql/current.rb
462
+ - lib/graphql/dashboard.rb
463
+ - lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css
464
+ - lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js
465
+ - lib/graphql/dashboard/statics/dashboard.css
466
+ - lib/graphql/dashboard/statics/dashboard.js
467
+ - lib/graphql/dashboard/statics/header-icon.png
468
+ - lib/graphql/dashboard/statics/icon.png
469
+ - lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb
470
+ - lib/graphql/dashboard/views/graphql/dashboard/traces/index.html.erb
471
+ - lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb
462
472
  - lib/graphql/dataloader.rb
463
473
  - lib/graphql/dataloader/active_record_association_source.rb
464
474
  - lib/graphql/dataloader/active_record_source.rb
@@ -721,6 +731,9 @@ files:
721
731
  - lib/graphql/tracing/call_legacy_tracers.rb
722
732
  - lib/graphql/tracing/data_dog_trace.rb
723
733
  - lib/graphql/tracing/data_dog_tracing.rb
734
+ - lib/graphql/tracing/detailed_trace.rb
735
+ - lib/graphql/tracing/detailed_trace/memory_backend.rb
736
+ - lib/graphql/tracing/detailed_trace/redis_backend.rb
724
737
  - lib/graphql/tracing/legacy_hooks_trace.rb
725
738
  - lib/graphql/tracing/legacy_trace.rb
726
739
  - lib/graphql/tracing/new_relic_trace.rb