diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-16 18:18:33 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-16 18:18:33 +0000 |
commit | f64a639bcfa1fc2bc89ca7db268f594306edfd7c (patch) | |
tree | a2c3c2ebcc3b45e596949db485d6ed18ffaacfa1 /lib/gitlab/graphql | |
parent | bfbc3e0d6583ea1a91f627528bedc3d65ba4b10f (diff) | |
download | gitlab-ce-f64a639bcfa1fc2bc89ca7db268f594306edfd7c.tar.gz |
Add latest changes from gitlab-org/gitlab@13-10-stable-eev13.10.0-rc40
Diffstat (limited to 'lib/gitlab/graphql')
-rw-r--r-- | lib/gitlab/graphql/calls_gitaly.rb | 15 | ||||
-rw-r--r-- | lib/gitlab/graphql/calls_gitaly/field_extension.rb | 87 | ||||
-rw-r--r-- | lib/gitlab/graphql/calls_gitaly/instrumentation.rb | 40 | ||||
-rw-r--r-- | lib/gitlab/graphql/docs/helper.rb | 53 | ||||
-rw-r--r-- | lib/gitlab/graphql/docs/templates/default.md.haml | 87 | ||||
-rw-r--r-- | lib/gitlab/graphql/extensions/externally_paginated_array_extension.rb | 2 | ||||
-rw-r--r-- | lib/gitlab/graphql/pagination/keyset/connection.rb | 1 | ||||
-rw-r--r-- | lib/gitlab/graphql/pagination/keyset/generic_keyset_pagination.rb | 47 | ||||
-rw-r--r-- | lib/gitlab/graphql/pagination/keyset/last_items.rb | 38 | ||||
-rw-r--r-- | lib/gitlab/graphql/pagination/keyset/order_info.rb | 7 | ||||
-rw-r--r-- | lib/gitlab/graphql/present.rb | 23 | ||||
-rw-r--r-- | lib/gitlab/graphql/present/field_extension.rb | 36 | ||||
-rw-r--r-- | lib/gitlab/graphql/present/instrumentation.rb | 49 | ||||
-rw-r--r-- | lib/gitlab/graphql/query_analyzers/logger_analyzer.rb | 4 |
14 files changed, 308 insertions, 181 deletions
diff --git a/lib/gitlab/graphql/calls_gitaly.rb b/lib/gitlab/graphql/calls_gitaly.rb deleted file mode 100644 index 40cd74a34f2..00000000000 --- a/lib/gitlab/graphql/calls_gitaly.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Graphql - # Wraps the field resolution to count Gitaly calls before and after. - # Raises an error if the field calls Gitaly but hadn't declared such. - module CallsGitaly - extend ActiveSupport::Concern - - def self.use(schema_definition) - schema_definition.instrument(:field, Gitlab::Graphql::CallsGitaly::Instrumentation.new, after_built_ins: true) - end - end - end -end diff --git a/lib/gitlab/graphql/calls_gitaly/field_extension.rb b/lib/gitlab/graphql/calls_gitaly/field_extension.rb new file mode 100644 index 00000000000..32530b47ce3 --- /dev/null +++ b/lib/gitlab/graphql/calls_gitaly/field_extension.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +module Gitlab + module Graphql + module CallsGitaly + # Check if any `calls_gitaly: true` declarations need to be added + # + # See BaseField: this extension is not applied if the field does not + # need it (i.e. it has a constant complexity or knows that it calls + # gitaly) + class FieldExtension < ::GraphQL::Schema::FieldExtension + include Laziness + + def resolve(object:, arguments:, **rest) + yield(object, arguments, [current_gitaly_call_count, accounted_for]) + end + + def after_resolve(value:, memo:, **rest) + (value, count) = value_with_count(value, memo) + calls_gitaly_check(count) + accounted_for(count) + + value + end + + private + + # Resolutions are not nested nicely (due to laziness), so we have to + # know not just how many calls were made before resolution started, but + # also how many were accounted for by fields with the correct settings + # in between. + # + # e.g. the following is not just plausible, but common: + # + # enter A.user (lazy) + # enter A.x + # leave A.x + # enter A.calls_gitaly + # leave A.calls_gitaly (accounts for 1 call) + # leave A.user + # + # In this circumstance we need to mark the calls made by A.calls_gitaly + # as accounted for, even though they were made after we yielded + # in A.user + def value_with_count(value, (previous_count, previous_accounted_for)) + newly_accounted_for = accounted_for - previous_accounted_for + value = force(value) + count = [current_gitaly_call_count - (previous_count + newly_accounted_for), 0].max + + [value, count] + end + + def current_gitaly_call_count + Gitlab::GitalyClient.get_request_count || 0 + end + + def calls_gitaly_check(calls) + return if calls < 1 || field.may_call_gitaly? + + error = RuntimeError.new(<<~ERROR) + #{field_name} unexpectedly calls Gitaly! + + Please either specify a constant complexity or add `calls_gitaly: true` + to the field declaration + ERROR + Gitlab::ErrorTracking.track_and_raise_for_dev_exception(error) + end + + def accounted_for(count = nil) + return 0 unless Gitlab::SafeRequestStore.active? + + Gitlab::SafeRequestStore["graphql_gitaly_accounted_for"] ||= 0 + + if count.nil? + Gitlab::SafeRequestStore["graphql_gitaly_accounted_for"] + else + Gitlab::SafeRequestStore["graphql_gitaly_accounted_for"] += count + end + end + + def field_name + "#{field.owner.graphql_name}.#{field.graphql_name}" + end + end + end + end +end diff --git a/lib/gitlab/graphql/calls_gitaly/instrumentation.rb b/lib/gitlab/graphql/calls_gitaly/instrumentation.rb deleted file mode 100644 index 11d3c50e093..00000000000 --- a/lib/gitlab/graphql/calls_gitaly/instrumentation.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Graphql - module CallsGitaly - class Instrumentation - # Check if any `calls_gitaly: true` declarations need to be added - # Do nothing if a constant complexity was provided - def instrument(_type, field) - type_object = field.metadata[:type_class] - return field unless type_object.respond_to?(:calls_gitaly?) - return field if type_object.constant_complexity? || type_object.calls_gitaly? - - old_resolver_proc = field.resolve_proc - - gitaly_wrapped_resolve = -> (typed_object, args, ctx) do - previous_gitaly_call_count = Gitlab::GitalyClient.get_request_count - result = old_resolver_proc.call(typed_object, args, ctx) - current_gitaly_call_count = Gitlab::GitalyClient.get_request_count - calls_gitaly_check(type_object, current_gitaly_call_count - previous_gitaly_call_count) - result - end - - field.redefine do - resolve(gitaly_wrapped_resolve) - end - end - - def calls_gitaly_check(type_object, calls) - return if calls < 1 - - # Will inform you if there needs to be `calls_gitaly: true` as a kwarg in the field declaration - # if there is at least 1 Gitaly call involved with the field resolution. - error = RuntimeError.new("Gitaly is called for field '#{type_object.name}' on #{type_object.owner.try(:name)} - please either specify a constant complexity or add `calls_gitaly: true` to the field declaration") - Gitlab::ErrorTracking.track_and_raise_for_dev_exception(error) - end - end - end - end -end diff --git a/lib/gitlab/graphql/docs/helper.rb b/lib/gitlab/graphql/docs/helper.rb index ad9e08e189c..e9ff85d9ca9 100644 --- a/lib/gitlab/graphql/docs/helper.rb +++ b/lib/gitlab/graphql/docs/helper.rb @@ -27,33 +27,36 @@ module Gitlab MD end - def render_name_and_description(object) - content = "### #{object[:name]}\n" + def render_name_and_description(object, level = 3) + content = [] + + content << "#{'#' * level} `#{object[:name]}`" if object[:description].present? - content += "\n#{object[:description]}.\n" + desc = object[:description].strip + desc += '.' unless desc.ends_with?('.') + content << desc end - content + content.join("\n\n") end def sorted_by_name(objects) + return [] unless objects.present? + objects.sort_by { |o| o[:name] } end def render_field(field) - '| %s | %s | %s |' % [ - render_name(field), - render_field_type(field[:type][:info]), - render_description(field) - ] + row(render_name(field), render_field_type(field[:type]), render_description(field)) end def render_enum_value(value) - '| %s | %s |' % [ - render_name(value), - render_description(value) - ] + row(render_name(value), render_description(value)) + end + + def row(*values) + "| #{values.join(' | ')} |" end def render_name(object) @@ -70,27 +73,19 @@ module Gitlab "**Deprecated:** #{object[:deprecation_reason]}" end - # Some fields types are arrays of other types and are displayed - # on docs wrapped in square brackets, for example: [String!]. - # This makes GitLab docs renderer thinks they are links so here - # we change them to be rendered as: String! => Array. def render_field_type(type) - array_type = type[/\[(.+)\]/, 1] + "[`#{type[:info]}`](##{type[:name].downcase})" + end - if array_type - "#{array_type} => Array" - else - type - end + def render_return_type(query) + "Returns #{render_field_type(query[:type])}.\n" end # We are ignoring connections and built in types for now, # they should be added when queries are generated. def objects object_types = graphql_object_types.select do |object_type| - !object_type[:name]["Connection"] && - !object_type[:name]["Edge"] && - !object_type[:name]["__"] + !object_type[:name]["__"] end object_types.each do |type| @@ -98,10 +93,14 @@ module Gitlab end end + def queries + graphql_operation_types.find { |type| type[:name] == 'Query' }.to_h.values_at(:fields, :connections).flatten + end + # We ignore the built-in enum types. def enums graphql_enum_types.select do |enum_type| - !enum_type[:name].in?(%w(__DirectiveLocation __TypeKind)) + !enum_type[:name].in?(%w[__DirectiveLocation __TypeKind]) end end end diff --git a/lib/gitlab/graphql/docs/templates/default.md.haml b/lib/gitlab/graphql/docs/templates/default.md.haml index 9dfb9b090a8..847f1777b08 100644 --- a/lib/gitlab/graphql/docs/templates/default.md.haml +++ b/lib/gitlab/graphql/docs/templates/default.md.haml @@ -14,13 +14,31 @@ WARNING: Fields that are deprecated are marked with **{warning-solid}**. - Items (fields, enums, etc) that have been removed according to our [deprecation process](../index.md#deprecation-process) can be found + Items (fields, enums, etc) that have been removed according to our [deprecation process](../index.md#deprecation-and-removal-process) can be found in [Removed Items](../removed_items.md). <!-- vale gitlab.Spelling = NO --> \ :plain + ## `Query` type + + The `Query` type contains the API's top-level entry points for all executable queries. +\ + +- sorted_by_name(queries).each do |query| + = render_name_and_description(query) + \ + = render_return_type(query) + - unless query[:arguments].empty? + ~ "#### Arguments\n" + ~ "| Name | Type | Description |" + ~ "| ---- | ---- | ----------- |" + - sorted_by_name(query[:arguments]).each do |argument| + = render_field(argument) + \ + +:plain ## Object types Object types represent the resources that the GitLab GraphQL API can return. @@ -36,6 +54,7 @@ - objects.each do |type| - unless type[:fields].empty? = render_name_and_description(type) + \ ~ "| Field | Type | Description |" ~ "| ----- | ---- | ----------- |" - sorted_by_name(type[:fields]).each do |field| @@ -56,8 +75,74 @@ - enums.each do |enum| - unless enum[:values].empty? = render_name_and_description(enum) + \ ~ "| Value | Description |" ~ "| ----- | ----------- |" - sorted_by_name(enum[:values]).each do |value| = render_enum_value(value) \ + +:plain + ## Scalar types + + Scalar values are atomic values, and do not have fields of their own. + Basic scalars include strings, boolean values, and numbers. This schema also + defines various custom scalar values, such as types for times and dates. + + This schema includes custom scalar types for identifiers, with a specific type for + each kind of object. + + For more information, read about [Scalar Types](https://graphql.org/learn/schema/#scalar-types) on `graphql.org`. +\ + +- graphql_scalar_types.each do |type| + = render_name_and_description(type) + \ + +:plain + ## Abstract types + + Abstract types (unions and interfaces) are ways the schema can represent + values that may be one of several concrete types. + + - A [`Union`](https://graphql.org/learn/schema/#union-types) is a set of possible types. + The types might not have any fields in common. + - An [`Interface`](https://graphql.org/learn/schema/#interfaces) is a defined set of fields. + Types may `implement` an interface, which + guarantees that they have all the fields in the set. A type may implement more than + one interface. + + See the [GraphQL documentation](https://graphql.org/learn/) for more information on using + abstract types. +\ + +:plain + ### Unions +\ + +- graphql_union_types.each do |type| + = render_name_and_description(type, 4) + \ + One of: + \ + - type[:possible_types].each do |type_name| + ~ "- [`#{type_name}`](##{type_name.downcase})" + \ + +:plain + ### Interfaces +\ + +- graphql_interface_types.each do |type| + = render_name_and_description(type, 4) + \ + Implementations: + \ + - type[:implemented_by].each do |type_name| + ~ "- [`#{type_name}`](##{type_name.downcase})" + \ + ~ "| Field | Type | Description |" + ~ "| ----- | ---- | ----------- |" + - sorted_by_name(type[:fields] + type[:connections]).each do |field| + = render_field(field) + \ diff --git a/lib/gitlab/graphql/extensions/externally_paginated_array_extension.rb b/lib/gitlab/graphql/extensions/externally_paginated_array_extension.rb index 1adedb500e6..f787e7be94a 100644 --- a/lib/gitlab/graphql/extensions/externally_paginated_array_extension.rb +++ b/lib/gitlab/graphql/extensions/externally_paginated_array_extension.rb @@ -4,7 +4,7 @@ module Gitlab module Extensions class ExternallyPaginatedArrayExtension < GraphQL::Schema::Field::ConnectionExtension def resolve(object:, arguments:, context:) - yield(object, arguments) + yield(object, arguments, arguments) end end end diff --git a/lib/gitlab/graphql/pagination/keyset/connection.rb b/lib/gitlab/graphql/pagination/keyset/connection.rb index f95c91c5706..e525996ec10 100644 --- a/lib/gitlab/graphql/pagination/keyset/connection.rb +++ b/lib/gitlab/graphql/pagination/keyset/connection.rb @@ -33,6 +33,7 @@ module Gitlab include Gitlab::Utils::StrongMemoize include ::Gitlab::Graphql::ConnectionCollectionMethods prepend ::Gitlab::Graphql::ConnectionRedaction + prepend GenericKeysetPagination # rubocop: disable Naming/PredicateName # https://relay.dev/graphql/connections.htm#sec-undefined.PageInfo.Fields diff --git a/lib/gitlab/graphql/pagination/keyset/generic_keyset_pagination.rb b/lib/gitlab/graphql/pagination/keyset/generic_keyset_pagination.rb new file mode 100644 index 00000000000..318c6e1734f --- /dev/null +++ b/lib/gitlab/graphql/pagination/keyset/generic_keyset_pagination.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Gitlab + module Graphql + module Pagination + module Keyset + # Use the generic keyset implementation if the given ActiveRecord scope supports it. + # Note: this module is temporary, at some point it will be merged with Keyset::Connection + module GenericKeysetPagination + extend ActiveSupport::Concern + + def ordered_items + return super unless Gitlab::Pagination::Keyset::Order.keyset_aware?(items) + + items + end + + def cursor_for(node) + return super unless Gitlab::Pagination::Keyset::Order.keyset_aware?(items) + + order = Gitlab::Pagination::Keyset::Order.extract_keyset_order_object(items) + encode(order.cursor_attributes_for_node(node).to_json) + end + + def slice_nodes(sliced, encoded_cursor, before_or_after) + return super unless Gitlab::Pagination::Keyset::Order.keyset_aware?(sliced) + + order = Gitlab::Pagination::Keyset::Order.extract_keyset_order_object(sliced) + order = order.reversed_order if before_or_after == :before + + decoded_cursor = ordering_from_encoded_json(encoded_cursor) + order.apply_cursor_conditions(sliced, decoded_cursor) + end + + def sliced_nodes + return super unless Gitlab::Pagination::Keyset::Order.keyset_aware?(items) + + sliced = ordered_items + sliced = slice_nodes(sliced, before, :before) if before.present? + sliced = slice_nodes(sliced, after, :after) if after.present? + sliced + end + end + end + end + end +end diff --git a/lib/gitlab/graphql/pagination/keyset/last_items.rb b/lib/gitlab/graphql/pagination/keyset/last_items.rb index 45bf15236c1..960567a6fbc 100644 --- a/lib/gitlab/graphql/pagination/keyset/last_items.rb +++ b/lib/gitlab/graphql/pagination/keyset/last_items.rb @@ -10,46 +10,14 @@ module Gitlab class LastItems # rubocop: disable CodeReuse/ActiveRecord def self.take_items(scope, count) - if custom_order = lookup_custom_reverse_order(scope.order_values) - items = scope.reorder(*custom_order).first(count) # returns a single record when count is nil + if Gitlab::Pagination::Keyset::Order.keyset_aware?(scope) + order = Gitlab::Pagination::Keyset::Order.extract_keyset_order_object(scope) + items = scope.reorder(order.reversed_order).first(count) items.is_a?(Array) ? items.reverse : items else scope.last(count) end end - # rubocop: enable CodeReuse/ActiveRecord - - # Detect special ordering and provide the reversed order - def self.lookup_custom_reverse_order(order_values) - if ordering_by_merged_at_and_mr_id_desc?(order_values) - [ - Gitlab::Database.nulls_first_order('merge_request_metrics.merged_at', 'ASC'), # reversing the order - MergeRequest.arel_table[:id].asc - ] - elsif ordering_by_merged_at_and_mr_id_asc?(order_values) - [ - Gitlab::Database.nulls_first_order('merge_request_metrics.merged_at', 'DESC'), - MergeRequest.arel_table[:id].asc - ] - end - end - - def self.ordering_by_merged_at_and_mr_id_desc?(order_values) - order_values.size == 2 && - order_values.first.to_s == Gitlab::Database.nulls_last_order('merge_request_metrics.merged_at', 'DESC') && - order_values.last.is_a?(Arel::Nodes::Descending) && - order_values.last.to_sql == MergeRequest.arel_table[:id].desc.to_sql - end - - def self.ordering_by_merged_at_and_mr_id_asc?(order_values) - order_values.size == 2 && - order_values.first.to_s == Gitlab::Database.nulls_last_order('merge_request_metrics.merged_at', 'ASC') && - order_values.last.is_a?(Arel::Nodes::Descending) && - order_values.last.to_sql == MergeRequest.arel_table[:id].desc.to_sql - end - - private_class_method :ordering_by_merged_at_and_mr_id_desc? - private_class_method :ordering_by_merged_at_and_mr_id_asc? end end end diff --git a/lib/gitlab/graphql/pagination/keyset/order_info.rb b/lib/gitlab/graphql/pagination/keyset/order_info.rb index d37264c1343..0494329bfd9 100644 --- a/lib/gitlab/graphql/pagination/keyset/order_info.rb +++ b/lib/gitlab/graphql/pagination/keyset/order_info.rb @@ -92,8 +92,6 @@ module Gitlab def extract_attribute_values(order_value) if ordering_by_lower?(order_value) [order_value.expr.expressions[0].name.to_s, order_value.direction, order_value.expr] - elsif ordering_by_similarity?(order_value) - ['similarity', order_value.direction, order_value.expr] elsif ordering_by_case?(order_value) ['case_order_value', order_value.direction, order_value.expr] elsif ordering_by_array_position?(order_value) @@ -113,11 +111,6 @@ module Gitlab order_value.expr.is_a?(Arel::Nodes::NamedFunction) && order_value.expr&.name&.downcase == 'array_position' end - # determine if ordering using SIMILARITY scoring based on Gitlab::Database::SimilarityScore - def ordering_by_similarity?(order_value) - Gitlab::Database::SimilarityScore.order_by_similarity?(order_value) - end - # determine if ordering using CASE def ordering_by_case?(order_value) order_value.expr.is_a?(Arel::Nodes::Case) diff --git a/lib/gitlab/graphql/present.rb b/lib/gitlab/graphql/present.rb index 6d86d632ab4..fdaf075eb25 100644 --- a/lib/gitlab/graphql/present.rb +++ b/lib/gitlab/graphql/present.rb @@ -12,11 +12,30 @@ module Gitlab def self.presenter_class @presenter_class end + + def self.present(object, attrs) + klass = @presenter_class + return object if !klass || object.is_a?(klass) + + @presenter_class.new(object, **attrs) + end + end + + def unpresented + unwrapped || object end - def self.use(schema_definition) - schema_definition.instrument(:field, ::Gitlab::Graphql::Present::Instrumentation.new) + def present(object_type, attrs) + return unless object_type.respond_to?(:present) + + self.unwrapped ||= object + # @object belongs to Schema::Object, which does not expose a writer. + @object = object_type.present(unwrapped, attrs) # rubocop: disable Gitlab/ModuleWithInstanceVariables end + + private + + attr_accessor :unwrapped end end end diff --git a/lib/gitlab/graphql/present/field_extension.rb b/lib/gitlab/graphql/present/field_extension.rb new file mode 100644 index 00000000000..2e211b70d35 --- /dev/null +++ b/lib/gitlab/graphql/present/field_extension.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Gitlab + module Graphql + module Present + class FieldExtension < ::GraphQL::Schema::FieldExtension + SAFE_CONTEXT_KEYS = %i[current_user].freeze + + def resolve(object:, arguments:, context:) + attrs = safe_context_values(context) + + # We need to handle the object being either a Schema::Object or an + # inner Schema::Object#object. This depends on whether the field + # has a @resolver_proc or not. + if object.is_a?(::Types::BaseObject) + object.present(field.owner, attrs) + yield(object, arguments) + else + # This is the legacy code-path, hit if the field has a @resolver_proc + # TODO: remove this when resolve procs are removed from the + # graphql-ruby library, and all field instrumentation is removed. + # See: https://github.com/rmosolgo/graphql-ruby/issues/3385 + presented = field.owner.try(:present, object, attrs) || object + yield(presented, arguments) + end + end + + private + + def safe_context_values(context) + context.to_h.slice(*SAFE_CONTEXT_KEYS) + end + end + end + end +end diff --git a/lib/gitlab/graphql/present/instrumentation.rb b/lib/gitlab/graphql/present/instrumentation.rb deleted file mode 100644 index b8535575da5..00000000000 --- a/lib/gitlab/graphql/present/instrumentation.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Graphql - module Present - class Instrumentation - SAFE_CONTEXT_KEYS = %i[current_user].freeze - - def instrument(type, field) - return field unless field.metadata[:type_class] - - presented_in = field.metadata[:type_class].owner - return field unless presented_in.respond_to?(:presenter_class) - return field unless presented_in.presenter_class - - old_resolver = field.resolve_proc - - resolve_with_presenter = -> (presented_type, args, context) do - # We need to wrap the original presentation type into a type that - # uses the presenter as an object. - object = presented_type.object - - if object.is_a?(presented_in.presenter_class) - next old_resolver.call(presented_type, args, context) - end - - attrs = safe_context_values(context) - presenter = presented_in.presenter_class.new(object, **attrs) - - # we have to use the new `authorized_new` method, as `new` is protected - wrapped = presented_type.class.authorized_new(presenter, context) - - old_resolver.call(wrapped, args, context) - end - - field.redefine do - resolve(resolve_with_presenter) - end - end - - private - - def safe_context_values(context) - context.to_h.slice(*SAFE_CONTEXT_KEYS) - end - end - end - end -end diff --git a/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb b/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb index 0665ea8b6c9..8acd27869a9 100644 --- a/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb +++ b/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb @@ -9,10 +9,6 @@ module Gitlab FIELD_USAGE_ANALYZER = GraphQL::Analysis::FieldUsage.new { |query, used_fields, used_deprecated_fields| [used_fields, used_deprecated_fields] } ALL_ANALYZERS = [COMPLEXITY_ANALYZER, DEPTH_ANALYZER, FIELD_USAGE_ANALYZER].freeze - def analyze?(query) - Feature.enabled?(:graphql_logging, default_enabled: true) - end - def initial_value(query) variables = process_variables(query.provided_variables) default_initial_values(query).merge({ |