graphql 2.5.14 → 2.5.22

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  4. data/lib/graphql/dashboard/application_controller.rb +41 -0
  5. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  6. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  7. data/lib/graphql/dashboard/subscriptions.rb +2 -1
  8. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +1 -0
  9. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +2 -2
  10. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +1 -1
  11. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +1 -1
  12. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +1 -1
  13. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +1 -1
  14. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +1 -1
  15. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +7 -7
  16. data/lib/graphql/dashboard.rb +11 -73
  17. data/lib/graphql/dataloader/null_dataloader.rb +7 -3
  18. data/lib/graphql/date_encoding_error.rb +1 -1
  19. data/lib/graphql/execution/interpreter.rb +0 -1
  20. data/lib/graphql/execution/multiplex.rb +1 -1
  21. data/lib/graphql/execution/next/field_resolve_step.rb +711 -0
  22. data/lib/graphql/execution/next/load_argument_step.rb +60 -0
  23. data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
  24. data/lib/graphql/execution/next/runner.rb +389 -0
  25. data/lib/graphql/execution/next/selections_step.rb +37 -0
  26. data/lib/graphql/execution/next.rb +70 -0
  27. data/lib/graphql/execution.rb +1 -0
  28. data/lib/graphql/execution_error.rb +13 -10
  29. data/lib/graphql/introspection/directive_type.rb +7 -3
  30. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  31. data/lib/graphql/introspection/entry_points.rb +11 -3
  32. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  33. data/lib/graphql/introspection/field_type.rb +13 -5
  34. data/lib/graphql/introspection/input_value_type.rb +21 -13
  35. data/lib/graphql/introspection/type_type.rb +64 -28
  36. data/lib/graphql/invalid_null_error.rb +11 -5
  37. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  38. data/lib/graphql/language.rb +21 -12
  39. data/lib/graphql/pagination/connection.rb +2 -0
  40. data/lib/graphql/pagination/connections.rb +32 -0
  41. data/lib/graphql/query/context.rb +3 -2
  42. data/lib/graphql/query/null_context.rb +9 -3
  43. data/lib/graphql/schema/argument.rb +12 -0
  44. data/lib/graphql/schema/build_from_definition.rb +7 -0
  45. data/lib/graphql/schema/directive.rb +8 -1
  46. data/lib/graphql/schema/field/connection_extension.rb +15 -35
  47. data/lib/graphql/schema/field/scope_extension.rb +22 -13
  48. data/lib/graphql/schema/field.rb +80 -48
  49. data/lib/graphql/schema/field_extension.rb +33 -0
  50. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
  51. data/lib/graphql/schema/member/has_arguments.rb +37 -14
  52. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  53. data/lib/graphql/schema/member/has_dataloader.rb +37 -0
  54. data/lib/graphql/schema/member/has_fields.rb +81 -4
  55. data/lib/graphql/schema/member.rb +5 -0
  56. data/lib/graphql/schema/object.rb +1 -0
  57. data/lib/graphql/schema/resolver.rb +45 -1
  58. data/lib/graphql/schema/validator/required_validator.rb +33 -2
  59. data/lib/graphql/schema/visibility.rb +3 -3
  60. data/lib/graphql/schema.rb +53 -10
  61. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +2 -2
  62. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  63. data/lib/graphql/subscriptions.rb +1 -1
  64. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  65. data/lib/graphql/testing.rb +1 -0
  66. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  67. data/lib/graphql/tracing/detailed_trace.rb +70 -7
  68. data/lib/graphql/tracing/perfetto_trace.rb +208 -78
  69. data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
  70. data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
  71. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  72. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  73. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  74. data/lib/graphql/unauthorized_error.rb +5 -1
  75. data/lib/graphql/version.rb +1 -1
  76. data/lib/graphql.rb +8 -2
  77. metadata +17 -3
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ class DetailedTrace
6
+ class ActiveRecordBackend
7
+ class GraphqlDetailedTrace < ActiveRecord::Base
8
+ end
9
+
10
+ def initialize(limit: nil, model_class: nil)
11
+ @limit = limit
12
+ @model_class = model_class || GraphqlDetailedTrace
13
+ end
14
+
15
+ def traces(last:, before:)
16
+ gdts = @model_class.all.order("begin_ms DESC")
17
+ if before
18
+ gdts = gdts.where("begin_ms < ?", before)
19
+ end
20
+ if last
21
+ gdts = gdts.limit(last)
22
+ end
23
+ gdts.map { |gdt| record_to_stored_trace(gdt) }
24
+ end
25
+
26
+ def delete_trace(id)
27
+ @model_class.where(id: id).destroy_all
28
+ nil
29
+ end
30
+
31
+ def delete_all_traces
32
+ @model_class.all.destroy_all
33
+ end
34
+
35
+ def find_trace(id)
36
+ gdt = @model_class.find_by(id: id)
37
+ if gdt
38
+ record_to_stored_trace(gdt)
39
+ else
40
+ nil
41
+ end
42
+ end
43
+
44
+ def save_trace(operation_name, duration_ms, begin_ms, trace_data)
45
+ gdt = @model_class.create!(
46
+ begin_ms: begin_ms,
47
+ operation_name: operation_name,
48
+ duration_ms: duration_ms,
49
+ trace_data: trace_data,
50
+ )
51
+ if @limit
52
+ @model_class
53
+ .where("id NOT IN(SELECT id FROM graphql_detailed_traces ORDER BY begin_ms DESC LIMIT ?)", @limit)
54
+ .delete_all
55
+ end
56
+ gdt.id
57
+ end
58
+
59
+ private
60
+
61
+ def record_to_stored_trace(gdt)
62
+ StoredTrace.new(
63
+ id: gdt.id,
64
+ begin_ms: gdt.begin_ms,
65
+ operation_name: gdt.operation_name,
66
+ duration_ms: gdt.duration_ms,
67
+ trace_data: gdt.trace_data
68
+ )
69
+
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,18 +1,34 @@
1
1
  # frozen_string_literal: true
2
+ if defined?(ActiveRecord)
3
+ require "graphql/tracing/detailed_trace/active_record_backend"
4
+ end
2
5
  require "graphql/tracing/detailed_trace/memory_backend"
3
6
  require "graphql/tracing/detailed_trace/redis_backend"
4
7
 
5
8
  module GraphQL
6
9
  module Tracing
7
- # `DetailedTrace` can make detailed profiles for a subset of production traffic.
10
+ # `DetailedTrace` can make detailed profiles for a subset of production traffic. Install it in Rails with `rails generate graphql:detailed_trace`.
8
11
  #
9
12
  # When `MySchema.detailed_trace?(query)` returns `true`, a profiler-specific `trace_mode: ...` will be used for the query,
10
13
  # overriding the one in `context[:trace_mode]`.
11
14
  #
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.
15
+ # By default, the detailed tracer calls `.inspect` on application objects returned from fields. You can customize
16
+ # this behavior by extending {DetailedTrace} and overriding {#inspect_object}. You can opt out of debug annotations
17
+ # entirely with `use ..., debug: false` or for a single query with `context: { detailed_trace_debug: false }`.
18
+ #
19
+ # You can store saved traces in two ways:
20
+ #
21
+ # - __ActiveRecord__: With `rails generate graphql:detailed_trace`, a new migration will be added to your app.
22
+ # That table will be used to store trace data.
23
+ #
24
+ # - __Redis__: Pass `redis: ...` to save trace data to a Redis database. Depending on your needs,
25
+ # you can configure this database to retain all data (persistent) or to expire data according to your rules.
26
+ #
14
27
  # If you need to save traces indefinitely, you can download them from Perfetto after opening them there.
15
28
  #
29
+ # @example Installing with Rails
30
+ # rails generate graphql:detailed_trace # optional: --redis
31
+ #
16
32
  # @example Adding the sampler to your schema
17
33
  # class MySchema < GraphQL::Schema
18
34
  # # Add the sampler:
@@ -27,24 +43,48 @@ module GraphQL
27
43
  # end
28
44
  #
29
45
  # @see Graphql::Dashboard GraphQL::Dashboard for viewing stored results
46
+ #
47
+ # @example Customizing debug output in traces
48
+ # class CustomDetailedTrace < GraphQL::Tracing::DetailedTrace
49
+ # def inspect_object(object)
50
+ # if object.is_a?(SomeThing)
51
+ # # handle it specially ...
52
+ # else
53
+ # super
54
+ # end
55
+ # end
56
+ # end
57
+ #
58
+ # @example disabling debug annotations completely
59
+ # use DetailedTrace, debug: false, ...
60
+ #
61
+ # @example disabling debug annotations for one query
62
+ # MySchema.execute(query_str, context: { detailed_trace_debug: false })
63
+ #
30
64
  class DetailedTrace
31
65
  # @param redis [Redis] If provided, profiles will be stored in Redis for later review
32
66
  # @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)
67
+ # @param debug [Boolean] if `false`, it won't create `debug` annotations in Perfetto traces (reduces overhead)
68
+ # @param model_class [Class<ActiveRecord::Base>] Overrides {ActiveRecordBackend::GraphqlDetailedTrace} if present
69
+ def self.use(schema, trace_mode: :profile_sample, memory: false, debug: debug?, redis: nil, limit: nil, model_class: nil)
34
70
  storage = if redis
35
71
  RedisBackend.new(redis: redis, limit: limit)
36
72
  elsif memory
37
73
  MemoryBackend.new(limit: limit)
74
+ elsif defined?(ActiveRecord)
75
+ ActiveRecordBackend.new(limit: limit, model_class: model_class)
38
76
  else
39
- raise ArgumentError, "Pass `redis: ...` to store traces in Redis for later review"
77
+ raise ArgumentError, "To store traces, install ActiveRecord or provide `redis: ...`"
40
78
  end
41
- schema.detailed_trace = self.new(storage: storage, trace_mode: trace_mode)
79
+ detailed_trace = self.new(storage: storage, trace_mode: trace_mode, debug: debug)
80
+ schema.detailed_trace = detailed_trace
42
81
  schema.trace_with(PerfettoTrace, mode: trace_mode, save_profile: true)
43
82
  end
44
83
 
45
- def initialize(storage:, trace_mode:)
84
+ def initialize(storage:, trace_mode:, debug:)
46
85
  @storage = storage
47
86
  @trace_mode = trace_mode
87
+ @debug = debug
48
88
  end
49
89
 
50
90
  # @return [Symbol] The trace mode to use when {Schema.detailed_trace?} returns `true`
@@ -55,6 +95,11 @@ module GraphQL
55
95
  @storage.save_trace(operation_name, duration_ms, begin_ms, trace_data)
56
96
  end
57
97
 
98
+ # @return [Boolean]
99
+ def debug?
100
+ @debug
101
+ end
102
+
58
103
  # @param last [Integer]
59
104
  # @param before [Integer] Timestamp in milliseconds since epoch
60
105
  # @return [Enumerable<StoredTrace>]
@@ -77,6 +122,24 @@ module GraphQL
77
122
  @storage.delete_all_traces
78
123
  end
79
124
 
125
+ def inspect_object(object)
126
+ self.class.inspect_object(object)
127
+ end
128
+
129
+ def self.inspect_object(object)
130
+ if defined?(ActiveRecord::Relation) && object.is_a?(ActiveRecord::Relation)
131
+ "#{object.class}, .to_sql=#{object.to_sql.inspect}"
132
+ else
133
+ object.inspect
134
+ end
135
+ end
136
+
137
+ # Default debug setting
138
+ # @return [true]
139
+ def self.debug?
140
+ true
141
+ end
142
+
80
143
  class StoredTrace
81
144
  def initialize(id:, operation_name:, duration_ms:, begin_ms:, trace_data:)
82
145
  @id = id