graphql 2.4.13 → 2.5.11

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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/query_complexity.rb +87 -7
  3. data/lib/graphql/backtrace/table.rb +37 -14
  4. data/lib/graphql/current.rb +1 -1
  5. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  6. data/lib/graphql/dashboard/installable.rb +22 -0
  7. data/lib/graphql/dashboard/limiters.rb +93 -0
  8. data/lib/graphql/dashboard/operation_store.rb +199 -0
  9. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  10. data/lib/graphql/dashboard/statics/dashboard.css +27 -0
  11. data/lib/graphql/dashboard/statics/dashboard.js +74 -9
  12. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  13. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  14. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  15. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  16. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  17. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  18. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  19. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  20. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  21. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  22. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  23. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  24. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  25. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  26. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  27. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +49 -1
  28. data/lib/graphql/dashboard.rb +45 -29
  29. data/lib/graphql/dataloader/active_record_association_source.rb +28 -8
  30. data/lib/graphql/dataloader/active_record_source.rb +26 -5
  31. data/lib/graphql/dataloader/null_dataloader.rb +7 -0
  32. data/lib/graphql/dataloader/source.rb +16 -4
  33. data/lib/graphql/dig.rb +2 -1
  34. data/lib/graphql/execution/interpreter/resolve.rb +3 -3
  35. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +34 -1
  36. data/lib/graphql/execution/interpreter/runtime.rb +163 -59
  37. data/lib/graphql/execution/interpreter.rb +5 -13
  38. data/lib/graphql/execution/multiplex.rb +6 -1
  39. data/lib/graphql/invalid_null_error.rb +15 -2
  40. data/lib/graphql/language/lexer.rb +9 -2
  41. data/lib/graphql/language/nodes.rb +5 -1
  42. data/lib/graphql/language/parser.rb +14 -6
  43. data/lib/graphql/query/context.rb +3 -8
  44. data/lib/graphql/query/partial.rb +179 -0
  45. data/lib/graphql/query.rb +59 -55
  46. data/lib/graphql/schema/addition.rb +3 -1
  47. data/lib/graphql/schema/always_visible.rb +1 -0
  48. data/lib/graphql/schema/argument.rb +9 -3
  49. data/lib/graphql/schema/build_from_definition.rb +96 -47
  50. data/lib/graphql/schema/directive/flagged.rb +2 -0
  51. data/lib/graphql/schema/directive.rb +33 -1
  52. data/lib/graphql/schema/field.rb +23 -1
  53. data/lib/graphql/schema/input_object.rb +38 -30
  54. data/lib/graphql/schema/list.rb +1 -1
  55. data/lib/graphql/schema/member/has_arguments.rb +2 -2
  56. data/lib/graphql/schema/member/has_dataloader.rb +4 -2
  57. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  58. data/lib/graphql/schema/member/has_interfaces.rb +2 -2
  59. data/lib/graphql/schema/member/type_system_helpers.rb +16 -2
  60. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  61. data/lib/graphql/schema/resolver.rb +1 -0
  62. data/lib/graphql/schema/scalar.rb +1 -6
  63. data/lib/graphql/schema/timeout.rb +19 -2
  64. data/lib/graphql/schema/validator/required_validator.rb +15 -6
  65. data/lib/graphql/schema/visibility/migration.rb +2 -2
  66. data/lib/graphql/schema/visibility/profile.rb +107 -21
  67. data/lib/graphql/schema/visibility.rb +41 -29
  68. data/lib/graphql/schema/warden.rb +13 -5
  69. data/lib/graphql/schema.rb +228 -32
  70. data/lib/graphql/static_validation/all_rules.rb +2 -2
  71. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  72. data/lib/graphql/static_validation/rules/fields_will_merge.rb +78 -16
  73. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  74. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  75. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  76. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +6 -2
  77. data/lib/graphql/testing/helpers.rb +5 -2
  78. data/lib/graphql/tracing/active_support_notifications_trace.rb +7 -0
  79. data/lib/graphql/tracing/appoptics_tracing.rb +5 -0
  80. data/lib/graphql/tracing/appsignal_trace.rb +26 -61
  81. data/lib/graphql/tracing/data_dog_trace.rb +41 -164
  82. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  83. data/lib/graphql/tracing/new_relic_trace.rb +34 -164
  84. data/lib/graphql/tracing/notifications_trace.rb +183 -37
  85. data/lib/graphql/tracing/null_trace.rb +1 -1
  86. data/lib/graphql/tracing/perfetto_trace.rb +16 -19
  87. data/lib/graphql/tracing/prometheus_trace.rb +47 -74
  88. data/lib/graphql/tracing/scout_trace.rb +25 -59
  89. data/lib/graphql/tracing/sentry_trace.rb +56 -99
  90. data/lib/graphql/tracing/statsd_trace.rb +24 -47
  91. data/lib/graphql/tracing/trace.rb +0 -17
  92. data/lib/graphql/tracing.rb +1 -0
  93. data/lib/graphql/type_kinds.rb +1 -0
  94. data/lib/graphql/version.rb +1 -1
  95. data/lib/graphql.rb +1 -1
  96. metadata +35 -26
  97. data/lib/graphql/dashboard/views/graphql/dashboard/traces/index.html.erb +0 -63
  98. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -0,0 +1,69 @@
1
+ <% content_for(:title, "Clients") %>
2
+ <div class="row mt-3 justify-content-between">
3
+ <div class="col-4">
4
+ <h3>
5
+ <%= pluralize(@clients_page.total_count, "Client") %>
6
+ </h3>
7
+ </div>
8
+ <div class="col-auto">
9
+ <%= link_to("New Client", graphql_dashboard.new_operation_store_client_path, class: "btn btn-outline-primary") %>
10
+ </div>
11
+ </div>
12
+
13
+ <table class="table table-striped">
14
+ <thead>
15
+ <tr>
16
+ <th><%= link_to("Name", graphql_dashboard.operation_store_clients_path, params: { order_by: "name", order_dir: ((@order_by == "name" && @order_dir != :desc) ? "desc" : "asc" )}) %></th>
17
+ <th>Operations</th>
18
+ <th>Created At</th>
19
+ <th>Last Updated</th>
20
+ <th><%= link_to("Last Used At", graphql_dashboard.operation_store_clients_path, params: { order_by: "last_used_at", order_dir: ((@order_by == "last_used_at" && @order_dir != :desc) ? "desc": "asc")}) %></th>
21
+ </tr>
22
+ </thead>
23
+ <tbody>
24
+ <% if @clients_page.total_count == 0 %>
25
+ <tr>
26
+ <td colspan="5" class="text-center">
27
+ <em>To get started, create a <%= link_to "new client", graphql_dashboard.new_operation_store_client_path %>, then <%= link_to "sync operations", "https://graphql-ruby.org/operation_store/client_workflow.html" %> to your schema.</em>
28
+ </td>
29
+ </tr>
30
+ <% else %>
31
+ <% @clients_page.items.each do |client| %>
32
+ <tr>
33
+ <td><%= link_to(client.name, graphql_dashboard.edit_operation_store_client_path(name: client.name)) %></td>
34
+ <td>
35
+ <%= link_to(graphql_dashboard.operation_store_client_operations_path(client_name: client.name)) do %>
36
+ <%= client.operations_count %><% if client.archived_operations_count > 0 %> <span class="muted">(<%=client.archived_operations_count%> archived)</span><% end %>
37
+ <% end %>
38
+ </td>
39
+ <td><%= client.created_at %></td>
40
+ <td>
41
+ <% if client.operations_count == 0 %>
42
+ &mdash;
43
+ <% else %>
44
+ <%= client.last_synced_at %>
45
+ <% end %>
46
+ </td>
47
+ <td><%= client.last_used_at || "—" %></td>
48
+ </tr>
49
+ <% end %>
50
+ <% end %>
51
+ </tbody>
52
+ </table>
53
+
54
+ <div class="row">
55
+ <div class="col-auto">
56
+ <% if @clients_page.prev_page %>
57
+ <%= link_to("« prev", graphql_dashboard.operation_store_clients_path(per_page: params[:per_page], page: @clients_page.prev_page), class: "btn btn-outline-secondary") %>
58
+ <% else %>
59
+ <button class="btn btn-outline-secondary" disabled>« prev</button>
60
+ <% end %>
61
+ </div>
62
+ <div class="col-auto">
63
+ <% if @clients_page.next_page %>
64
+ <%= link_to("next »", graphql_dashboard.operation_store_clients_path(per_page: params[:per_page], page: @clients_page.next_page), class: "btn btn-outline-secondary") %>
65
+ <% else %>
66
+ <button class="btn btn-outline-secondary" disabled>next »</button>
67
+ <% end %>
68
+ </div>
69
+ </div>
@@ -0,0 +1,7 @@
1
+ <% content_for(:title, "New Client") %>
2
+ <div class="row">
3
+ <div class="col">
4
+ <h1>New Client</h1>
5
+ </div>
6
+ <div>
7
+ <%= render partial: "graphql/dashboard/operation_store/clients/form" %>
@@ -0,0 +1,39 @@
1
+ <% content_for(:title, "Index#{@search_term ? " - #{@search_term}" : ""}") %>
2
+ <div class="row mt-2">
3
+ <h3>Schema Index</h3>
4
+ <div class="col">
5
+ <p style="margin-left: 15px;">
6
+ <%= pluralize(@index_entries_page.total_count, @search_term ? "result" : "entry") %>
7
+ </p>
8
+ </div>
9
+ <div class="col">
10
+ <form method="GET" action="<%= graphql_dashboard.operation_store_index_entries_path %>" style="margin-left: auto; margin-top:-5px;">
11
+ <div class="input-group">
12
+ <%= text_field_tag "q", @search_term, class: "form-control", placeholder: "Find types, fields, arguments, or enum values" %>
13
+ <input type="submit" value="Search" class="btn btn-outline-primary btn-sm"/>
14
+ </div>
15
+ </form>
16
+ </div>
17
+ </div>
18
+ <table class="table">
19
+ <thead>
20
+ <tr>
21
+ <th>Name</th>
22
+ <th># Usages</th>
23
+ <th>Last Used At</th>
24
+ </tr>
25
+ </thead>
26
+ <tbody>
27
+ <% @index_entries_page.items.each do |entry| %>
28
+ <tr>
29
+ <td><%= link_to(entry.name, graphql_dashboard.operation_store_index_entry_path(name: entry.name)) %></td>
30
+ <td><%= entry.references_count %><% if entry.archived_references_count.nil? %><span class="muted">(missing data - call `YourSchema.operation_store.reindex` to repair index)</span><% elsif entry.archived_references_count > 0 %> <span class="muted">(<%= entry.archived_references_count %> archived)</span><% end %></td>
31
+ <td><%= entry.last_used_at %></td>
32
+ </tr>
33
+ <% end %>
34
+ </tbody>
35
+ </table>
36
+
37
+ <%=
38
+ # render_partial("_pagination")
39
+ %>
@@ -0,0 +1,32 @@
1
+ <% name = @chain.pop %>
2
+ <% content_for(:title, "Index - #{@entry.name}") %>
3
+ <div class="row mt-2">
4
+ <div class="col">
5
+ <%= link_to("Index", graphql_dashboard.operation_store_index_entries_path) %>
6
+ <% @chain.each do |c| %>
7
+ > <%= link_to(c.split(".").last, graphql_dashboard.operation_store_index_entry_path(name: c)) %>
8
+ <% end %>
9
+ > <%= name.split(".").last %>
10
+ </div>
11
+ </div>
12
+ <div class="row mt-2">
13
+ <div class="col">
14
+ <h3><%= name %></h3>
15
+ <p>
16
+ Used By:
17
+ <% if @operations.any? %>
18
+ <ul>
19
+ <% @operations.each do |operation| %>
20
+ <li>
21
+ <%= link_to(operation.name, graphql_dashboard.operation_store_operation_path(digest: operation.digest)) %><% if operation.is_archived %> <span class="muted">(archived)</span><% end %>
22
+ </li>
23
+ <% end %>
24
+ </ul>
25
+ <% else %>
26
+ <i>none</i>
27
+ <% end %>
28
+ </p>
29
+
30
+ <p>Last used at: <%= @entry.last_used_at || "—" %></p>
31
+ </div>
32
+ </div>
@@ -0,0 +1,81 @@
1
+ <div class="row mt-2">
2
+ <% if @client_operations %>
3
+ <%= content_for(:title, "#{params[:client_name]} Operations") %>
4
+ <div class="col">
5
+ <h3><%= params[:client_name] %></h3>
6
+ <ul class="nav nav-tabs">
7
+ <li class="nav-item">
8
+ <%= link_to "#{@unarchived_operations_count} Active", graphql_dashboard.operation_store_client_operations_path(client_name: params[:client_name]), class: "nav-link #{@is_archived ? "" : "active"}" %>
9
+ </li>
10
+ <li class="nav-item">
11
+ <%= link_to "#{@archived_operations_count} Archived", graphql_dashboard.archived_operation_store_client_operations_path(client_name: params[:client_name]), class: "nav-link #{@is_archived ? "active" : ""}" %>
12
+ </li>
13
+ </ul>
14
+ </div>
15
+ <% else %>
16
+ <%= content_for(:title, "Operations") %>
17
+ <div class="col">
18
+ <ul class="nav nav-tabs">
19
+ <li class="nav-item">
20
+ <%= link_to "#{@unarchived_operations_count} Active", graphql_dashboard.operation_store_operations_path, class: "nav-link #{@is_archived ? "" : "active"}" %>
21
+ </li>
22
+ <li class="nav-item">
23
+ <%= link_to "#{@archived_operations_count} Archived", graphql_dashboard.archived_operation_store_operations_path, class: "nav-link #{@is_archived ? "active" : ""}" %>
24
+ </li>
25
+ </ul>
26
+ </div>
27
+ <% end %>
28
+ </div>
29
+
30
+ <div class="row">
31
+ <div class="col">
32
+ <table class="table table-striped">
33
+ <thead>
34
+ <tr>
35
+ <th><%= link_to "Name", graphql_dashboard.operation_store_operations_path({ order_by: "name", order_dir: params[:order_dir] == "asc" ? "desc" : "asc" }) %></th>
36
+ <% if @client_operations %>
37
+ <th>Alias</th>
38
+ <% else %>
39
+ <th># Clients</th>
40
+ <% end %>
41
+ <th>Digest</th>
42
+ <th><%= link_to "Last Used At", graphql_dashboard.operation_store_operations_path({ order_by: "last_used_at", order_dir: params[:order_dir] == "asc" ? "desc" : "asc" }) %></th>
43
+ <th>
44
+ <button class="btn btn-sm btn-outline-primary" data-archive-client="<%= params[:client_name] %>" data-archive-all="<%= params[:client_name] ? nil : "true" %>">
45
+ <%= @is_archived ? "Unarchive" : "Archive" %>
46
+ </button>
47
+ </th>
48
+ </tr>
49
+ </thead>
50
+ <tbody>
51
+ <% if @operations_page.total_count == 0 %>
52
+ <tr>
53
+ <td colspan="5" class="text-center">
54
+ <% if @is_archived %>
55
+ <em><%= link_to "Archived operations", "https://graphql-ruby.org/operation_store/server_management.html#archiving-and-deleting-data" %> will appear here.</em>
56
+ <% else %>
57
+ <em>Add your first stored operations with <%= link_to "sync", "https://graphql-ruby.org/operation_store/client_workflow.html" %>.</em>
58
+ <% end %>
59
+ </td>
60
+ </tr>
61
+ <% else %>
62
+ <% @operations_page.items.each do |operation| %>
63
+ <tr>
64
+ <td><%= link_to(operation.name, graphql_dashboard.operation_store_operation_path(digest: operation.digest)) %></td>
65
+ <% if @client_operations %>
66
+ <td><code><%= operation.operation_alias %></code></td>
67
+ <% else %>
68
+ <td><%= operation.clients_count %></td>
69
+ <% end %>
70
+ <td><code><%= operation.digest %></code></td>
71
+ <td><%= operation.last_used_at %></td>
72
+ <td>
73
+ <%= check_box_tag("value", (@client_operations ? operation.operation_alias : operation.digest), class: "archive-check form-check-input") %>
74
+ </td>
75
+ </tr>
76
+ <% end %>
77
+ <% end %>
78
+ </tbody>
79
+ </table>
80
+ </div>
81
+ </div>
@@ -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.