graphql 2.4.3 → 2.5.2
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/analysis/analyzer.rb +2 -1
- data/lib/graphql/analysis/visitor.rb +38 -41
- data/lib/graphql/analysis.rb +15 -12
- data/lib/graphql/autoload.rb +38 -0
- data/lib/graphql/backtrace/table.rb +118 -55
- data/lib/graphql/backtrace.rb +1 -19
- data/lib/graphql/current.rb +6 -1
- data/lib/graphql/dashboard/detailed_traces.rb +47 -0
- data/lib/graphql/dashboard/installable.rb +22 -0
- data/lib/graphql/dashboard/limiters.rb +93 -0
- data/lib/graphql/dashboard/operation_store.rb +199 -0
- data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
- data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
- data/lib/graphql/dashboard/statics/charts.min.css +1 -0
- data/lib/graphql/dashboard/statics/dashboard.css +30 -0
- data/lib/graphql/dashboard/statics/dashboard.js +143 -0
- data/lib/graphql/dashboard/statics/header-icon.png +0 -0
- data/lib/graphql/dashboard/statics/icon.png +0 -0
- data/lib/graphql/dashboard/subscriptions.rb +96 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
- data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
- data/lib/graphql/dashboard.rb +158 -0
- data/lib/graphql/dataloader/active_record_association_source.rb +64 -0
- data/lib/graphql/dataloader/active_record_source.rb +26 -0
- data/lib/graphql/dataloader/async_dataloader.rb +21 -9
- data/lib/graphql/dataloader/null_dataloader.rb +1 -1
- data/lib/graphql/dataloader/source.rb +3 -3
- data/lib/graphql/dataloader.rb +43 -14
- data/lib/graphql/execution/interpreter/resolve.rb +3 -3
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +34 -4
- data/lib/graphql/execution/interpreter/runtime.rb +94 -51
- data/lib/graphql/execution/interpreter.rb +16 -7
- data/lib/graphql/execution/multiplex.rb +1 -5
- data/lib/graphql/introspection/directive_location_enum.rb +1 -1
- data/lib/graphql/invalid_name_error.rb +1 -1
- data/lib/graphql/invalid_null_error.rb +5 -15
- data/lib/graphql/language/cache.rb +13 -0
- data/lib/graphql/language/document_from_schema_definition.rb +8 -7
- data/lib/graphql/language/lexer.rb +11 -4
- data/lib/graphql/language/nodes.rb +3 -0
- data/lib/graphql/language/parser.rb +15 -8
- data/lib/graphql/language/printer.rb +8 -8
- data/lib/graphql/language/static_visitor.rb +37 -33
- data/lib/graphql/language/visitor.rb +59 -55
- data/lib/graphql/pagination/connection.rb +1 -1
- data/lib/graphql/query/context/scoped_context.rb +1 -1
- data/lib/graphql/query/context.rb +6 -5
- data/lib/graphql/query/variable_validation_error.rb +1 -1
- data/lib/graphql/query.rb +19 -23
- data/lib/graphql/railtie.rb +7 -0
- data/lib/graphql/schema/addition.rb +1 -1
- data/lib/graphql/schema/argument.rb +7 -8
- data/lib/graphql/schema/build_from_definition.rb +99 -53
- data/lib/graphql/schema/directive/flagged.rb +3 -1
- data/lib/graphql/schema/directive.rb +2 -2
- data/lib/graphql/schema/enum.rb +36 -1
- data/lib/graphql/schema/enum_value.rb +1 -1
- data/lib/graphql/schema/field/scope_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +27 -13
- data/lib/graphql/schema/field_extension.rb +1 -1
- data/lib/graphql/schema/has_single_input_argument.rb +3 -1
- data/lib/graphql/schema/input_object.rb +77 -40
- data/lib/graphql/schema/interface.rb +3 -2
- data/lib/graphql/schema/loader.rb +1 -1
- data/lib/graphql/schema/member/has_arguments.rb +25 -17
- data/lib/graphql/schema/member/has_dataloader.rb +60 -0
- data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
- data/lib/graphql/schema/member/has_directives.rb +4 -4
- data/lib/graphql/schema/member/has_fields.rb +19 -1
- data/lib/graphql/schema/member/has_interfaces.rb +5 -5
- data/lib/graphql/schema/member/has_validators.rb +1 -1
- data/lib/graphql/schema/member/scoped.rb +1 -1
- data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
- data/lib/graphql/schema/member.rb +1 -0
- data/lib/graphql/schema/object.rb +25 -8
- data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
- data/lib/graphql/schema/resolver.rb +12 -10
- data/lib/graphql/schema/subscription.rb +52 -6
- data/lib/graphql/schema/union.rb +1 -1
- data/lib/graphql/schema/validator/required_validator.rb +23 -6
- data/lib/graphql/schema/validator.rb +1 -1
- data/lib/graphql/schema/visibility/migration.rb +1 -0
- data/lib/graphql/schema/visibility/profile.rb +95 -243
- data/lib/graphql/schema/visibility/visit.rb +190 -0
- data/lib/graphql/schema/visibility.rb +169 -28
- data/lib/graphql/schema/warden.rb +18 -5
- data/lib/graphql/schema.rb +93 -44
- data/lib/graphql/static_validation/all_rules.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
- data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
- data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
- data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
- data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
- data/lib/graphql/static_validation/validation_context.rb +1 -0
- data/lib/graphql/static_validation/validator.rb +6 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
- data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
- data/lib/graphql/subscriptions/event.rb +12 -1
- data/lib/graphql/subscriptions/serialize.rb +1 -1
- data/lib/graphql/subscriptions.rb +1 -1
- data/lib/graphql/testing/helpers.rb +7 -4
- data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
- data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
- data/lib/graphql/tracing/appoptics_trace.rb +9 -1
- data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
- data/lib/graphql/tracing/appsignal_trace.rb +32 -55
- data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
- data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
- data/lib/graphql/tracing/data_dog_trace.rb +46 -158
- data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
- data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
- data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
- data/lib/graphql/tracing/detailed_trace.rb +93 -0
- data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
- data/lib/graphql/tracing/legacy_trace.rb +4 -61
- data/lib/graphql/tracing/monitor_trace.rb +283 -0
- data/lib/graphql/tracing/new_relic_trace.rb +47 -54
- data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
- data/lib/graphql/tracing/notifications_trace.rb +182 -34
- data/lib/graphql/tracing/notifications_tracing.rb +2 -0
- data/lib/graphql/tracing/null_trace.rb +9 -0
- data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
- data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
- data/lib/graphql/tracing/perfetto_trace.rb +734 -0
- data/lib/graphql/tracing/platform_trace.rb +5 -0
- data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
- data/lib/graphql/tracing/prometheus_trace.rb +72 -68
- data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
- data/lib/graphql/tracing/scout_trace.rb +32 -55
- data/lib/graphql/tracing/scout_tracing.rb +2 -0
- data/lib/graphql/tracing/sentry_trace.rb +62 -94
- data/lib/graphql/tracing/statsd_trace.rb +33 -41
- data/lib/graphql/tracing/statsd_tracing.rb +2 -0
- data/lib/graphql/tracing/trace.rb +111 -1
- data/lib/graphql/tracing.rb +31 -30
- data/lib/graphql/types/relay/connection_behaviors.rb +3 -3
- data/lib/graphql/types/relay/edge_behaviors.rb +2 -2
- data/lib/graphql/types.rb +18 -11
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +55 -47
- metadata +146 -11
- data/lib/graphql/backtrace/inspect_result.rb +0 -38
- data/lib/graphql/backtrace/trace.rb +0 -93
- data/lib/graphql/backtrace/tracer.rb +0 -80
- data/lib/graphql/schema/null_mask.rb +0 -11
- 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
|
+
<%= stylesheet_link_tag graphql_dashboard.static_path("bootstrap-5.3.3.min.css") %>
|
9
|
+
<%= stylesheet_link_tag graphql_dashboard.static_path("charts.min.css") %>
|
10
|
+
<%= stylesheet_link_tag graphql_dashboard.static_path("dashboard.css") %>
|
11
|
+
<%= javascript_include_tag graphql_dashboard.static_path("bootstrap-5.3.3.min.js") %>
|
12
|
+
<%= javascript_include_tag graphql_dashboard.static_path("dashboard.js") %>
|
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,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'rails/engine'
|
3
|
+
module Graphql
|
4
|
+
# `GraphQL::Dashboard` is a `Rails::Engine`-based dashboard for viewing metadata about your GraphQL schema.
|
5
|
+
#
|
6
|
+
# Pass the class name of your schema when mounting it.
|
7
|
+
# @see GraphQL::Tracing::DetailedTrace DetailedTrace for viewing production traces in the Dashboard
|
8
|
+
#
|
9
|
+
# @example Mounting the Dashboard in your app
|
10
|
+
# mount GraphQL::Dashboard, at: "graphql_dashboard", schema: "MySchema"
|
11
|
+
#
|
12
|
+
# @example Authenticating the Dashboard with HTTP Basic Auth
|
13
|
+
# # config/initializers/graphql_dashboard.rb
|
14
|
+
# GraphQL::Dashboard.middleware.use(Rack::Auth::Basic) do |username, password|
|
15
|
+
# # Compare the provided username/password to an application setting:
|
16
|
+
# ActiveSupport::SecurityUtils.secure_compare(Rails.application.credentials.graphql_dashboard_username, username) &&
|
17
|
+
# ActiveSupport::SecurityUtils.secure_compare(Rails.application.credentials.graphql_dashboard_username, password)
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# @example Custom Rails authentication
|
21
|
+
# # config/initializers/graphql_dashboard.rb
|
22
|
+
# ActiveSupport.on_load(:graphql_dashboard_application_controller) do
|
23
|
+
# # context here is GraphQL::Dashboard::ApplicationController
|
24
|
+
#
|
25
|
+
# before_action do
|
26
|
+
# raise ActionController::RoutingError.new('Not Found') unless current_user&.admin?
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# def current_user
|
30
|
+
# # load current user
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
class Dashboard < Rails::Engine
|
35
|
+
engine_name "graphql_dashboard"
|
36
|
+
isolate_namespace(Graphql::Dashboard)
|
37
|
+
routes.draw do
|
38
|
+
root "landings#show"
|
39
|
+
resources :statics, only: :show, constraints: { id: /[0-9A-Za-z\-.]+/ }
|
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
|
79
|
+
end
|
80
|
+
|
81
|
+
class ApplicationController < ActionController::Base
|
82
|
+
protect_from_forgery with: :exception
|
83
|
+
prepend_view_path(File.join(__FILE__, "../dashboard/views"))
|
84
|
+
|
85
|
+
content_security_policy do |policy|
|
86
|
+
policy.default_src(:self) if policy.default_src(*policy.default_src).blank?
|
87
|
+
policy.connect_src(:self) if policy.connect_src(*policy.connect_src).blank?
|
88
|
+
policy.base_uri(:none) if policy.base_uri(*policy.base_uri).blank?
|
89
|
+
policy.font_src(:self) if policy.font_src(*policy.font_src).blank?
|
90
|
+
policy.img_src(:self, :data) if policy.img_src(*policy.img_src).blank?
|
91
|
+
policy.object_src(:none) if policy.object_src(*policy.object_src).blank?
|
92
|
+
policy.script_src(:self) if policy.script_src(*policy.script_src).blank?
|
93
|
+
policy.style_src(:self) if policy.style_src(*policy.style_src).blank?
|
94
|
+
policy.form_action(:self) if policy.form_action(*policy.form_action).blank?
|
95
|
+
policy.frame_ancestors(:none) if policy.frame_ancestors(*policy.frame_ancestors).blank?
|
96
|
+
end
|
97
|
+
|
98
|
+
def schema_class
|
99
|
+
@schema_class ||= begin
|
100
|
+
schema_param = request.query_parameters["schema"] || params[:schema]
|
101
|
+
case schema_param
|
102
|
+
when Class
|
103
|
+
schema_param
|
104
|
+
when String
|
105
|
+
schema_param.constantize
|
106
|
+
else
|
107
|
+
raise "Missing `params[:schema]`, please provide a class or string to `mount GraphQL::Dashboard, schema: ...`"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
helper_method :schema_class
|
112
|
+
end
|
113
|
+
|
114
|
+
class LandingsController < ApplicationController
|
115
|
+
def show
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class StaticsController < ApplicationController
|
120
|
+
skip_after_action :verify_same_origin_request
|
121
|
+
# Use an explicit list of files to avoid any chance of reading other files from disk
|
122
|
+
STATICS = {}
|
123
|
+
|
124
|
+
[
|
125
|
+
"icon.png",
|
126
|
+
"header-icon.png",
|
127
|
+
"charts.min.css",
|
128
|
+
"dashboard.css",
|
129
|
+
"dashboard.js",
|
130
|
+
"bootstrap-5.3.3.min.css",
|
131
|
+
"bootstrap-5.3.3.min.js",
|
132
|
+
].each do |static_file|
|
133
|
+
STATICS[static_file] = File.expand_path("../dashboard/statics/#{static_file}", __FILE__)
|
134
|
+
end
|
135
|
+
|
136
|
+
def show
|
137
|
+
expires_in 1.year, public: true
|
138
|
+
if (filepath = STATICS[params[:id]])
|
139
|
+
render file: filepath
|
140
|
+
else
|
141
|
+
head :not_found
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
require 'graphql/dashboard/detailed_traces'
|
149
|
+
require 'graphql/dashboard/limiters'
|
150
|
+
require 'graphql/dashboard/operation_store'
|
151
|
+
require 'graphql/dashboard/subscriptions'
|
152
|
+
|
153
|
+
# Rails expects the engine to be called `Graphql::Dashboard`,
|
154
|
+
# but `GraphQL::Dashboard` is consistent with this gem's naming.
|
155
|
+
# So define both constants to refer to the same class.
|
156
|
+
GraphQL::Dashboard = Graphql::Dashboard
|
157
|
+
|
158
|
+
ActiveSupport.run_load_hooks(:graphql_dashboard_application_controller, GraphQL::Dashboard::ApplicationController)
|
@@ -0,0 +1,64 @@
|
|
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 load(record)
|
16
|
+
if (assoc = record.association(@association)).loaded?
|
17
|
+
assoc.target
|
18
|
+
else
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def fetch(records)
|
24
|
+
record_classes = Set.new.compare_by_identity
|
25
|
+
associated_classes = Set.new.compare_by_identity
|
26
|
+
records.each do |record|
|
27
|
+
if record_classes.add?(record.class)
|
28
|
+
reflection = record.class.reflect_on_association(@association)
|
29
|
+
if !reflection.polymorphic? && reflection.klass
|
30
|
+
associated_classes.add(reflection.klass)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
available_records = []
|
36
|
+
associated_classes.each do |assoc_class|
|
37
|
+
already_loaded_records = dataloader.with(RECORD_SOURCE_CLASS, assoc_class).results.values
|
38
|
+
available_records.concat(already_loaded_records)
|
39
|
+
end
|
40
|
+
|
41
|
+
::ActiveRecord::Associations::Preloader.new(records: records, associations: @association, available_records: available_records, scope: @scope).call
|
42
|
+
|
43
|
+
loaded_associated_records = records.map { |r| r.public_send(@association) }
|
44
|
+
records_by_model = {}
|
45
|
+
loaded_associated_records.each do |record|
|
46
|
+
if record
|
47
|
+
updates = records_by_model[record.class] ||= {}
|
48
|
+
updates[record.id] = record
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if @scope.nil?
|
53
|
+
# Don't cache records loaded via scope because they might have reduced `SELECT`s
|
54
|
+
# Could check .select_values here?
|
55
|
+
records_by_model.each do |model_class, updates|
|
56
|
+
dataloader.with(RECORD_SOURCE_CLASS, model_class).merge(updates)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
loaded_associated_records
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,26 @@
|
|
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
|
+
@type_for_column = @model_class.type_for_attribute(@find_by)
|
11
|
+
end
|
12
|
+
|
13
|
+
def load(requested_key)
|
14
|
+
casted_key = @type_for_column.cast(requested_key)
|
15
|
+
super(casted_key)
|
16
|
+
end
|
17
|
+
|
18
|
+
def fetch(record_ids)
|
19
|
+
records = @model_class.where(@find_by => record_ids)
|
20
|
+
record_lookup = {}
|
21
|
+
records.each { |r| record_lookup[r.public_send(@find_by)] = r }
|
22
|
+
record_ids.map { |id| record_lookup[id] }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -2,16 +2,20 @@
|
|
2
2
|
module GraphQL
|
3
3
|
class Dataloader
|
4
4
|
class AsyncDataloader < Dataloader
|
5
|
-
def yield
|
6
|
-
|
5
|
+
def yield(source = Fiber[:__graphql_current_dataloader_source])
|
6
|
+
trace = Fiber[:__graphql_current_multiplex]&.current_trace
|
7
|
+
trace&.dataloader_fiber_yield(source)
|
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
17
|
def run
|
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,11 +24,12 @@ module GraphQL
|
|
20
24
|
first_pass = true
|
21
25
|
sources_condition = Async::Condition.new
|
22
26
|
manager = spawn_fiber do
|
23
|
-
|
27
|
+
trace&.begin_dataloader(self)
|
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)))
|
32
|
+
while (f = (job_fibers.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size) < jobs_fiber_limit) && spawn_job_fiber(trace))))
|
28
33
|
if f.alive?
|
29
34
|
finished = run_fiber(f)
|
30
35
|
if !finished
|
@@ -37,8 +42,8 @@ module GraphQL
|
|
37
42
|
|
38
43
|
Sync do |root_task|
|
39
44
|
set_fiber_variables(fiber_vars)
|
40
|
-
while source_tasks.
|
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))))
|
45
|
+
while !source_tasks.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) }
|
46
|
+
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
47
|
if task.alive?
|
43
48
|
root_task.yield # give the source task a chance to run
|
44
49
|
next_source_tasks << task
|
@@ -50,6 +55,7 @@ module GraphQL
|
|
50
55
|
end
|
51
56
|
end
|
52
57
|
end
|
58
|
+
trace&.end_dataloader(self)
|
53
59
|
end
|
54
60
|
|
55
61
|
manager.resume
|
@@ -63,7 +69,7 @@ module GraphQL
|
|
63
69
|
|
64
70
|
private
|
65
71
|
|
66
|
-
def spawn_source_task(parent_task, condition)
|
72
|
+
def spawn_source_task(parent_task, condition, trace)
|
67
73
|
pending_sources = nil
|
68
74
|
@source_cache.each_value do |source_by_batch_params|
|
69
75
|
source_by_batch_params.each_value do |source|
|
@@ -77,10 +83,16 @@ module GraphQL
|
|
77
83
|
if pending_sources
|
78
84
|
fiber_vars = get_fiber_variables
|
79
85
|
parent_task.async do
|
86
|
+
trace&.dataloader_spawn_source_fiber(pending_sources)
|
80
87
|
set_fiber_variables(fiber_vars)
|
81
|
-
|
82
|
-
pending_sources.each
|
88
|
+
Fiber[:graphql_dataloader_next_tick] = condition
|
89
|
+
pending_sources.each do |s|
|
90
|
+
trace&.begin_dataloader_source(s)
|
91
|
+
s.run_pending_keys
|
92
|
+
trace&.end_dataloader_source(s)
|
93
|
+
end
|
83
94
|
cleanup_fiber
|
95
|
+
trace&.dataloader_fiber_exit
|
84
96
|
end
|
85
97
|
end
|
86
98
|
end
|
@@ -11,7 +11,7 @@ module GraphQL
|
|
11
11
|
# executed synchronously.
|
12
12
|
def run; end
|
13
13
|
def run_isolated; yield; end
|
14
|
-
def yield
|
14
|
+
def yield(_source)
|
15
15
|
raise GraphQL::Error, "GraphQL::Dataloader is not running -- add `use GraphQL::Dataloader` to your schema to use Dataloader sources."
|
16
16
|
end
|
17
17
|
|
@@ -73,7 +73,7 @@ module GraphQL
|
|
73
73
|
end
|
74
74
|
}
|
75
75
|
|
76
|
-
if pending_keys.
|
76
|
+
if !pending_keys.empty?
|
77
77
|
sync(pending_keys)
|
78
78
|
end
|
79
79
|
|
@@ -93,14 +93,14 @@ module GraphQL
|
|
93
93
|
# Then run the batch and update the cache.
|
94
94
|
# @return [void]
|
95
95
|
def sync(pending_result_keys)
|
96
|
-
@dataloader.yield
|
96
|
+
@dataloader.yield(self)
|
97
97
|
iterations = 0
|
98
98
|
while pending_result_keys.any? { |key| !@results.key?(key) }
|
99
99
|
iterations += 1
|
100
100
|
if iterations > MAX_ITERATIONS
|
101
101
|
raise "#{self.class}#sync tried #{MAX_ITERATIONS} times to load pending keys (#{pending_result_keys}), but they still weren't loaded. There is likely a circular dependency#{@dataloader.fiber_limit ? " or `fiber_limit: #{@dataloader.fiber_limit}` is set too low" : ""}."
|
102
102
|
end
|
103
|
-
@dataloader.yield
|
103
|
+
@dataloader.yield(self)
|
104
104
|
end
|
105
105
|
nil
|
106
106
|
end
|