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,97 @@
1
+ # frozen_string_literal: true
2
+ require_relative "./installable"
3
+ module Graphql
4
+ class Dashboard < Rails::Engine
5
+ module Subscriptions
6
+ class BaseController < Graphql::Dashboard::ApplicationController
7
+ include Installable
8
+
9
+ def feature_installed?
10
+ defined?(GraphQL::Pro::Subscriptions) && schema_class.subscriptions.is_a?(GraphQL::Pro::Subscriptions)
11
+ end
12
+
13
+ INSTALLABLE_COMPONENT_HEADER_HTML = "GraphQL-Pro Subscriptions aren't installed on this schema yet.".html_safe
14
+ INSTALLABLE_COMPONENT_MESSAGE_HTML = <<-HTML.html_safe
15
+ Deliver live updates over
16
+ <a href="https://graphql-ruby.org/subscriptions/pusher_implementation.html">Pusher</a> or
17
+ <a href="https://graphql-ruby.org/subscriptions/ably_implementation.html"> Ably</a>
18
+ with GraphQL-Pro's subscription integrations.
19
+ HTML
20
+ end
21
+
22
+ class TopicsController < BaseController
23
+ def show
24
+ topic_name = params[:name]
25
+ all_subscription_ids = []
26
+ schema_class.subscriptions.each_subscription_id(topic_name) do |sid|
27
+ all_subscription_ids << sid
28
+ end
29
+
30
+ page = params[:page]&.to_i || 1
31
+ limit = params[:per_page]&.to_i || 20
32
+ offset = limit * (page - 1)
33
+ subscription_ids = all_subscription_ids[offset, limit]
34
+ subs = schema_class.subscriptions.read_subscriptions(subscription_ids)
35
+ show_broadcast_subscribers_count = schema_class.subscriptions.show_broadcast_subscribers_count?
36
+ subs.each do |sub|
37
+ sub[:is_broadcast] = is_broadcast = schema_class.subscriptions.broadcast_subscription_id?(sub[:id])
38
+ if is_broadcast && show_broadcast_subscribers_count
39
+ sub[:subscribers_count] = sub_count =schema_class.subscriptions.count_broadcast_subscribed(sub[:id])
40
+ sub[:still_subscribed] = sub_count > 0
41
+ else
42
+ sub[:still_subscribed] = schema_class.subscriptions.still_subscribed?(sub[:id])
43
+ sub[:subscribers_count] = nil
44
+ end
45
+ end
46
+
47
+ @topic_last_triggered_at = schema_class.subscriptions.topic_last_triggered_at(topic_name)
48
+ @subscriptions = subs
49
+ @subscriptions_count = all_subscription_ids.size
50
+ @show_broadcast_subscribers_count = show_broadcast_subscribers_count
51
+ @has_next_page = all_subscription_ids.size > offset + limit ? page + 1 : false
52
+ end
53
+
54
+ def index
55
+ page = params[:page]&.to_i || 1
56
+ per_page = params[:per_page]&.to_i || 20
57
+ offset = per_page * (page - 1)
58
+ limit = per_page
59
+ topics, all_topics_count, has_next_page = schema_class.subscriptions.topics(offset: offset, limit: limit)
60
+
61
+ @topics = topics
62
+ @all_topics_count = all_topics_count
63
+ @has_next_page = has_next_page
64
+ @page = page
65
+ end
66
+ end
67
+
68
+ class SubscriptionsController < BaseController
69
+ def show
70
+ subscription_id = params[:id]
71
+ subscriptions = schema_class.subscriptions
72
+ query_data = subscriptions.read_subscription(subscription_id)
73
+ is_broadcast = subscriptions.broadcast_subscription_id?(subscription_id)
74
+
75
+ if is_broadcast && subscriptions.show_broadcast_subscribers_count?
76
+ subscribers_count = subscriptions.count_broadcast_subscribed(subscription_id)
77
+ is_still_subscribed = subscribers_count > 0
78
+ else
79
+ subscribers_count = nil
80
+ is_still_subscribed = subscriptions.still_subscribed?(subscription_id)
81
+ end
82
+
83
+ @query_data = query_data
84
+ @still_subscribed = is_still_subscribed
85
+ @is_broadcast = is_broadcast
86
+ @subscribers_count = subscribers_count
87
+ end
88
+
89
+ def clear_all
90
+ schema_class.subscriptions.clear
91
+ flash[:success] = "All subscription data cleared."
92
+ head :no_content
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,45 @@
1
+ <% content_for(:title, "Profiles") %>
2
+ <div class="row justify-content-between mt-3">
3
+ <div class="col-auto">
4
+ <h3>Detailed Profiles</h3>
5
+ </div>
6
+ <div class="col-auto">
7
+ <%= button_tag "Delete All Traces", class: "btn btn-sm btn-outline-danger", data: { perfetto_delete_all: graphql_dashboard.delete_all_detailed_traces_traces_path } %>
8
+ </div>
9
+ </div>
10
+
11
+ <div class="row">
12
+ <div class="col">
13
+ <table class="table table-striped">
14
+ <thead>
15
+ <tr>
16
+ <th>Operation</th>
17
+ <th>Duration (ms) </th>
18
+ <th>Timestamp</th>
19
+ <th>Open in Perfetto UI</th>
20
+ </tr>
21
+ </thead>
22
+ <tbody>
23
+ <% if @traces.empty? %>
24
+ <tr>
25
+ <td colspan="4" class="text-center">
26
+ <em>No traces saved yet. Read about saving traces <%= link_to "in the docs", "https://graphql-ruby.org/queries/tracing#detailed-profiles" %>.</em>
27
+ </td>
28
+ </tr>
29
+ <% end %>
30
+ <% @traces.each do |trace| %>
31
+ <tr>
32
+ <td><%= trace.operation_name %></td>
33
+ <td><%= trace.duration_ms.round(2) %></td>
34
+ <td><%= Time.at(trace.begin_ms / 1000.0).strftime("%Y-%m-%d %H:%M:%S.%L") %></td>
35
+ <td><%= link_to "View ↗", "#", data: { perfetto_open: trace.operation_name, perfetto_path: graphql_dashboard.detailed_traces_trace_path(trace.id) } %></td>
36
+ <td><%= link_to "Delete", "#", data: { perfetto_delete: graphql_dashboard.detailed_traces_trace_path(trace.id) }, class: "text-danger" %></td>
37
+ </tr>
38
+ <% end %>
39
+ </tbody>
40
+ </table>
41
+ <% if @last && @traces.size >= @last %>
42
+ <%= link_to("Previous >", graphql_dashboard.detailed_traces_traces_path(last: @last, before: @traces.last.begin_ms), class: "btn btn-outline-primary") %>
43
+ <% end %>
44
+ </div>
45
+ </div>
@@ -0,0 +1,18 @@
1
+ <% content_for(:title, "Landing") %>
2
+
3
+ <div class="row">
4
+ <div class="col-md col-lg-8 mx-auto pt-4">
5
+ <div class="card mt-4">
6
+ <div class="card-body">
7
+ <div class="card-title">
8
+ <h2>
9
+ Welcome to the GraphQL-Ruby Dashboard
10
+ </h2>
11
+ </div>
12
+ <p class="card-text">
13
+ Click the links above to see data about your schema (<code><%= schema_class %></code>).
14
+ </p>
15
+ </div>
16
+ </div>
17
+ </div>
18
+ </div>
@@ -0,0 +1,62 @@
1
+ <% content_for(:title, @title) %>
2
+ <% if @install_path %>
3
+ <div class="row mt-3">
4
+ <div class="col">
5
+ <h3><%= @title %></h3>
6
+ <p>It looks like this limiter isn't installed yet. <a href="<%= @install_path %>">Install it now</a>.</p>
7
+ </div>
8
+ </div>
9
+ <% else %>
10
+ <div class="row mt-3 justify-content-between">
11
+ <div class="col-auto">
12
+ <h3><%= @title %></h3>
13
+ </div>
14
+ <div class="col-auto">
15
+ <div class="btn-group">
16
+ <%= link_to("This Hour", graphql_dashboard.limiters_limiter_path(params[:name], chart: "hour"), class: "btn btn-sm btn-outline-primary #{@chart_mode == "hour" ? "active" : "inactive"}", params: { chart: "hour" }) %>
17
+ <%= link_to("Today", graphql_dashboard.limiters_limiter_path(params[:name], chart: "day"), class: "btn btn-sm btn-outline-primary #{@chart_mode == "day" ? "active" : "inactive"}", params: { chart: "day" }) %>
18
+ <%= link_to("This Month", graphql_dashboard.limiters_limiter_path(params[:name], chart: "month"), class: "btn btn-sm btn-outline-primary #{@chart_mode == "month" ? "active" : "inactive"}", params: { chart: "month" }) %>
19
+ </div>
20
+ </div>
21
+ <div class="col-auto">
22
+ <%= form_tag graphql_dashboard.limiters_limiter_path(params[:name], chart: @chart_mode), method: "patch" do %>
23
+ <%= submit_tag "#{@current_soft ? "Disable" : "Enable"} Soft Limiting", class: "btn btn-sm btn-outline-warning" %>
24
+ <% end %>
25
+ </div>
26
+ </div>
27
+
28
+ <div class="row">
29
+ <div class="col ms-4">
30
+ <div id="limiter-histogram">
31
+ <table class="charts-css column multiple stacked show-labels hide-data show-primary-axis">
32
+ <thead>
33
+ <th>Date</th>
34
+ <th scope="col">Limited Requests</th>
35
+ <th scope="col">Unlimited Requests</th>
36
+ </thead>
37
+ <tbody>
38
+ <% @histogram.columns.each_with_index do |col, col_idx| %>
39
+ <tr>
40
+ <th scope="row" class="text-end"><%= col.label %></th>
41
+ <% col.values.each_with_index do |value, val_idx| %>
42
+ <td id="data-<%= col_idx %>-<%= val_idx %>">
43
+ <span class="data"><%= value.formatted_value %></span>
44
+ <span class="tooltip"><%= value.label %>: <%= value.formatted_value %><br><%= col.label %></span>
45
+ </td>
46
+ <% end %>
47
+ </tr>
48
+ <% end %>
49
+ </tbody>
50
+ </table>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ <%= content_tag "style", nonce: @csp_nonce do %>
55
+ <% @histogram.columns.each_with_index do |col, col_idx| %>
56
+ <% col_max = @histogram.max_column_value.to_f %>
57
+ <% col.values.each_with_index do |val, val_idx| %>
58
+ #data-<%= col_idx %>-<%= val_idx %> { --size: <%= val.value / col_max %>}
59
+ <% end %>
60
+ <% end %>
61
+ <% end %>
62
+ <% end %>
@@ -0,0 +1,18 @@
1
+ <% content_for(:title, "Operation Store") %>
2
+
3
+ <div class="row">
4
+ <div class="col-md col-lg-8 mx-auto pt-4">
5
+ <div class="card mt-4">
6
+ <div class="card-body">
7
+ <div class="card-title">
8
+ <h2>
9
+ <%= @component_header_html %>
10
+ </h2>
11
+ </div>
12
+ <p class="card-text">
13
+ <%= @component_message_html %>
14
+ </p>
15
+ </div>
16
+ </div>
17
+ </div>
18
+ </div>
@@ -0,0 +1,24 @@
1
+ <%= form_tag((@client.persisted? ? graphql_dashboard.operation_store_client_path(name: @client.name) : graphql_dashboard.operation_store_clients_path), method: (@client.persisted? ? "patch" : "post")) do %>
2
+ <div class="row">
3
+ <label class="col-2 col-form-label">Name</label>
4
+ <div class="col">
5
+ <%= text_field_tag "client[name]", @client.name, class: "form-control", disabled: @client.persisted? %>
6
+ <div class="form-text">a unique identifier for this owner of persisted operations</div>
7
+ </div>
8
+ </div>
9
+ <div class="row">
10
+ <label class="col-2 col-form-label">Secret</label>
11
+ <div class="col">
12
+ <%= textarea_tag "client[secret]", @client.secret, class: "form-control" %>
13
+ <div class="form-text">authentication credential for <code>sync</code> transactions</div>
14
+ </div>
15
+ </div>
16
+ <div class="row">
17
+ <div class="col-auto">
18
+ <%= submit_tag "Save", class: "btn btn-outline-primary" %>
19
+ </div>
20
+ <div class="col-auto">
21
+ <%= link_to "Back", graphql_dashboard.operation_store_clients_path, class: "btn btn-outline-secondary" %>
22
+ </div>
23
+ </div>
24
+ <% end %>
@@ -0,0 +1,21 @@
1
+ <% content_for(:title, "Edit #{@client.name}") %>
2
+ <div class="row">
3
+ <div class="col">
4
+ <h1>Edit <%= @client.name %></h1>
5
+ </div>
6
+ </div>
7
+ <%= render partial: "graphql/dashboard/operation_store/clients/form" %>
8
+
9
+ <hr class="mt-5">
10
+ <div class="row mt-5">
11
+ <div class="col">
12
+ <div class="alert alert-danger">
13
+ <h4>Delete <%= @client.name %></h4>
14
+ <p>If you delete this client, it will no longer be able to use stored operations.</p>
15
+ <p>There is no way to undo this action.</p>
16
+ <%= form_tag(graphql_dashboard.operation_store_client_path(name: @client.name), method: "delete") do %>
17
+ <%= submit_tag "Permanently Delete #{@client.name.inspect}", class: "btn btn-outline-danger" %>
18
+ <% end %>
19
+ </div>
20
+ </div>
21
+ </div>
@@ -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></p>
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 %>