graphql 2.4.15 → 2.5.0

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  3. data/lib/graphql/dashboard/installable.rb +22 -0
  4. data/lib/graphql/dashboard/limiters.rb +93 -0
  5. data/lib/graphql/dashboard/operation_store.rb +199 -0
  6. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  7. data/lib/graphql/dashboard/statics/dashboard.css +27 -0
  8. data/lib/graphql/dashboard/statics/dashboard.js +74 -9
  9. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  10. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  11. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  12. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  13. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  14. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  15. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  16. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  17. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  18. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  19. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  20. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  21. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  22. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  23. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  24. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +49 -1
  25. data/lib/graphql/dashboard.rb +45 -29
  26. data/lib/graphql/execution/interpreter.rb +3 -5
  27. data/lib/graphql/execution/multiplex.rb +1 -1
  28. data/lib/graphql/language/parser.rb +13 -6
  29. data/lib/graphql/query.rb +2 -4
  30. data/lib/graphql/static_validation/all_rules.rb +1 -1
  31. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  32. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  33. data/lib/graphql/tracing/active_support_notifications_trace.rb +7 -0
  34. data/lib/graphql/tracing/appoptics_tracing.rb +5 -0
  35. data/lib/graphql/tracing/appsignal_trace.rb +26 -61
  36. data/lib/graphql/tracing/data_dog_trace.rb +41 -164
  37. data/lib/graphql/tracing/monitor_trace.rb +285 -0
  38. data/lib/graphql/tracing/new_relic_trace.rb +34 -166
  39. data/lib/graphql/tracing/notifications_trace.rb +181 -37
  40. data/lib/graphql/tracing/perfetto_trace.rb +15 -18
  41. data/lib/graphql/tracing/prometheus_trace.rb +47 -74
  42. data/lib/graphql/tracing/scout_trace.rb +25 -59
  43. data/lib/graphql/tracing/sentry_trace.rb +57 -99
  44. data/lib/graphql/tracing/statsd_trace.rb +24 -47
  45. data/lib/graphql/tracing/trace.rb +0 -17
  46. data/lib/graphql/tracing.rb +1 -0
  47. data/lib/graphql/version.rb +1 -1
  48. metadata +25 -4
  49. data/lib/graphql/dashboard/views/graphql/dashboard/traces/index.html.erb +0 -63
  50. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -0,0 +1,71 @@
1
+ <% content_for(:title, "View #{params[:digest]}") %>
2
+ <% if @operation.nil? %>
3
+ <div class="row">
4
+ <div class="col">
5
+ <p>No stored operation found for <code><%= params[:digest] %></code>
6
+ </div>
7
+ </div>
8
+ <% else %>
9
+ <div class="row">
10
+ <div class="col">
11
+ <h2>
12
+ <%= @operation.name %>
13
+ <% if @operation.is_archived %><small> (archived)</small><% end %>
14
+ </h2>
15
+ </div>
16
+ </div>
17
+ <div class="row mt-3">
18
+ <div class="col">
19
+ <p>Aliases</p>
20
+ <% if @client_operations.empty? %>
21
+ <p><em>None</em></p>
22
+ <% else %>
23
+ <ul>
24
+ <% @client_operations.each do |cl_op| %>
25
+ <li>
26
+ <code><%= cl_op.operation_alias %></code>
27
+ <%= link_to(cl_op.client_name, graphql_dashboard.operation_store_client_operations_path(client_name: cl_op.client_name)) %>
28
+ <%= cl_op.is_archived ? " (archived)" : "" %>
29
+ </li>
30
+ <% end %>
31
+ </ul>
32
+ <% end %>
33
+ </div>
34
+ </div>
35
+ <div class="row">
36
+ <div class="col">
37
+ <p>Last Used At</p>
38
+ <p><code><%= @operation.last_used_at %></code></p>
39
+ </div>
40
+ </div>
41
+ <div class="row">
42
+ <div class="col">
43
+ <p>Source</p>
44
+ <%= textarea_tag "_source", @graphql_source, class: "graphql-highlight form-control", disabled: true, rows: @graphql_source.count("\n") + 1 %>
45
+ </div>
46
+ </div>
47
+ <div class="row mt-3">
48
+ <div class="col">
49
+ <p>References</p>
50
+ <ul>
51
+ <% @entries.each do |entry| %>
52
+ <li>
53
+ <%= link_to(entry.name, graphql_dashboard.operation_store_index_entry_path(name: entry.name)) %>
54
+ </li>
55
+ <% end %>
56
+ </ul>
57
+ </div>
58
+ </div>
59
+ <div class="row mt-3">
60
+ <div class="col">
61
+ <p>Digest</p>
62
+ <p><code><%= @operation.digest %></code></p>
63
+ </div>
64
+ </div>
65
+ <div class="row mt-3">
66
+ <div class="col">
67
+ <p>Minified Source</p>
68
+ <%= textarea_tag "_source", @operation.body, class: "graphql-highlight form-control", disabled: true, rows: @operation.body.count("\n") + 1 %>
69
+ </div>
70
+ </div>
71
+ <% end %>
@@ -0,0 +1,41 @@
1
+ <% content_for(:title, "Subscription #{params[:id]}") %>
2
+ <div class="row mt-3">
3
+ <div class="col">
4
+ <h3>Subscription: <code><%= params[:id] %></code></h3>
5
+ </div>
6
+ </div>
7
+
8
+ <% if @query_data.nil? %>
9
+ <div class="row">
10
+ <div class="col">
11
+ <p class="muted"><i>This subscription was not found or is no longer active.</i></p>
12
+ </div>
13
+ </div>
14
+ <% else %>
15
+ <div class="row">
16
+ <div class="col">
17
+ <p>Created at <%= @query_data[:created_at] %>, last triggered at <%= @query_data[:last_triggered_at] || "--" %></p>
18
+
19
+ <p>Subscribed? <code><%= @still_subscribed ? "YES" : "NO" %></code></p>
20
+ <p>Broadcast? <code><%= @is_broadcast ? "YES" : "NO" %></code> <% if @is_broadcast %>
21
+ <small class="muted"><% if @subscribers_count.nil? %>
22
+ This subscription may have multiple subscribers.
23
+ <% else %>
24
+ (<%= pluralize(@subscribers_count, "subscriber") %>)
25
+ <% end %></small>
26
+ <% end %></p>
27
+
28
+ <p>Context:</p>
29
+ <pre><%= @query_data[:context].inspect %></pre>
30
+
31
+ <p>Variables:</p>
32
+ <pre><%= @query_data[:variables].inspect %></pre>
33
+
34
+ <p>Operation Name:</p>
35
+ <pre><%= @query_data[:operation_name].inspect %></pre>
36
+
37
+ <p>Query String:</p>
38
+ <%= textarea_tag "_source", @query_data[:query_string], class: "graphql-highlight form-control", disabled: true, rows: @query_data[:query_string].count("\n") + 1 %>
39
+ </div>
40
+ </div>
41
+ <% end %>
@@ -0,0 +1,55 @@
1
+ <% content_for(:title, "Subscriptions - Topics") %>
2
+ <div class="row mt-3 justify-content-between">
3
+ <div class="col-auto">
4
+ <h3>
5
+ <%= pluralize(@all_topics_count, "Subscription Topic") %>
6
+ </h3>
7
+ </div>
8
+ <div class="col-auto">
9
+ <%= button_tag "Clear All", class: "btn btn-outline-danger", data: { subscriptions_delete_all: graphql_dashboard.subscriptions_clear_all_path } %>
10
+ </div>
11
+ </div>
12
+
13
+ <table class="table table-striped">
14
+ <thead>
15
+ <tr>
16
+ <th>Name</th>
17
+ <th># Subscriptions</th>
18
+ <th>Last Triggered At</th>
19
+ </tr>
20
+ </thead>
21
+ <tbody>
22
+ <% if @all_topics_count == 0 %>
23
+ <tr>
24
+ <td colspan="3" class="text-center">
25
+ <em>There aren't any subscriptions right now.</em>
26
+ </td>
27
+ </tr>
28
+ <% else %>
29
+ <% @topics.each do |topic| %>
30
+ <tr>
31
+ <td><%= link_to(topic.name, graphql_dashboard.subscriptions_topic_path(name: topic.name)) %></td>
32
+ <td><%= topic.subscriptions_count %></td>
33
+ <td><%= topic.last_triggered_at || "--" %></td>
34
+ </tr>
35
+ <% end %>
36
+ <% end %>
37
+ </tbody>
38
+ </table>
39
+
40
+ <div class="row">
41
+ <div class="col-auto">
42
+ <% if @page > 1 %>
43
+ <%= link_to("« prev", graphql_dashboard.subscriptions_topics_path(per_page: params[:per_page], page: @page - 1), class: "btn btn-outline-secondary") %>
44
+ <% else %>
45
+ <button class="btn btn-outline-secondary" disabled>« prev</button>
46
+ <% end %>
47
+ </div>
48
+ <div class="col-auto">
49
+ <% if @has_next_page %>
50
+ <%= link_to("next »", graphql_dashboard.subscriptions_topics_path(per_page: params[:per_page], page: @page + 1), class: "btn btn-outline-secondary") %>
51
+ <% else %>
52
+ <button class="btn btn-outline-secondary" disabled>next »</button>
53
+ <% end %>
54
+ </div>
55
+ </div>
@@ -0,0 +1,40 @@
1
+ <%= content_for(:title, "Subscriptions - #{params[:name]}") %>
2
+ <div class="row mt-3">
3
+ <div class="col">
4
+ <h3>Topic: <code><%= params[:name] %></code></h3>
5
+ </div>
6
+ </div>
7
+
8
+ <div class="row">
9
+ <div class="col">
10
+ <p>Last triggered: <%= @topic_last_triggered_at || "none" %></p>
11
+ <p><%= pluralize(@subscriptions_count, "Subscription") %></p>
12
+ <div>
13
+ </div>
14
+
15
+ <div class="row">
16
+ <div class="col">
17
+ <table class="table table-striped">
18
+ <thead>
19
+ <tr>
20
+ <th>Subscription ID</th>
21
+ <th>Created At</th>
22
+ <th>Subscribed?</th>
23
+ <th>Broadcast?</th>
24
+ <% if @show_broadcast_subscribers_count %><th>Subscribers</th><% end %>
25
+ </tr>
26
+ </thead>
27
+ <tbody>
28
+ <% @subscriptions.each do |subscription| %>
29
+ <tr>
30
+ <td><%= link_to(subscription[:id], graphql_dashboard.subscriptions_subscription_path(subscription[:id])) %></td>
31
+ <td><%= subscription[:created_at] %></td>
32
+ <td><code><%= subscription[:still_subscribed] ? "YES" : "NO" %></code></td>
33
+ <td><code><%= subscription[:is_broadcast] ? "YES" : "NO" %></code></td>
34
+ <% if @show_broadcast_subscribers_count %><td><%= subscription[:subscribers_count] %></td><% end %>
35
+ </tr>
36
+ <% end %>
37
+ </tbody>
38
+ </table>
39
+ </div>
40
+ </div>
@@ -6,6 +6,7 @@
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1">
7
7
  <title>GraphQL Dashboard <%= content_for?(:title) ? " · #{content_for(:title)}" : "" %> </title>
8
8
  <%= stylesheet_link_tag graphql_dashboard.static_path("bootstrap-5.3.3.min.css") %>
9
+ <%= stylesheet_link_tag graphql_dashboard.static_path("charts.min.css") %>
9
10
  <%= stylesheet_link_tag graphql_dashboard.static_path("dashboard.css") %>
10
11
  <%= javascript_include_tag graphql_dashboard.static_path("bootstrap-5.3.3.min.js") %>
11
12
  <%= javascript_include_tag graphql_dashboard.static_path("dashboard.js") %>
@@ -27,7 +28,44 @@
27
28
  <div class="collapse navbar-collapse" id="navbarSupportedContent">
28
29
  <ul class="navbar-nav me-auto mb-2 mb-lg-0">
29
30
  <li class="nav-item">
30
- <%= link_to "Traces", graphql_dashboard.traces_path, class: "nav-link #{params[:controller] == "graphql/dashboard/traces" ? "active" : ""}" %>
31
+ <%= link_to "Traces", graphql_dashboard.detailed_traces_traces_path, class: "nav-link #{params[:controller] == "graphql/dashboard/detailed_traces" ? "active" : ""}" %>
32
+ </li>
33
+ <li class="nav-item dropdown">
34
+ <a class="nav-link dropdown-toggle <%= params[:controller].include?("operation_store") ? "active" : "" %>" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
35
+ Persisted Operations
36
+ </a>
37
+ <ul class="dropdown-menu">
38
+ <li>
39
+ <%= link_to "Clients", graphql_dashboard.operation_store_clients_path, class: "dropdown-item" %>
40
+ </li>
41
+ <li>
42
+ <%= link_to "Operations", graphql_dashboard.operation_store_operations_path, class: "dropdown-item" %>
43
+ </li>
44
+ <li>
45
+ <%= link_to "Index", graphql_dashboard.operation_store_index_entries_path, class: "dropdown-item" %>
46
+ </li>
47
+ </ul>
48
+ </li>
49
+ <li class="nav-item">
50
+ <%= link_to "Subscriptions", graphql_dashboard.subscriptions_topics_path, class: "nav-link #{params[:controller] == "graphql/dashboard/subscriptions" ? "active" : ""}" %>
51
+ </li>
52
+ <li class="nav-item dropdown">
53
+ <a class="nav-link dropdown-toggle <%= params[:controller].include?("limiters") ? "active" : "" %>" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
54
+ Rate Limiters
55
+ </a>
56
+ <ul class="dropdown-menu">
57
+ <li>
58
+ <%= link_to "Runtime", graphql_dashboard.limiters_limiter_path("runtime", chart: params[:chart]), class: "dropdown-item" %>
59
+ </li>
60
+ <li>
61
+ <%= link_to "Active Operations", graphql_dashboard.limiters_limiter_path("active_operations", chart: params[:chart]), class: "dropdown-item" %>
62
+ </li>
63
+ <% if schema_class.respond_to?(:enterprise_mutation_limiter) && schema_class.enterprise_mutation_limiter %>
64
+ <li>
65
+ <%= link_to "Mutations", graphql_dashboard.limiters_limiter_path("mutations", chart: params[:chart]), class: "dropdown-item" %>
66
+ </li>
67
+ <% end %>
68
+ </ul>
31
69
  </li>
32
70
  </ul>
33
71
  <span class="navbar-text pe-2">
@@ -38,6 +76,16 @@
38
76
  </nav>
39
77
  </div>
40
78
  </div>
79
+ <% flash.each do |flash_type, flash_message| %>
80
+ <div class="row mt-2">
81
+ <div class="col">
82
+ <div class="alert alert-<%= flash_type %> alert-dismissible fade show" role="alert">
83
+ <%= flash_message %>
84
+ <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ <% end %>
41
89
  </div>
42
90
  <div class="container-fluid">
43
91
  <%= yield %>
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  require 'rails/engine'
3
-
4
3
  module Graphql
5
4
  # `GraphQL::Dashboard` is a `Rails::Engine`-based dashboard for viewing metadata about your GraphQL schema.
6
5
  #
@@ -38,8 +37,45 @@ module Graphql
38
37
  routes.draw do
39
38
  root "landings#show"
40
39
  resources :statics, only: :show, constraints: { id: /[0-9A-Za-z\-.]+/ }
41
- delete "/traces/delete_all", to: "traces#delete_all", as: :traces_delete_all
42
- resources :traces, only: [:index, :show, :destroy]
40
+
41
+ namespace :detailed_traces do
42
+ resources :traces, only: [:index, :show, :destroy] do
43
+ collection do
44
+ delete :delete_all, to: "traces#delete_all", as: :delete_all
45
+ end
46
+ end
47
+ end
48
+
49
+ namespace :limiters do
50
+ resources :limiters, only: [:show, :update], param: :name
51
+ end
52
+
53
+ namespace :operation_store do
54
+ resources :clients, param: :name do
55
+ resources :operations, param: :digest, only: [:index] do
56
+ collection do
57
+ get :archived, to: "operations#index", archived_status: :archived, as: :archived
58
+ post :archive, to: "operations#update", modification: :archive, as: :archive
59
+ post :unarchive, to: "operations#update", modification: :unarchive, as: :unarchive
60
+ end
61
+ end
62
+ end
63
+
64
+ resources :operations, param: :digest, only: [:index, :show] do
65
+ collection do
66
+ get :archived, to: "operations#index", archived_status: :archived, as: :archived
67
+ post :archive, to: "operations#update", modification: :archive, as: :archive
68
+ post :unarchive, to: "operations#update", modification: :unarchive, as: :unarchive
69
+ end
70
+ end
71
+ resources :index_entries, only: [:index, :show], param: :name, constraints: { name: /[A-Za-z0-9_.]+/}
72
+ end
73
+
74
+ namespace :subscriptions do
75
+ resources :topics, only: [:index, :show], param: :name, constraints: { name: /.*/ }
76
+ resources :subscriptions, only: [:show], constraints: { id: /[a-zA-Z0-9\-]+/ }
77
+ post "/subscriptions/clear_all", to: "subscriptions#clear_all", as: :clear_all
78
+ end
43
79
  end
44
80
 
45
81
  class ApplicationController < ActionController::Base
@@ -80,32 +116,6 @@ module Graphql
80
116
  end
81
117
  end
82
118
 
83
- class TracesController < ApplicationController
84
- def index
85
- @detailed_trace_installed = !!schema_class.detailed_trace
86
- if @detailed_trace_installed
87
- @last = params[:last]&.to_i || 50
88
- @before = params[:before]&.to_i
89
- @traces = schema_class.detailed_trace.traces(last: @last, before: @before)
90
- end
91
- end
92
-
93
- def show
94
- trace = schema_class.detailed_trace.find_trace(params[:id].to_i)
95
- send_data(trace.trace_data)
96
- end
97
-
98
- def destroy
99
- schema_class.detailed_trace.delete_trace(params[:id])
100
- head :no_content
101
- end
102
-
103
- def delete_all
104
- schema_class.detailed_trace.delete_all_traces
105
- head :no_content
106
- end
107
- end
108
-
109
119
  class StaticsController < ApplicationController
110
120
  skip_after_action :verify_same_origin_request
111
121
  # Use an explicit list of files to avoid any chance of reading other files from disk
@@ -114,6 +124,7 @@ module Graphql
114
124
  [
115
125
  "icon.png",
116
126
  "header-icon.png",
127
+ "charts.min.css",
117
128
  "dashboard.css",
118
129
  "dashboard.js",
119
130
  "bootstrap-5.3.3.min.css",
@@ -134,6 +145,11 @@ module Graphql
134
145
  end
135
146
  end
136
147
 
148
+ require 'graphql/dashboard/detailed_traces'
149
+ require 'graphql/dashboard/limiters'
150
+ require 'graphql/dashboard/operation_store'
151
+ require 'graphql/dashboard/subscriptions'
152
+
137
153
  # Rails expects the engine to be called `Graphql::Dashboard`,
138
154
  # but `GraphQL::Dashboard` is consistent with this gem's naming.
139
155
  # So define both constants to refer to the same class.
@@ -23,7 +23,7 @@ module GraphQL
23
23
  # @return [Array<GraphQL::Query::Result>] One result per query
24
24
  def run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
25
25
  queries = query_options.map do |opts|
26
- case opts
26
+ query = case opts
27
27
  when Hash
28
28
  schema.query_class.new(schema, nil, **opts)
29
29
  when GraphQL::Query
@@ -31,14 +31,14 @@ module GraphQL
31
31
  else
32
32
  raise "Expected Hash or GraphQL::Query, not #{opts.class} (#{opts.inspect})"
33
33
  end
34
+ query
34
35
  end
35
36
 
36
37
  return GraphQL::EmptyObjects::EMPTY_ARRAY if queries.empty?
37
38
 
38
39
  multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
39
- Fiber[:__graphql_current_multiplex] = multiplex
40
40
  trace = multiplex.current_trace
41
- trace.begin_execute_multiplex(multiplex)
41
+ Fiber[:__graphql_current_multiplex] = multiplex
42
42
  trace.execute_multiplex(multiplex: multiplex) do
43
43
  schema = multiplex.schema
44
44
  queries = multiplex.queries
@@ -155,8 +155,6 @@ module GraphQL
155
155
  }
156
156
  end
157
157
  end
158
- ensure
159
- trace&.end_execute_multiplex(multiplex)
160
158
  end
161
159
  end
162
160
 
@@ -32,10 +32,10 @@ module GraphQL
32
32
  @queries = queries
33
33
  @queries.each { |q| q.multiplex = self }
34
34
  @context = context
35
- @current_trace = @context[:trace] || schema.new_trace(multiplex: self)
36
35
  @dataloader = @context[:dataloader] ||= @schema.dataloader_class.new
37
36
  @tracers = schema.tracers + (context[:tracers] || [])
38
37
  @max_complexity = max_complexity
38
+ @current_trace = context[:trace] ||= schema.new_trace(multiplex: self)
39
39
  end
40
40
  end
41
41
  end
@@ -488,26 +488,33 @@ module GraphQL
488
488
  end
489
489
 
490
490
  def type
491
- type = case token_name
491
+ parsed_type = case token_name
492
492
  when :IDENTIFIER
493
493
  parse_type_name
494
494
  when :LBRACKET
495
495
  list_type
496
+ else
497
+ nil
496
498
  end
497
499
 
498
- if at?(:BANG)
499
- type = Nodes::NonNullType.new(pos: pos, of_type: type, source: self)
500
+ if at?(:BANG) && parsed_type
501
+ parsed_type = Nodes::NonNullType.new(pos: pos, of_type: parsed_type, source: self)
500
502
  expect_token(:BANG)
501
503
  end
502
- type
504
+ parsed_type
503
505
  end
504
506
 
505
507
  def list_type
506
508
  loc = pos
507
509
  expect_token(:LBRACKET)
508
- type = Nodes::ListType.new(pos: loc, of_type: self.type, source: self)
510
+ inner_type = self.type
511
+ parsed_list_type = if inner_type
512
+ Nodes::ListType.new(pos: loc, of_type: inner_type, source: self)
513
+ else
514
+ nil
515
+ end
509
516
  expect_token(:RBRACKET)
510
- type
517
+ parsed_list_type
511
518
  end
512
519
 
513
520
  def parse_operation_type
data/lib/graphql/query.rb CHANGED
@@ -98,9 +98,10 @@ module GraphQL
98
98
  # @param max_depth [Numeric] the maximum number of nested selections allowed for this query (falls back to schema-level value)
99
99
  # @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value)
100
100
  # @param visibility_profile [Symbol] Another way to assign `context[:visibility_profile]`
101
- def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, static_validator: nil, visibility_profile: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, warden: nil, use_visibility_profile: nil)
101
+ def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, multiplex: nil, validate: true, static_validator: nil, visibility_profile: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, warden: nil, use_visibility_profile: nil)
102
102
  # Even if `variables: nil` is passed, use an empty hash for simpler logic
103
103
  variables ||= {}
104
+ @multiplex = multiplex
104
105
  @schema = schema
105
106
  @context = schema.context_class.new(query: self, values: context)
106
107
  if visibility_profile
@@ -441,7 +442,6 @@ module GraphQL
441
442
  @warden ||= @schema.warden_class.new(schema: @schema, context: @context)
442
443
  parse_error = nil
443
444
  @document ||= begin
444
- current_trace.begin_parse(query_string)
445
445
  if query_string
446
446
  GraphQL.parse(query_string, trace: self.current_trace, max_tokens: @schema.max_query_string_tokens)
447
447
  end
@@ -449,8 +449,6 @@ module GraphQL
449
449
  parse_error = err
450
450
  @schema.parse_error(err, @context)
451
451
  nil
452
- ensure
453
- current_trace.end_parse(query_string)
454
452
  end
455
453
 
456
454
  @fragments = {}
@@ -34,7 +34,7 @@ module GraphQL
34
34
  GraphQL::StaticValidation::VariableUsagesAreAllowed,
35
35
  GraphQL::StaticValidation::MutationRootExists,
36
36
  GraphQL::StaticValidation::QueryRootExists,
37
- GraphQL::StaticValidation::SubscriptionRootExists,
37
+ GraphQL::StaticValidation::SubscriptionRootExistsAndSingleSubscriptionSelection,
38
38
  GraphQL::StaticValidation::InputObjectNamesAreUnique,
39
39
  GraphQL::StaticValidation::OneOfInputObjectsAreValid,
40
40
  ]
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ class NotSingleSubscriptionError < StaticValidation::Error
5
+ def initialize(message, path: nil, nodes: [])
6
+ super(message, path: path, nodes: nodes)
7
+ end
8
+
9
+ # A hash representation of this Message
10
+ def to_h
11
+ extensions = {
12
+ "code" => code,
13
+ }
14
+
15
+ super.merge({
16
+ "extensions" => extensions
17
+ })
18
+ end
19
+
20
+ def code
21
+ "notSingleSubscription"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ module SubscriptionRootExistsAndSingleSubscriptionSelection
5
+ def on_operation_definition(node, parent)
6
+ if node.operation_type == "subscription"
7
+ if context.types.subscription_root.nil?
8
+ add_error(GraphQL::StaticValidation::SubscriptionRootExistsError.new(
9
+ 'Schema is not configured for subscriptions',
10
+ nodes: node
11
+ ))
12
+ elsif node.selections.size != 1
13
+ add_error(GraphQL::StaticValidation::NotSingleSubscriptionError.new(
14
+ 'A subscription operation may only have one selection',
15
+ nodes: node,
16
+ ))
17
+ else
18
+ super
19
+ end
20
+ else
21
+ super
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -10,6 +10,13 @@ module GraphQL
10
10
  # class MySchema < GraphQL::Schema
11
11
  # trace_with(GraphQL::Tracing::ActiveSupportNotificationsTrace)
12
12
  # end
13
+ #
14
+ # @example Subscribing to GraphQL events with ActiveSupport::Notifications
15
+ # ActiveSupport::Notifications.subscribe(/graphql/) do |event|
16
+ # pp event.name
17
+ # pp event.payload
18
+ # end
19
+ #
13
20
  module ActiveSupportNotificationsTrace
14
21
  include NotificationsTrace
15
22
  def initialize(engine: ActiveSupport::Notifications, **rest)
@@ -22,6 +22,11 @@ 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
+ def initialize(...)
26
+ warn "GraphQL::Tracing::AppOptics tracing is deprecated; update to SolarWindsAPM instead, which uses OpenTelemetry."
27
+ super
28
+ end
29
+
25
30
  # During auto-instrumentation this version of AppOpticsTracing is compared
26
31
  # with the version provided in the appoptics_apm gem, so that the newer
27
32
  # version of the class can be used