graphql 2.5.11 → 2.5.16
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.
- checksums.yaml +4 -4
- data/lib/graphql/dataloader/async_dataloader.rb +22 -11
- data/lib/graphql/dataloader/null_dataloader.rb +44 -10
- data/lib/graphql/dataloader.rb +75 -23
- data/lib/graphql/date_encoding_error.rb +1 -1
- data/lib/graphql/execution/interpreter/resolve.rb +7 -13
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +13 -0
- data/lib/graphql/execution/interpreter/runtime.rb +21 -16
- data/lib/graphql/execution/interpreter.rb +2 -13
- data/lib/graphql/language/document_from_schema_definition.rb +2 -1
- data/lib/graphql/schema/build_from_definition.rb +3 -1
- data/lib/graphql/schema/directive.rb +22 -4
- data/lib/graphql/schema/member/has_arguments.rb +6 -0
- data/lib/graphql/schema/validator/required_validator.rb +33 -2
- data/lib/graphql/schema/visibility.rb +2 -2
- data/lib/graphql/schema.rb +20 -3
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +2 -2
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
- data/lib/graphql/testing/helpers.rb +12 -9
- data/lib/graphql/testing/mock_action_cable.rb +111 -0
- data/lib/graphql/testing.rb +1 -0
- data/lib/graphql/tracing/detailed_trace.rb +52 -4
- data/lib/graphql/tracing/perfetto_trace.rb +161 -77
- data/lib/graphql/tracing/sentry_trace.rb +3 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +1 -1
- metadata +4 -3
|
@@ -8,6 +8,13 @@ module GraphQL
|
|
|
8
8
|
#
|
|
9
9
|
# (This is for specifying mutually exclusive sets of arguments.)
|
|
10
10
|
#
|
|
11
|
+
# If you use {GraphQL::Schema::Visibility} to hide all the arguments in a `one_of: [..]` set,
|
|
12
|
+
# then a developer-facing {GraphQL::Error} will be raised during execution. Pass `allow_all_hidden: true` to
|
|
13
|
+
# skip validation in this case instead.
|
|
14
|
+
#
|
|
15
|
+
# This validator also implements `argument ... required: :nullable`. If an argument has `required: :nullable`
|
|
16
|
+
# but it's hidden with {GraphQL::Schema::Visibility}, then this validator doesn't run.
|
|
17
|
+
#
|
|
11
18
|
# @example Require exactly one of these arguments
|
|
12
19
|
#
|
|
13
20
|
# field :update_amount, IngredientAmount, null: false do
|
|
@@ -37,15 +44,17 @@ module GraphQL
|
|
|
37
44
|
class RequiredValidator < Validator
|
|
38
45
|
# @param one_of [Array<Symbol>] A list of arguments, exactly one of which is required for this field
|
|
39
46
|
# @param argument [Symbol] An argument that is required for this field
|
|
47
|
+
# @param allow_all_hidden [Boolean] If `true`, then this validator won't run if all the `one_of: ...` arguments have been hidden
|
|
40
48
|
# @param message [String]
|
|
41
|
-
def initialize(one_of: nil, argument: nil, message: nil, **default_options)
|
|
49
|
+
def initialize(one_of: nil, argument: nil, allow_all_hidden: nil, message: nil, **default_options)
|
|
42
50
|
@one_of = if one_of
|
|
43
51
|
one_of
|
|
44
52
|
elsif argument
|
|
45
|
-
[argument]
|
|
53
|
+
[ argument ]
|
|
46
54
|
else
|
|
47
55
|
raise ArgumentError, "`one_of:` or `argument:` must be given in `validates required: {...}`"
|
|
48
56
|
end
|
|
57
|
+
@allow_all_hidden = allow_all_hidden.nil? ? !!argument : allow_all_hidden
|
|
49
58
|
@message = message
|
|
50
59
|
super(**default_options)
|
|
51
60
|
end
|
|
@@ -54,10 +63,17 @@ module GraphQL
|
|
|
54
63
|
fully_matched_conditions = 0
|
|
55
64
|
partially_matched_conditions = 0
|
|
56
65
|
|
|
66
|
+
visible_keywords = context.types.arguments(@validated).map(&:keyword)
|
|
67
|
+
no_visible_conditions = true
|
|
68
|
+
|
|
57
69
|
if !value.nil?
|
|
58
70
|
@one_of.each do |one_of_condition|
|
|
59
71
|
case one_of_condition
|
|
60
72
|
when Symbol
|
|
73
|
+
if no_visible_conditions && visible_keywords.include?(one_of_condition)
|
|
74
|
+
no_visible_conditions = false
|
|
75
|
+
end
|
|
76
|
+
|
|
61
77
|
if value.key?(one_of_condition)
|
|
62
78
|
fully_matched_conditions += 1
|
|
63
79
|
end
|
|
@@ -66,6 +82,9 @@ module GraphQL
|
|
|
66
82
|
full_match = true
|
|
67
83
|
|
|
68
84
|
one_of_condition.each do |k|
|
|
85
|
+
if no_visible_conditions && visible_keywords.include?(k)
|
|
86
|
+
no_visible_conditions = false
|
|
87
|
+
end
|
|
69
88
|
if value.key?(k)
|
|
70
89
|
any_match = true
|
|
71
90
|
else
|
|
@@ -88,6 +107,18 @@ module GraphQL
|
|
|
88
107
|
end
|
|
89
108
|
end
|
|
90
109
|
|
|
110
|
+
if no_visible_conditions
|
|
111
|
+
if @allow_all_hidden
|
|
112
|
+
return nil
|
|
113
|
+
else
|
|
114
|
+
raise GraphQL::Error, <<~ERR
|
|
115
|
+
#{@validated.path} validates `required: ...` but all required arguments were hidden.
|
|
116
|
+
|
|
117
|
+
Update your schema definition to allow the client to see some fields or skip validation by adding `required: { ..., allow_all_hidden: true }`
|
|
118
|
+
ERR
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
91
122
|
if fully_matched_conditions == 1 && partially_matched_conditions == 0
|
|
92
123
|
nil # OK
|
|
93
124
|
else
|
|
@@ -10,9 +10,9 @@ module GraphQL
|
|
|
10
10
|
class Visibility
|
|
11
11
|
# @param schema [Class<GraphQL::Schema>]
|
|
12
12
|
# @param profiles [Hash<Symbol => Hash>] A hash of `name => context` pairs for preloading visibility profiles
|
|
13
|
-
# @param preload [Boolean] if `true`, load the default schema profile and all named profiles immediately (defaults to `true` for `Rails.env.production?`)
|
|
13
|
+
# @param preload [Boolean] if `true`, load the default schema profile and all named profiles immediately (defaults to `true` for `Rails.env.production?` and `Rails.env.staging?`)
|
|
14
14
|
# @param migration_errors [Boolean] if `true`, raise an error when `Visibility` and `Warden` return different results
|
|
15
|
-
def self.use(schema, dynamic: false, profiles: EmptyObjects::EMPTY_HASH, preload: (defined?(Rails.env) ? Rails.env.production? : nil), migration_errors: false)
|
|
15
|
+
def self.use(schema, dynamic: false, profiles: EmptyObjects::EMPTY_HASH, preload: (defined?(Rails.env) ? (Rails.env.production? || Rails.env.staging?) : nil), migration_errors: false)
|
|
16
16
|
profiles&.each { |name, ctx|
|
|
17
17
|
ctx[:visibility_profile] = name
|
|
18
18
|
ctx.freeze
|
data/lib/graphql/schema.rb
CHANGED
|
@@ -1708,7 +1708,7 @@ module GraphQL
|
|
|
1708
1708
|
# If you need to support previous, non-spec behavior which allowed selecting union fields
|
|
1709
1709
|
# but *not* selecting any fields on that union, set this to `true` to continue allowing that behavior.
|
|
1710
1710
|
#
|
|
1711
|
-
# If this is `true`, then {.
|
|
1711
|
+
# If this is `true`, then {.legacy_invalid_empty_selections_on_union_with_type} will be called with {Query} objects
|
|
1712
1712
|
# with that kind of selections. You must implement that method
|
|
1713
1713
|
# @param new_value [Boolean]
|
|
1714
1714
|
# @return [true, false, nil]
|
|
@@ -1724,6 +1724,22 @@ module GraphQL
|
|
|
1724
1724
|
end
|
|
1725
1725
|
end
|
|
1726
1726
|
|
|
1727
|
+
# This method is called during validation when a previously-allowed, but non-spec
|
|
1728
|
+
# query is encountered where a union field has no child selections on it.
|
|
1729
|
+
#
|
|
1730
|
+
# If `legacy_invalid_empty_selections_on_union_with_type` is overridden, this method will not be called.
|
|
1731
|
+
#
|
|
1732
|
+
# You should implement this method or `legacy_invalid_empty_selections_on_union_with_type`
|
|
1733
|
+
# to log the violation so that you can contact clients and notify them about changing their queries.
|
|
1734
|
+
# Then return a suitable value to tell GraphQL-Ruby how to continue.
|
|
1735
|
+
# @param query [GraphQL::Query]
|
|
1736
|
+
# @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query
|
|
1737
|
+
# @return [String] A validation error to return for this query
|
|
1738
|
+
# @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute)
|
|
1739
|
+
def legacy_invalid_empty_selections_on_union(query)
|
|
1740
|
+
raise "Implement `def self.legacy_invalid_empty_selections_on_union_with_type(query, type)` or `def self.legacy_invalid_empty_selections_on_union(query)` to handle this scenario"
|
|
1741
|
+
end
|
|
1742
|
+
|
|
1727
1743
|
# This method is called during validation when a previously-allowed, but non-spec
|
|
1728
1744
|
# query is encountered where a union field has no child selections on it.
|
|
1729
1745
|
#
|
|
@@ -1731,11 +1747,12 @@ module GraphQL
|
|
|
1731
1747
|
# and notify them about changing their queries. Then return a suitable value to
|
|
1732
1748
|
# tell GraphQL-Ruby how to continue.
|
|
1733
1749
|
# @param query [GraphQL::Query]
|
|
1750
|
+
# @param type [Module] A GraphQL type definition
|
|
1734
1751
|
# @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query
|
|
1735
1752
|
# @return [String] A validation error to return for this query
|
|
1736
1753
|
# @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute)
|
|
1737
|
-
def
|
|
1738
|
-
|
|
1754
|
+
def legacy_invalid_empty_selections_on_union_with_type(query, type)
|
|
1755
|
+
legacy_invalid_empty_selections_on_union(query)
|
|
1739
1756
|
end
|
|
1740
1757
|
|
|
1741
1758
|
# This setting controls how GraphQL-Ruby handles overlapping selections on scalar types when the types
|
|
@@ -49,7 +49,7 @@ module GraphQL
|
|
|
49
49
|
if !resolved_type.kind.fields?
|
|
50
50
|
case @schema.allow_legacy_invalid_empty_selections_on_union
|
|
51
51
|
when true
|
|
52
|
-
legacy_invalid_empty_selection_result = @schema.
|
|
52
|
+
legacy_invalid_empty_selection_result = @schema.legacy_invalid_empty_selections_on_union_with_type(@context.query, resolved_type)
|
|
53
53
|
case legacy_invalid_empty_selection_result
|
|
54
54
|
when :return_validation_error
|
|
55
55
|
# keep `return_validation_error = true`
|
|
@@ -61,7 +61,7 @@ module GraphQL
|
|
|
61
61
|
return_validation_error = false
|
|
62
62
|
legacy_invalid_empty_selection_result = nil
|
|
63
63
|
else
|
|
64
|
-
raise GraphQL::InvariantError, "Unexpected return value from
|
|
64
|
+
raise GraphQL::InvariantError, "Unexpected return value from legacy_invalid_empty_selections_on_union_with_type, must be `:return_validation_error`, String, or nil (got: #{legacy_invalid_empty_selection_result.inspect})"
|
|
65
65
|
end
|
|
66
66
|
when false
|
|
67
67
|
# pass -- error below
|
|
@@ -39,9 +39,9 @@ module GraphQL
|
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
def run_graphql_field(schema, field_path, object, arguments: {}, context: {}, ast_node: nil, lookahead: nil)
|
|
42
|
+
def run_graphql_field(schema, field_path, object, arguments: {}, context: {}, ast_node: nil, lookahead: nil, visibility_profile: nil)
|
|
43
43
|
type_name, *field_names = field_path.split(".")
|
|
44
|
-
dummy_query = GraphQL::Query.new(schema, "{ __typename }", context: context)
|
|
44
|
+
dummy_query = GraphQL::Query.new(schema, "{ __typename }", context: context, visibility_profile: visibility_profile)
|
|
45
45
|
query_context = dummy_query.context
|
|
46
46
|
dataloader = query_context.dataloader
|
|
47
47
|
object_type = dummy_query.types.type(type_name) # rubocop:disable Development/ContextIsPassedCop
|
|
@@ -104,32 +104,35 @@ module GraphQL
|
|
|
104
104
|
end
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
-
def with_resolution_context(schema, type:, object:, context:{})
|
|
107
|
+
def with_resolution_context(schema, type:, object:, context:{}, visibility_profile: nil)
|
|
108
108
|
resolution_context = ResolutionAssertionContext.new(
|
|
109
109
|
self,
|
|
110
110
|
schema: schema,
|
|
111
111
|
type_name: type,
|
|
112
112
|
object: object,
|
|
113
|
-
context: context
|
|
113
|
+
context: context,
|
|
114
|
+
visibility_profile: visibility_profile,
|
|
114
115
|
)
|
|
115
116
|
yield(resolution_context)
|
|
116
117
|
end
|
|
117
118
|
|
|
118
119
|
class ResolutionAssertionContext
|
|
119
|
-
def initialize(test, type_name:, object:, schema:, context:)
|
|
120
|
+
def initialize(test, type_name:, object:, schema:, context:, visibility_profile:)
|
|
120
121
|
@test = test
|
|
121
122
|
@type_name = type_name
|
|
122
123
|
@object = object
|
|
123
124
|
@schema = schema
|
|
124
125
|
@context = context
|
|
126
|
+
@visibility_profile = visibility_profile
|
|
125
127
|
end
|
|
126
128
|
|
|
129
|
+
attr_reader :visibility_profile
|
|
127
130
|
|
|
128
131
|
def run_graphql_field(field_name, arguments: {})
|
|
129
132
|
if @schema
|
|
130
|
-
@test.run_graphql_field(@schema, "#{@type_name}.#{field_name}", @object, arguments: arguments, context: @context)
|
|
133
|
+
@test.run_graphql_field(@schema, "#{@type_name}.#{field_name}", @object, arguments: arguments, context: @context, visibility_profile: @visibility_profile)
|
|
131
134
|
else
|
|
132
|
-
@test.run_graphql_field("#{@type_name}.#{field_name}", @object, arguments: arguments, context: @context)
|
|
135
|
+
@test.run_graphql_field("#{@type_name}.#{field_name}", @object, arguments: arguments, context: @context, visibility_profile: @visibility_profile)
|
|
133
136
|
end
|
|
134
137
|
end
|
|
135
138
|
end
|
|
@@ -137,8 +140,8 @@ module GraphQL
|
|
|
137
140
|
module SchemaHelpers
|
|
138
141
|
include Helpers
|
|
139
142
|
|
|
140
|
-
def run_graphql_field(field_path, object, arguments: {}, context: {})
|
|
141
|
-
super(@@schema_class_for_helpers, field_path, object, arguments: arguments, context: context)
|
|
143
|
+
def run_graphql_field(field_path, object, arguments: {}, context: {}, visibility_profile: nil)
|
|
144
|
+
super(@@schema_class_for_helpers, field_path, object, arguments: arguments, context: context, visibility_profile: visibility_profile)
|
|
142
145
|
end
|
|
143
146
|
|
|
144
147
|
def with_resolution_context(*args, **kwargs, &block)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphQL
|
|
3
|
+
module Testing
|
|
4
|
+
# A stub implementation of ActionCable.
|
|
5
|
+
# Any methods to support the mock backend have `mock` in the name.
|
|
6
|
+
#
|
|
7
|
+
# @example Configuring your schema to use MockActionCable in the test environment
|
|
8
|
+
# class MySchema < GraphQL::Schema
|
|
9
|
+
# # Use MockActionCable in test:
|
|
10
|
+
# use GraphQL::Subscriptions::ActionCableSubscriptions,
|
|
11
|
+
# action_cable: Rails.env.test? ? GraphQL::Testing::MockActionCable : ActionCable
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# @example Clearing old data before each test
|
|
15
|
+
# setup do
|
|
16
|
+
# GraphQL::Testing::MockActionCable.clear_mocks
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# @example Using MockActionCable in a test case
|
|
20
|
+
# # Create a channel to use in the test, pass it to GraphQL
|
|
21
|
+
# mock_channel = GraphQL::Testing::MockActionCable.get_mock_channel
|
|
22
|
+
# ActionCableTestSchema.execute("subscription { newsFlash { text } }", context: { channel: mock_channel })
|
|
23
|
+
#
|
|
24
|
+
# # Trigger a subscription update
|
|
25
|
+
# ActionCableTestSchema.subscriptions.trigger(:news_flash, {}, {text: "After yesterday's rain, someone stopped on Rio Road to help a box turtle across five lanes of traffic"})
|
|
26
|
+
#
|
|
27
|
+
# # Check messages on the channel
|
|
28
|
+
# expected_msg = {
|
|
29
|
+
# result: {
|
|
30
|
+
# "data" => {
|
|
31
|
+
# "newsFlash" => {
|
|
32
|
+
# "text" => "After yesterday's rain, someone stopped on Rio Road to help a box turtle across five lanes of traffic"
|
|
33
|
+
# }
|
|
34
|
+
# }
|
|
35
|
+
# },
|
|
36
|
+
# more: true,
|
|
37
|
+
# }
|
|
38
|
+
# assert_equal [expected_msg], mock_channel.mock_broadcasted_messages
|
|
39
|
+
#
|
|
40
|
+
class MockActionCable
|
|
41
|
+
class MockChannel
|
|
42
|
+
def initialize
|
|
43
|
+
@mock_broadcasted_messages = []
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @return [Array<Hash>] Payloads "sent" to this channel by GraphQL-Ruby
|
|
47
|
+
attr_reader :mock_broadcasted_messages
|
|
48
|
+
|
|
49
|
+
# Called by ActionCableSubscriptions. Implements a Rails API.
|
|
50
|
+
def stream_from(stream_name, coder: nil, &block)
|
|
51
|
+
# Rails uses `coder`, we don't
|
|
52
|
+
block ||= ->(msg) { @mock_broadcasted_messages << msg }
|
|
53
|
+
MockActionCable.mock_stream_for(stream_name).add_mock_channel(self, block)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Used by mock code
|
|
58
|
+
# @api private
|
|
59
|
+
class MockStream
|
|
60
|
+
def initialize
|
|
61
|
+
@mock_channels = {}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def add_mock_channel(channel, handler)
|
|
65
|
+
@mock_channels[channel] = handler
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def mock_broadcast(message)
|
|
69
|
+
@mock_channels.each do |channel, handler|
|
|
70
|
+
handler && handler.call(message)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
class << self
|
|
76
|
+
# Call this before each test run to make sure that MockActionCable's data is empty
|
|
77
|
+
def clear_mocks
|
|
78
|
+
@mock_streams = {}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Implements Rails API
|
|
82
|
+
def server
|
|
83
|
+
self
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Implements Rails API
|
|
87
|
+
def broadcast(stream_name, message)
|
|
88
|
+
stream = @mock_streams[stream_name]
|
|
89
|
+
stream && stream.mock_broadcast(message)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Used by mock code
|
|
93
|
+
def mock_stream_for(stream_name)
|
|
94
|
+
@mock_streams[stream_name] ||= MockStream.new
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Use this as `context[:channel]` to simulate an ActionCable channel
|
|
98
|
+
#
|
|
99
|
+
# @return [GraphQL::Testing::MockActionCable::MockChannel]
|
|
100
|
+
def get_mock_channel
|
|
101
|
+
MockChannel.new
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# @return [Array<String>] Streams that currently have subscribers
|
|
105
|
+
def mock_stream_names
|
|
106
|
+
@mock_streams.keys
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
data/lib/graphql/testing.rb
CHANGED
|
@@ -9,8 +9,12 @@ module GraphQL
|
|
|
9
9
|
# When `MySchema.detailed_trace?(query)` returns `true`, a profiler-specific `trace_mode: ...` will be used for the query,
|
|
10
10
|
# overriding the one in `context[:trace_mode]`.
|
|
11
11
|
#
|
|
12
|
+
# By default, the detailed tracer calls `.inspect` on application objects returned from fields. You can customize
|
|
13
|
+
# this behavior by extending {DetailedTrace} and overriding {#inspect_object}. You can opt out of debug annotations
|
|
14
|
+
# entirely with `use ..., debug: false` or for a single query with `context: { detailed_trace_debug: false }`.
|
|
15
|
+
#
|
|
12
16
|
# __Redis__: The sampler stores its results in a provided Redis database. Depending on your needs,
|
|
13
|
-
# You can configure this database to
|
|
17
|
+
# You can configure this database to retain all data (persistent) or to expire data according to your rules.
|
|
14
18
|
# If you need to save traces indefinitely, you can download them from Perfetto after opening them there.
|
|
15
19
|
#
|
|
16
20
|
# @example Adding the sampler to your schema
|
|
@@ -27,10 +31,29 @@ module GraphQL
|
|
|
27
31
|
# end
|
|
28
32
|
#
|
|
29
33
|
# @see Graphql::Dashboard GraphQL::Dashboard for viewing stored results
|
|
34
|
+
#
|
|
35
|
+
# @example Customizing debug output in traces
|
|
36
|
+
# class CustomDetailedTrace < GraphQL::Tracing::DetailedTrace
|
|
37
|
+
# def inspect_object(object)
|
|
38
|
+
# if object.is_a?(SomeThing)
|
|
39
|
+
# # handle it specially ...
|
|
40
|
+
# else
|
|
41
|
+
# super
|
|
42
|
+
# end
|
|
43
|
+
# end
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# @example disabling debug annotations completely
|
|
47
|
+
# use DetailedTrace, debug: false, ...
|
|
48
|
+
#
|
|
49
|
+
# @example disabling debug annotations for one query
|
|
50
|
+
# MySchema.execute(query_str, context: { detailed_trace_debug: false })
|
|
51
|
+
#
|
|
30
52
|
class DetailedTrace
|
|
31
53
|
# @param redis [Redis] If provided, profiles will be stored in Redis for later review
|
|
32
54
|
# @param limit [Integer] A maximum number of profiles to store
|
|
33
|
-
|
|
55
|
+
# @param debug [Boolean] if `false`, it won't create `debug` annotations in Perfetto traces (reduces overhead)
|
|
56
|
+
def self.use(schema, trace_mode: :profile_sample, memory: false, debug: debug?, redis: nil, limit: nil)
|
|
34
57
|
storage = if redis
|
|
35
58
|
RedisBackend.new(redis: redis, limit: limit)
|
|
36
59
|
elsif memory
|
|
@@ -38,13 +61,15 @@ module GraphQL
|
|
|
38
61
|
else
|
|
39
62
|
raise ArgumentError, "Pass `redis: ...` to store traces in Redis for later review"
|
|
40
63
|
end
|
|
41
|
-
|
|
64
|
+
detailed_trace = self.new(storage: storage, trace_mode: trace_mode, debug: debug)
|
|
65
|
+
schema.detailed_trace = detailed_trace
|
|
42
66
|
schema.trace_with(PerfettoTrace, mode: trace_mode, save_profile: true)
|
|
43
67
|
end
|
|
44
68
|
|
|
45
|
-
def initialize(storage:, trace_mode:)
|
|
69
|
+
def initialize(storage:, trace_mode:, debug:)
|
|
46
70
|
@storage = storage
|
|
47
71
|
@trace_mode = trace_mode
|
|
72
|
+
@debug = debug
|
|
48
73
|
end
|
|
49
74
|
|
|
50
75
|
# @return [Symbol] The trace mode to use when {Schema.detailed_trace?} returns `true`
|
|
@@ -55,6 +80,11 @@ module GraphQL
|
|
|
55
80
|
@storage.save_trace(operation_name, duration_ms, begin_ms, trace_data)
|
|
56
81
|
end
|
|
57
82
|
|
|
83
|
+
# @return [Boolean]
|
|
84
|
+
def debug?
|
|
85
|
+
@debug
|
|
86
|
+
end
|
|
87
|
+
|
|
58
88
|
# @param last [Integer]
|
|
59
89
|
# @param before [Integer] Timestamp in milliseconds since epoch
|
|
60
90
|
# @return [Enumerable<StoredTrace>]
|
|
@@ -77,6 +107,24 @@ module GraphQL
|
|
|
77
107
|
@storage.delete_all_traces
|
|
78
108
|
end
|
|
79
109
|
|
|
110
|
+
def inspect_object(object)
|
|
111
|
+
self.class.inspect_object(object)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def self.inspect_object(object)
|
|
115
|
+
if defined?(ActiveRecord::Relation) && object.is_a?(ActiveRecord::Relation)
|
|
116
|
+
"#{object.class}, .to_sql=#{object.to_sql.inspect}"
|
|
117
|
+
else
|
|
118
|
+
object.inspect
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Default debug setting
|
|
123
|
+
# @return [true]
|
|
124
|
+
def self.debug?
|
|
125
|
+
true
|
|
126
|
+
end
|
|
127
|
+
|
|
80
128
|
class StoredTrace
|
|
81
129
|
def initialize(id:, operation_name:, duration_ms:, begin_ms:, trace_data:)
|
|
82
130
|
@id = id
|