graphql 2.4.5 → 2.5.21

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 (192) 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/analysis/analyzer.rb +2 -1
  5. data/lib/graphql/analysis/query_complexity.rb +87 -7
  6. data/lib/graphql/analysis/visitor.rb +37 -40
  7. data/lib/graphql/analysis.rb +12 -9
  8. data/lib/graphql/autoload.rb +1 -0
  9. data/lib/graphql/backtrace/table.rb +118 -55
  10. data/lib/graphql/backtrace.rb +1 -19
  11. data/lib/graphql/current.rb +6 -1
  12. data/lib/graphql/dashboard/application_controller.rb +41 -0
  13. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  14. data/lib/graphql/dashboard/installable.rb +22 -0
  15. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  16. data/lib/graphql/dashboard/limiters.rb +93 -0
  17. data/lib/graphql/dashboard/operation_store.rb +199 -0
  18. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  19. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  20. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  21. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  22. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  23. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  24. data/lib/graphql/dashboard/statics/icon.png +0 -0
  25. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  26. data/lib/graphql/dashboard/subscriptions.rb +97 -0
  27. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  28. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  29. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  30. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  31. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +24 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  36. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  37. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  38. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  39. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  40. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  41. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  42. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  43. data/lib/graphql/dashboard.rb +96 -0
  44. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  45. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  46. data/lib/graphql/dataloader/async_dataloader.rb +38 -15
  47. data/lib/graphql/dataloader/null_dataloader.rb +55 -10
  48. data/lib/graphql/dataloader/source.rb +18 -6
  49. data/lib/graphql/dataloader.rb +110 -26
  50. data/lib/graphql/date_encoding_error.rb +1 -1
  51. data/lib/graphql/dig.rb +2 -1
  52. data/lib/graphql/execution/interpreter/resolve.rb +10 -16
  53. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +58 -5
  54. data/lib/graphql/execution/interpreter/runtime.rb +229 -93
  55. data/lib/graphql/execution/interpreter.rb +15 -24
  56. data/lib/graphql/execution/multiplex.rb +7 -6
  57. data/lib/graphql/execution/next/field_resolve_step.rb +690 -0
  58. data/lib/graphql/execution/next/load_argument_step.rb +60 -0
  59. data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
  60. data/lib/graphql/execution/next/runner.rb +389 -0
  61. data/lib/graphql/execution/next/selections_step.rb +37 -0
  62. data/lib/graphql/execution/next.rb +69 -0
  63. data/lib/graphql/execution.rb +1 -0
  64. data/lib/graphql/execution_error.rb +13 -10
  65. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  66. data/lib/graphql/introspection/directive_type.rb +7 -3
  67. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  68. data/lib/graphql/introspection/entry_points.rb +11 -3
  69. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  70. data/lib/graphql/introspection/field_type.rb +13 -5
  71. data/lib/graphql/introspection/input_value_type.rb +21 -13
  72. data/lib/graphql/introspection/type_type.rb +64 -28
  73. data/lib/graphql/invalid_name_error.rb +1 -1
  74. data/lib/graphql/invalid_null_error.rb +25 -16
  75. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  76. data/lib/graphql/language/lexer.rb +16 -5
  77. data/lib/graphql/language/nodes.rb +8 -1
  78. data/lib/graphql/language/parser.rb +16 -8
  79. data/lib/graphql/language/static_visitor.rb +37 -33
  80. data/lib/graphql/language/visitor.rb +59 -55
  81. data/lib/graphql/language.rb +21 -12
  82. data/lib/graphql/pagination/connection.rb +2 -0
  83. data/lib/graphql/pagination/connections.rb +32 -0
  84. data/lib/graphql/query/context.rb +6 -10
  85. data/lib/graphql/query/null_context.rb +9 -3
  86. data/lib/graphql/query/partial.rb +179 -0
  87. data/lib/graphql/query.rb +64 -64
  88. data/lib/graphql/railtie.rb +1 -1
  89. data/lib/graphql/schema/addition.rb +3 -1
  90. data/lib/graphql/schema/always_visible.rb +1 -0
  91. data/lib/graphql/schema/argument.rb +24 -8
  92. data/lib/graphql/schema/build_from_definition.rb +113 -54
  93. data/lib/graphql/schema/directive/flagged.rb +2 -0
  94. data/lib/graphql/schema/directive.rb +52 -2
  95. data/lib/graphql/schema/enum.rb +36 -1
  96. data/lib/graphql/schema/enum_value.rb +1 -1
  97. data/lib/graphql/schema/field/connection_extension.rb +15 -35
  98. data/lib/graphql/schema/field/scope_extension.rb +22 -13
  99. data/lib/graphql/schema/field.rb +101 -51
  100. data/lib/graphql/schema/field_extension.rb +33 -0
  101. data/lib/graphql/schema/input_object.rb +45 -38
  102. data/lib/graphql/schema/interface.rb +2 -1
  103. data/lib/graphql/schema/list.rb +1 -1
  104. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
  105. data/lib/graphql/schema/member/has_arguments.rb +56 -19
  106. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  107. data/lib/graphql/schema/member/has_dataloader.rb +79 -0
  108. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  109. data/lib/graphql/schema/member/has_directives.rb +1 -1
  110. data/lib/graphql/schema/member/has_fields.rb +81 -5
  111. data/lib/graphql/schema/member/has_interfaces.rb +3 -3
  112. data/lib/graphql/schema/member/scoped.rb +1 -1
  113. data/lib/graphql/schema/member/type_system_helpers.rb +17 -3
  114. data/lib/graphql/schema/member.rb +6 -0
  115. data/lib/graphql/schema/object.rb +18 -8
  116. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  117. data/lib/graphql/schema/resolver.rb +52 -6
  118. data/lib/graphql/schema/scalar.rb +1 -6
  119. data/lib/graphql/schema/subscription.rb +50 -4
  120. data/lib/graphql/schema/timeout.rb +19 -2
  121. data/lib/graphql/schema/validator/required_validator.rb +71 -14
  122. data/lib/graphql/schema/visibility/migration.rb +3 -2
  123. data/lib/graphql/schema/visibility/profile.rb +115 -23
  124. data/lib/graphql/schema/visibility.rb +49 -32
  125. data/lib/graphql/schema/warden.rb +23 -2
  126. data/lib/graphql/schema.rb +333 -68
  127. data/lib/graphql/static_validation/all_rules.rb +2 -2
  128. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  129. data/lib/graphql/static_validation/rules/fields_will_merge.rb +79 -17
  130. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  131. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  132. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  133. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +6 -2
  134. data/lib/graphql/static_validation/validator.rb +6 -1
  135. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  136. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  137. data/lib/graphql/subscriptions/event.rb +12 -1
  138. data/lib/graphql/subscriptions/serialize.rb +1 -1
  139. data/lib/graphql/subscriptions.rb +1 -1
  140. data/lib/graphql/testing/helpers.rb +17 -11
  141. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  142. data/lib/graphql/testing.rb +1 -0
  143. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  144. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  145. data/lib/graphql/tracing/appoptics_trace.rb +9 -1
  146. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  147. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  148. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  149. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  150. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  151. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  152. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  153. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  154. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  155. data/lib/graphql/tracing/detailed_trace.rb +156 -0
  156. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  157. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  158. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  159. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  160. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  161. data/lib/graphql/tracing/notifications_trace.rb +184 -34
  162. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  163. data/lib/graphql/tracing/null_trace.rb +9 -0
  164. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  165. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  166. data/lib/graphql/tracing/perfetto_trace.rb +864 -0
  167. data/lib/graphql/tracing/platform_trace.rb +5 -0
  168. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  169. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  170. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  171. data/lib/graphql/tracing/scout_trace.rb +32 -55
  172. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  173. data/lib/graphql/tracing/sentry_trace.rb +64 -94
  174. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  175. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  176. data/lib/graphql/tracing/trace.rb +111 -1
  177. data/lib/graphql/tracing.rb +31 -30
  178. data/lib/graphql/type_kinds.rb +1 -0
  179. data/lib/graphql/types/relay/connection_behaviors.rb +9 -7
  180. data/lib/graphql/types/relay/edge_behaviors.rb +5 -4
  181. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  182. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  183. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  184. data/lib/graphql/unauthorized_error.rb +5 -1
  185. data/lib/graphql/version.rb +1 -1
  186. data/lib/graphql.rb +12 -31
  187. metadata +174 -11
  188. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  189. data/lib/graphql/backtrace/trace.rb +0 -93
  190. data/lib/graphql/backtrace/tracer.rb +0 -80
  191. data/lib/graphql/schema/null_mask.rb +0 -11
  192. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -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>
@@ -0,0 +1,108 @@
1
+ <!doctype html>
2
+ <html lang="en" class="h-100" >
3
+ <head>
4
+ <link rel="icon" type="image/png" href="<%= graphql_dashboard.static_path("icon.png") %>">
5
+ <meta charset="utf-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <title>GraphQL Dashboard <%= content_for?(:title) ? " · #{content_for(:title)}" : "" %> </title>
8
+ <link rel="stylesheet" href="<%= graphql_dashboard.static_path("bootstrap-5.3.3.min.css") %>" media="screen">
9
+ <link rel="stylesheet" href="<%= graphql_dashboard.static_path("charts.min.css") %>" media="screen">
10
+ <link rel="stylesheet" href="<%= graphql_dashboard.static_path("dashboard.css") %>" media="screen">
11
+ <script src="<%= graphql_dashboard.static_path("bootstrap-5.3.3.min.js") %>"></script>
12
+ <script src="<%= graphql_dashboard.static_path("dashboard.js") %>"></script>
13
+ <%= csrf_meta_tags %>
14
+ </head>
15
+ <body class="h-100 d-flex flex-column">
16
+ <main class="flex-shrink-0">
17
+ <div class="container-fluid">
18
+ <div class="row">
19
+ <div class="col gx-0">
20
+ <nav class="navbar navbar-expand-lg bg-body-tertiary">
21
+ <div class="container-fluid">
22
+ <%= link_to graphql_dashboard.root_path, class: "navbar-brand" do %>
23
+ <img id="header-icon" src="<%= graphql_dashboard.static_path("header-icon.png") %>" alt="GraphQL-Ruby">
24
+ <% end %>
25
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
26
+ <span class="navbar-toggler-icon"></span>
27
+ </button>
28
+ <div class="collapse navbar-collapse" id="navbarSupportedContent">
29
+ <ul class="navbar-nav me-auto mb-2 mb-lg-0">
30
+ <li class="nav-item">
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>
69
+ </li>
70
+ </ul>
71
+ <span class="navbar-text pe-2">
72
+ <%= link_to ".", "#", class: "nav-link", id: "themeToggle" %>
73
+ </span>
74
+ </div>
75
+ </div>
76
+ </nav>
77
+ </div>
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 %>
89
+ </div>
90
+ <div class="container-fluid">
91
+ <%= yield %>
92
+ </div>
93
+ </main>
94
+ <footer class="mt-auto">
95
+ <div class="container-fluid">
96
+ <div class="sticky-bottom">
97
+ <div class="row bg-body-tertiary">
98
+ <div class="col gx-0 px-4">
99
+ <p class="fs-6 text-center pt-2 text-muted">
100
+ <em>GraphQL-Ruby v<%= GraphQL::VERSION %></em> · <code><%= schema_class %></code>
101
+ </p>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ </div>
106
+ </footer>
107
+ </body>
108
+ </html>
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+ require 'rails/engine'
3
+ require 'action_controller'
4
+ module Graphql
5
+ # `GraphQL::Dashboard` is a `Rails::Engine`-based dashboard for viewing metadata about your GraphQL schema.
6
+ #
7
+ # Pass the class name of your schema when mounting it.
8
+ # @see GraphQL::Tracing::DetailedTrace DetailedTrace for viewing production traces in the Dashboard
9
+ #
10
+ # @example Mounting the Dashboard in your app
11
+ # mount GraphQL::Dashboard, at: "graphql_dashboard", schema: "MySchema"
12
+ #
13
+ # @example Authenticating the Dashboard with HTTP Basic Auth
14
+ # # config/initializers/graphql_dashboard.rb
15
+ # GraphQL::Dashboard.middleware.use(Rack::Auth::Basic) do |username, password|
16
+ # # Compare the provided username/password to an application setting:
17
+ # ActiveSupport::SecurityUtils.secure_compare(Rails.application.credentials.graphql_dashboard_username, username) &&
18
+ # ActiveSupport::SecurityUtils.secure_compare(Rails.application.credentials.graphql_dashboard_username, password)
19
+ # end
20
+ #
21
+ # @example Custom Rails authentication
22
+ # # config/initializers/graphql_dashboard.rb
23
+ # ActiveSupport.on_load(:graphql_dashboard_application_controller) do
24
+ # # context here is GraphQL::Dashboard::ApplicationController
25
+ #
26
+ # before_action do
27
+ # raise ActionController::RoutingError.new('Not Found') unless current_user&.admin?
28
+ # end
29
+ #
30
+ # def current_user
31
+ # # load current user
32
+ # end
33
+ # end
34
+ #
35
+ class Dashboard < Rails::Engine
36
+ engine_name "graphql_dashboard"
37
+ isolate_namespace(Graphql::Dashboard)
38
+
39
+ autoload :ApplicationController, "graphql/dashboard/application_controller"
40
+ autoload :LandingsController, "graphql/dashboard/landings_controller"
41
+ autoload :StaticsController, "graphql/dashboard/statics_controller"
42
+ autoload :DetailedTraces, "graphql/dashboard/detailed_traces"
43
+ autoload :Subscriptions, "graphql/dashboard/subscriptions"
44
+ autoload :OperationStore, "graphql/dashboard/operation_store"
45
+ autoload :Limiters, "graphql/dashboard/limiters"
46
+
47
+ routes do
48
+ root "landings#show"
49
+ resources :statics, only: :show, constraints: { id: /[0-9A-Za-z\-.]+/ }
50
+
51
+ namespace :detailed_traces do
52
+ resources :traces, only: [:index, :show, :destroy] do
53
+ collection do
54
+ delete :delete_all, to: "traces#delete_all", as: :delete_all
55
+ end
56
+ end
57
+ end
58
+
59
+ namespace :limiters do
60
+ resources :limiters, only: [:show, :update], param: :name
61
+ end
62
+
63
+ namespace :operation_store do
64
+ resources :clients, param: :name do
65
+ resources :operations, param: :digest, only: [:index] do
66
+ collection do
67
+ get :archived, to: "operations#index", archived_status: :archived, as: :archived
68
+ post :archive, to: "operations#update", modification: :archive, as: :archive
69
+ post :unarchive, to: "operations#update", modification: :unarchive, as: :unarchive
70
+ end
71
+ end
72
+ end
73
+
74
+ resources :operations, param: :digest, only: [:index, :show] do
75
+ collection do
76
+ get :archived, to: "operations#index", archived_status: :archived, as: :archived
77
+ post :archive, to: "operations#update", modification: :archive, as: :archive
78
+ post :unarchive, to: "operations#update", modification: :unarchive, as: :unarchive
79
+ end
80
+ end
81
+ resources :index_entries, only: [:index, :show], param: :name, constraints: { name: /[A-Za-z0-9_.]+/}
82
+ end
83
+
84
+ namespace :subscriptions do
85
+ resources :topics, only: [:index, :show], param: :name, constraints: { name: /.*/ }
86
+ resources :subscriptions, only: [:show], constraints: { id: /[a-zA-Z0-9\-]+/ }
87
+ post "/subscriptions/clear_all", to: "subscriptions#clear_all", as: :clear_all
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ # Rails expects the engine to be called `Graphql::Dashboard`,
94
+ # but `GraphQL::Dashboard` is consistent with this gem's naming.
95
+ # So define both constants to refer to the same class.
96
+ GraphQL::Dashboard = Graphql::Dashboard
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+ require "graphql/dataloader/source"
3
+ require "graphql/dataloader/active_record_source"
4
+
5
+ module GraphQL
6
+ class Dataloader
7
+ class ActiveRecordAssociationSource < GraphQL::Dataloader::Source
8
+ RECORD_SOURCE_CLASS = ActiveRecordSource
9
+
10
+ def initialize(association, scope = nil)
11
+ @association = association
12
+ @scope = scope
13
+ end
14
+
15
+ def self.batch_key_for(association, scope = nil)
16
+ if scope
17
+ [association, scope.to_sql]
18
+ else
19
+ [association]
20
+ end
21
+ end
22
+
23
+ def load(record)
24
+ if (assoc = record.association(@association)).loaded?
25
+ assoc.target
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ def fetch(records)
32
+ record_classes = Set.new.compare_by_identity
33
+ associated_classes = Set.new.compare_by_identity
34
+ scoped_fetch = !@scope.nil?
35
+ records.each do |record|
36
+ if scoped_fetch
37
+ assoc = record.association(@association)
38
+ assoc.reset
39
+ end
40
+ if record_classes.add?(record.class)
41
+ reflection = record.class.reflect_on_association(@association)
42
+ if !reflection.polymorphic? && reflection.klass
43
+ associated_classes.add(reflection.klass)
44
+ end
45
+ end
46
+ end
47
+
48
+ available_records = []
49
+ associated_classes.each do |assoc_class|
50
+ already_loaded_records = dataloader.with(RECORD_SOURCE_CLASS, assoc_class).results.values
51
+ available_records.concat(already_loaded_records)
52
+ end
53
+
54
+ ::ActiveRecord::Associations::Preloader.new(records: records, associations: @association, available_records: available_records, scope: @scope).call
55
+
56
+ loaded_associated_records = records.map { |r|
57
+ assoc = r.association(@association)
58
+ lar = assoc.target
59
+ if scoped_fetch
60
+ assoc.reset
61
+ end
62
+ lar
63
+ }
64
+
65
+ if !scoped_fetch
66
+ # Don't cache records loaded via scope because they might have reduced `SELECT`s
67
+ # Could check .select_values here?
68
+ records_by_model = {}
69
+ loaded_associated_records.flatten.each do |record|
70
+ if record
71
+ updates = records_by_model[record.class] ||= {}
72
+ updates[record.id] = record
73
+ end
74
+ end
75
+ records_by_model.each do |model_class, updates|
76
+ dataloader.with(RECORD_SOURCE_CLASS, model_class).merge(updates)
77
+ end
78
+ end
79
+
80
+ loaded_associated_records
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+ require "graphql/dataloader/source"
3
+
4
+ module GraphQL
5
+ class Dataloader
6
+ class ActiveRecordSource < GraphQL::Dataloader::Source
7
+ def initialize(model_class, find_by: model_class.primary_key)
8
+ @model_class = model_class
9
+ @find_by = find_by
10
+ @find_by_many = find_by.is_a?(Array)
11
+ if @find_by_many
12
+ @type_for_column = @find_by.map { |fb| @model_class.type_for_attribute(fb) }
13
+ else
14
+ @type_for_column = @model_class.type_for_attribute(@find_by)
15
+ end
16
+ end
17
+
18
+ def result_key_for(requested_key)
19
+ normalize_fetch_key(requested_key)
20
+ end
21
+
22
+ def normalize_fetch_key(requested_key)
23
+ if @find_by_many
24
+ requested_key.each_with_index.map do |k, idx|
25
+ @type_for_column[idx].cast(k)
26
+ end
27
+ else
28
+ @type_for_column.cast(requested_key)
29
+ end
30
+ end
31
+
32
+ def fetch(record_ids)
33
+ records = @model_class.where(@find_by => record_ids)
34
+ record_lookup = {}
35
+ if @find_by_many
36
+ records.each do |r|
37
+ key = @find_by.map { |fb| r.public_send(fb) }
38
+ record_lookup[key] = r
39
+ end
40
+ else
41
+ records.each { |r| record_lookup[r.public_send(@find_by)] = r }
42
+ end
43
+ record_ids.map { |id| record_lookup[id] }
44
+ end
45
+ end
46
+ end
47
+ end
@@ -2,16 +2,20 @@
2
2
  module GraphQL
3
3
  class Dataloader
4
4
  class AsyncDataloader < Dataloader
5
- def yield
5
+ def yield(source = Fiber[:__graphql_current_dataloader_source])
6
+ trace = Fiber[:__graphql_current_multiplex]&.current_trace
7
+ trace&.dataloader_fiber_yield(source)
6
8
  if (condition = Fiber[:graphql_dataloader_next_tick])
7
9
  condition.wait
8
10
  else
9
11
  Fiber.yield
10
12
  end
13
+ trace&.dataloader_fiber_resume(source)
11
14
  nil
12
15
  end
13
16
 
14
- def run
17
+ def run(trace_query_lazy: nil)
18
+ trace = Fiber[:__graphql_current_multiplex]&.current_trace
15
19
  jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
16
20
  job_fibers = []
17
21
  next_job_fibers = []
@@ -20,25 +24,17 @@ module GraphQL
20
24
  first_pass = true
21
25
  sources_condition = Async::Condition.new
22
26
  manager = spawn_fiber do
27
+ trace&.begin_dataloader(self)
23
28
  while first_pass || !job_fibers.empty?
24
29
  first_pass = false
25
30
  fiber_vars = get_fiber_variables
26
31
 
27
- while (f = (job_fibers.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size) < jobs_fiber_limit) && spawn_job_fiber)))
28
- if f.alive?
29
- finished = run_fiber(f)
30
- if !finished
31
- next_job_fibers << f
32
- end
33
- end
34
- end
35
- job_fibers.concat(next_job_fibers)
36
- next_job_fibers.clear
32
+ run_pending_steps(job_fibers, next_job_fibers, source_tasks, jobs_fiber_limit, trace)
37
33
 
38
34
  Sync do |root_task|
39
35
  set_fiber_variables(fiber_vars)
40
36
  while !source_tasks.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) }
41
- while (task = (source_tasks.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size + next_source_tasks.size) < total_fiber_limit) && spawn_source_task(root_task, sources_condition))))
37
+ while (task = (source_tasks.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size + next_source_tasks.size) < total_fiber_limit) && spawn_source_task(root_task, sources_condition, trace))))
42
38
  if task.alive?
43
39
  root_task.yield # give the source task a chance to run
44
40
  next_source_tasks << task
@@ -49,7 +45,15 @@ module GraphQL
49
45
  next_source_tasks.clear
50
46
  end
51
47
  end
48
+
49
+ if !@lazies_at_depth.empty?
50
+ with_trace_query_lazy(trace_query_lazy) do
51
+ run_next_pending_lazies(job_fibers, trace)
52
+ run_pending_steps(job_fibers, next_job_fibers, source_tasks, jobs_fiber_limit, trace)
53
+ end
54
+ end
52
55
  end
56
+ trace&.end_dataloader(self)
53
57
  end
54
58
 
55
59
  manager.resume
@@ -63,7 +67,20 @@ module GraphQL
63
67
 
64
68
  private
65
69
 
66
- def spawn_source_task(parent_task, condition)
70
+ def run_pending_steps(job_fibers, next_job_fibers, source_tasks, jobs_fiber_limit, trace)
71
+ while (f = (job_fibers.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size) < jobs_fiber_limit) && spawn_job_fiber(trace))))
72
+ if f.alive?
73
+ finished = run_fiber(f)
74
+ if !finished
75
+ next_job_fibers << f
76
+ end
77
+ end
78
+ end
79
+ job_fibers.concat(next_job_fibers)
80
+ next_job_fibers.clear
81
+ end
82
+
83
+ def spawn_source_task(parent_task, condition, trace)
67
84
  pending_sources = nil
68
85
  @source_cache.each_value do |source_by_batch_params|
69
86
  source_by_batch_params.each_value do |source|
@@ -77,10 +94,16 @@ module GraphQL
77
94
  if pending_sources
78
95
  fiber_vars = get_fiber_variables
79
96
  parent_task.async do
97
+ trace&.dataloader_spawn_source_fiber(pending_sources)
80
98
  set_fiber_variables(fiber_vars)
81
99
  Fiber[:graphql_dataloader_next_tick] = condition
82
- pending_sources.each(&:run_pending_keys)
100
+ pending_sources.each do |s|
101
+ trace&.begin_dataloader_source(s)
102
+ s.run_pending_keys
103
+ trace&.end_dataloader_source(s)
104
+ end
83
105
  cleanup_fiber
106
+ trace&.dataloader_fiber_exit
84
107
  end
85
108
  end
86
109
  end
@@ -2,23 +2,68 @@
2
2
 
3
3
  module GraphQL
4
4
  class Dataloader
5
- # The default implementation of dataloading -- all no-ops.
5
+ # GraphQL-Ruby uses this when Dataloader isn't enabled.
6
6
  #
7
- # The Dataloader interface isn't public, but it enables
8
- # simple internal code while adding the option to add Dataloader.
7
+ # It runs execution code inline and gathers lazy objects (eg. Promises)
8
+ # and resolves them during {#run}.
9
9
  class NullDataloader < Dataloader
10
- # These are all no-ops because code was
11
- # executed synchronously.
12
- def run; end
13
- def run_isolated; yield; end
14
- def yield
10
+ def initialize(*)
11
+ @lazies_at_depth = Hash.new { |h,k| h[k] = [] }
12
+ end
13
+
14
+ def freeze
15
+ @lazies_at_depth.default_proc = nil
16
+ @lazies_at_depth.freeze
17
+ super
18
+ end
19
+
20
+ def run(trace_query_lazy: nil)
21
+ with_trace_query_lazy(trace_query_lazy) do
22
+ while !@lazies_at_depth.empty?
23
+ smallest_depth = nil
24
+ @lazies_at_depth.each_key do |depth_key|
25
+ smallest_depth ||= depth_key
26
+ if depth_key < smallest_depth
27
+ smallest_depth = depth_key
28
+ end
29
+ end
30
+
31
+ if smallest_depth
32
+ lazies = @lazies_at_depth.delete(smallest_depth)
33
+ lazies.each(&:value) # resolve these Lazy instances
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ def run_isolated
40
+ # Reuse this instance because execution code may already have a reference to _this_ `dataloader` inside the given block.
41
+ prev_lazies_at_depth = @lazies_at_depth
42
+ @lazies_at_depth = @lazies_at_depth.dup.clear
43
+ res = nil
44
+ append_job {
45
+ res = yield
46
+ }
47
+ run
48
+ res
49
+ ensure
50
+ @lazies_at_depth = prev_lazies_at_depth
51
+ end
52
+
53
+ def clear_cache; end
54
+
55
+ def yield(_source)
15
56
  raise GraphQL::Error, "GraphQL::Dataloader is not running -- add `use GraphQL::Dataloader` to your schema to use Dataloader sources."
16
57
  end
17
58
 
18
- def append_job
19
- yield
59
+ def append_job(callable = nil)
60
+ callable ? callable.call : yield
20
61
  nil
21
62
  end
63
+
64
+ def with(*)
65
+ raise GraphQL::Error, "GraphQL::Dataloader is not running -- add `use GraphQL::Dataloader` to your schema to use Dataloader sources."
66
+ end
22
67
  end
23
68
  end
24
69
  end