diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 15:44:42 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 15:44:42 +0000 |
commit | 4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch) | |
tree | 5423a1c7516cffe36384133ade12572cf709398d /lib/gitlab/graphql | |
parent | e570267f2f6b326480d284e0164a6464ba4081bc (diff) | |
download | gitlab-ce-4555e1b21c365ed8303ffb7a3325d773c9b8bf31.tar.gz |
Add latest changes from gitlab-org/gitlab@13-12-stable-eev13.12.0-rc42
Diffstat (limited to 'lib/gitlab/graphql')
-rw-r--r-- | lib/gitlab/graphql/deprecation.rb | 2 | ||||
-rw-r--r-- | lib/gitlab/graphql/docs/helper.rb | 401 | ||||
-rw-r--r-- | lib/gitlab/graphql/docs/renderer.rb | 11 | ||||
-rw-r--r-- | lib/gitlab/graphql/docs/templates/default.md.haml | 144 | ||||
-rw-r--r-- | lib/gitlab/graphql/pagination/keyset/connection.rb | 4 | ||||
-rw-r--r-- | lib/gitlab/graphql/pagination/keyset/generic_keyset_pagination.rb | 13 | ||||
-rw-r--r-- | lib/gitlab/graphql/pagination/keyset/order_info.rb | 10 | ||||
-rw-r--r-- | lib/gitlab/graphql/pagination/keyset/query_builder.rb | 4 | ||||
-rw-r--r-- | lib/gitlab/graphql/present.rb | 6 | ||||
-rw-r--r-- | lib/gitlab/graphql/present/field_extension.rb | 3 | ||||
-rw-r--r-- | lib/gitlab/graphql/queries.rb | 7 | ||||
-rw-r--r-- | lib/gitlab/graphql/query_analyzers/logger_analyzer.rb | 4 | ||||
-rw-r--r-- | lib/gitlab/graphql/variables.rb | 2 |
13 files changed, 480 insertions, 131 deletions
diff --git a/lib/gitlab/graphql/deprecation.rb b/lib/gitlab/graphql/deprecation.rb index e0176e2d6e0..8b73eeb4e52 100644 --- a/lib/gitlab/graphql/deprecation.rb +++ b/lib/gitlab/graphql/deprecation.rb @@ -41,7 +41,7 @@ module Gitlab parts = [ "#{deprecated_in(format: :markdown)}.", reason_text, - replacement.then { |r| "Use: `#{r}`." if r } + replacement.then { |r| "Use: [`#{r}`](##{r.downcase.tr('.', '')})." if r } ].compact case context diff --git a/lib/gitlab/graphql/docs/helper.rb b/lib/gitlab/graphql/docs/helper.rb index f4173e26224..b598b605141 100644 --- a/lib/gitlab/graphql/docs/helper.rb +++ b/lib/gitlab/graphql/docs/helper.rb @@ -5,11 +5,52 @@ return if Rails.env.production? module Gitlab module Graphql module Docs + # We assume a few things about the schema. We use the graphql-ruby gem, which enforces: + # - All mutations have a single input field named 'input' + # - All mutations have a payload type, named after themselves + # - All mutations have an input type, named after themselves + # If these things change, then some of this code will break. Such places + # are guarded with an assertion that our assumptions are not violated. + ViolatedAssumption = Class.new(StandardError) + + SUGGESTED_ACTION = <<~MSG + We expect it to be impossible to violate our assumptions about + how mutation arguments work. + + If that is not the case, then something has probably changed in the + way we generate our schema, perhaps in the library we use: graphql-ruby + + Please ask for help in the #f_graphql or #backend channels. + MSG + + CONNECTION_ARGS = %w[after before first last].to_set + + FIELD_HEADER = <<~MD + #### Fields + + | Name | Type | Description | + | ---- | ---- | ----------- | + MD + + ARG_HEADER = <<~MD + # Arguments + + | Name | Type | Description | + | ---- | ---- | ----------- | + MD + + CONNECTION_NOTE = <<~MD + This field returns a [connection](#connections). It accepts the + four standard [pagination arguments](#connection-pagination-arguments): + `before: String`, `after: String`, `first: Int`, `last: Int`. + MD + # Helper with functions to be used by HAML templates # This includes graphql-docs gem helpers class. # You can check the included module on: https://github.com/gjtorikian/graphql-docs/blob/v1.6.0/lib/graphql-docs/helpers.rb module Helper include GraphQLDocs::Helpers + include Gitlab::Utils::StrongMemoize def auto_generated_comment <<-MD.strip_heredoc @@ -30,44 +71,52 @@ module Gitlab # Template methods: # Methods that return chunks of Markdown for insertion into the document - def render_name_and_description(object, owner: nil, level: 3) - content = [] + def render_full_field(field, heading_level: 3, owner: nil) + conn = connection?(field) + args = field[:arguments].reject { |arg| conn && CONNECTION_ARGS.include?(arg[:name]) } + arg_owner = [owner, field[:name]] + + chunks = [ + render_name_and_description(field, level: heading_level, owner: owner), + render_return_type(field), + render_input_type(field), + render_connection_note(field), + render_argument_table(heading_level, args, arg_owner), + render_return_fields(field, owner: owner) + ] + + join(:block, chunks) + end - content << "#{'#' * level} `#{object[:name]}`" + def render_argument_table(level, args, owner) + arg_header = ('#' * level) + ARG_HEADER + render_field_table(arg_header, args, owner) + end - if object[:description].present? - desc = object[:description].strip - desc += '.' unless desc.ends_with?('.') - end + def render_name_and_description(object, owner: nil, level: 3) + content = [] - if object[:is_deprecated] - owner = Array.wrap(owner) - deprecation = schema_deprecation(owner, object[:name]) - content << (deprecation&.original_description || desc) - content << render_deprecation(object, owner, :block) - else - content << desc - end + heading = '#' * level + name = [owner, object[:name]].compact.join('.') - content.compact.join("\n\n") - end + content << "#{heading} `#{name}`" + content << render_description(object, owner, :block) - def render_return_type(query) - "Returns #{render_field_type(query[:type])}.\n" + join(:block, content) end - def sorted_by_name(objects) - return [] unless objects.present? + def render_object_fields(fields, owner:, level_bump: 0) + return if fields.blank? - objects.sort_by { |o| o[:name] } - end + (with_args, no_args) = fields.partition { |f| args?(f) } + type_name = owner[:name] if owner + header_prefix = '#' * level_bump + sections = [ + render_simple_fields(no_args, type_name, header_prefix), + render_fields_with_arguments(with_args, type_name, header_prefix) + ] - def render_field(field, owner) - render_row( - render_name(field, owner), - render_field_type(field[:type]), - render_description(field, owner, :inline) - ) + join(:block, sections) end def render_enum_value(enum, value) @@ -82,104 +131,302 @@ module Gitlab # Methods that return parts of the schema, or related information: - # 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]["__"] - end + def connection_object_types + objects.select { |t| t[:is_edge] || t[:is_connection] } + end + + def object_types + objects.reject { |t| t[:is_edge] || t[:is_connection] || t[:is_payload] } + end + + def interfaces + graphql_interface_types.map { |t| t.merge(fields: t[:fields] + t[:connections]) } + end - object_types.each do |type| - type[:fields] += type[:connections] + def fields_of(type_name) + graphql_operation_types + .find { |type| type[:name] == type_name } + .values_at(:fields, :connections) + .flatten + .then { |fields| sorted_by_name(fields) } + end + + # Place the arguments of the input types on the mutation itself. + # see: `#input_types` - this method must not call `#input_types` to avoid mutual recursion + def mutations + @mutations ||= sorted_by_name(graphql_mutation_types).map do |t| + inputs = t[:input_fields] + input = inputs.first + name = t[:name] + + assert!(inputs.one?, "Expected exactly 1 input field named #{name}. Found #{inputs.count} instead.") + assert!(input[:name] == 'input', "Expected the input of #{name} to be named 'input'") + + input_type_name = input[:type][:name] + input_type = graphql_input_object_types.find { |t| t[:name] == input_type_name } + assert!(input_type.present?, "Cannot find #{input_type_name} for #{name}.input") + + arguments = input_type[:input_fields] + seen_type!(input_type_name) + t.merge(arguments: arguments) end end - def queries - graphql_operation_types.find { |type| type[:name] == 'Query' }.to_h.values_at(:fields, :connections).flatten + # We assume that the mutations have been processed first, marking their + # inputs as `seen_type?` + def input_types + mutations # ensure that mutations have seen their inputs first + graphql_input_object_types.reject { |t| seen_type?(t[:name]) } end - # We ignore the built-in enum types. + # We ignore the built-in enum types, and sort values by name def enums - graphql_enum_types.select do |enum_type| - !enum_type[:name].in?(%w[__DirectiveLocation __TypeKind]) - end + graphql_enum_types + .reject { |type| type[:values].empty? } + .reject { |enum_type| enum_type[:name].start_with?('__') } + .map { |type| type.merge(values: sorted_by_name(type[:values])) } end private # DO NOT CALL THESE METHODS IN TEMPLATES # Template methods + def render_return_type(query) + return unless query[:type] # for example, mutations + + "Returns #{render_field_type(query[:type])}." + end + + def render_simple_fields(fields, type_name, header_prefix) + render_field_table(header_prefix + FIELD_HEADER, fields, type_name) + end + + def render_fields_with_arguments(fields, type_name, header_prefix) + return if fields.empty? + + level = 5 + header_prefix.length + sections = sorted_by_name(fields).map do |f| + render_full_field(f, heading_level: level, owner: type_name) + end + + <<~MD.chomp + #{header_prefix}#### Fields with arguments + + #{join(:block, sections)} + MD + end + + def render_field_table(header, fields, owner) + return if fields.empty? + + fields = sorted_by_name(fields) + header + join(:table, fields.map { |f| render_field(f, owner) }) + end + + def render_field(field, owner) + render_row( + render_name(field, owner), + render_field_type(field[:type]), + render_description(field, owner, :inline) + ) + end + + def render_return_fields(mutation, owner:) + fields = mutation[:return_fields] + return if fields.blank? + + name = owner.to_s + mutation[:name] + render_object_fields(fields, owner: { name: name }) + end + + def render_connection_note(field) + return unless connection?(field) + + CONNECTION_NOTE.chomp + end + def render_row(*values) "| #{values.map { |val| val.to_s.squish }.join(' | ')} |" end def render_name(object, owner = nil) rendered_name = "`#{object[:name]}`" - rendered_name += ' **{warning-solid}**' if object[:is_deprecated] - rendered_name + rendered_name += ' **{warning-solid}**' if deprecated?(object, owner) + + return rendered_name unless owner + + owner = Array.wrap(owner).join('') + id = (owner + object[:name]).downcase + + %(<a id="#{id}"></a>) + rendered_name end # Returns the object description. If the object has been deprecated, # the deprecation reason will be returned in place of the description. def render_description(object, owner = nil, context = :block) - owner = Array.wrap(owner) - return render_deprecation(object, owner, context) if object[:is_deprecated] - return if object[:description].blank? + if deprecated?(object, owner) + render_deprecation(object, owner, context) + else + render_description_of(object, owner, context) + end + end + + def deprecated?(object, owner) + return true if object[:is_deprecated] # only populated for fields, not arguments! + + key = [*Array.wrap(owner), object[:name]].join('.') + deprecations.key?(key) + end + + def render_description_of(object, owner, context = nil) + desc = if object[:is_edge] + base = object[:name].chomp('Edge') + "The edge type for [`#{base}`](##{base.downcase})." + elsif object[:is_connection] + base = object[:name].chomp('Connection') + "The connection type for [`#{base}`](##{base.downcase})." + else + object[:description]&.strip + end + + return if desc.blank? - desc = object[:description].strip desc += '.' unless desc.ends_with?('.') + see = doc_reference(object, owner) + desc += " #{see}" if see + desc += " (see [Connections](#connections))" if connection?(object) && context != :block desc end + def doc_reference(object, owner) + field = schema_field(owner, object[:name]) if owner + return unless field + + ref = field.try(:doc_reference) + return if ref.blank? + + parts = ref.to_a.map do |(title, url)| + "[#{title.strip}](#{url.strip})" + end + + "See #{parts.join(', ')}." + end + def render_deprecation(object, owner, context) + buff = [] deprecation = schema_deprecation(owner, object[:name]) - return deprecation.markdown(context: context) if deprecation - reason = object[:deprecation_reason] || 'Use of this is deprecated.' - "**Deprecated:** #{reason}" + buff << (deprecation&.original_description || render_description_of(object, owner)) if context == :block + buff << if deprecation + deprecation.markdown(context: context) + else + "**Deprecated:** #{object[:deprecation_reason]}" + end + + join(context, buff) end def render_field_type(type) "[`#{type[:info]}`](##{type[:name].downcase})" end + def join(context, chunks) + chunks.compact! + return if chunks.blank? + + case context + when :block + chunks.join("\n\n") + when :inline + chunks.join(" ").squish.presence + when :table + chunks.join("\n") + end + end + # Queries + def sorted_by_name(objects) + return [] unless objects.present? + + objects.sort_by { |o| o[:name] } + end + + def connection?(field) + type_name = field.dig(:type, :name) + type_name.present? && type_name.ends_with?('Connection') + end + + # We are ignoring connections and built in types for now, + # they should be added when queries are generated. + def objects + strong_memoize(:objects) do + mutations = schema.mutation&.fields&.keys&.to_set || [] + + graphql_object_types + .reject { |object_type| object_type[:name]["__"] || object_type[:name] == 'Subscription' } # We ignore introspection and subscription types. + .map do |type| + name = type[:name] + type.merge( + is_edge: name.ends_with?('Edge'), + is_connection: name.ends_with?('Connection'), + is_payload: name.ends_with?('Payload') && mutations.include?(name.chomp('Payload').camelcase(:lower)), + fields: type[:fields] + type[:connections] + ) + end + end + end + + def args?(field) + args = field[:arguments] + return false if args.blank? + return true unless connection?(field) + + args.any? { |arg| CONNECTION_ARGS.exclude?(arg[:name]) } + end + # returns the deprecation information for a field or argument # See: Gitlab::Graphql::Deprecation def schema_deprecation(type_name, field_name) - schema_member(type_name, field_name)&.deprecation - end - - # Return a part of the schema. - # - # This queries the Schema by owner and name to find: - # - # - fields (e.g. `schema_member('Query', 'currentUser')`) - # - arguments (e.g. `schema_member(['Query', 'project], 'fullPath')`) - def schema_member(type_name, field_name) - type_name = Array.wrap(type_name) - if type_name.size == 2 - arg_name = field_name - type_name, field_name = type_name - else - type_name = type_name.first - arg_name = nil - end + key = [*Array.wrap(type_name), field_name].join('.') + deprecations[key] + end - return if type_name.nil? || field_name.nil? + def render_input_type(query) + input_field = query[:input_fields]&.first + return unless input_field + "Input type: `#{input_field[:type][:name]}`" + end + + def schema_field(type_name, field_name) type = schema.types[type_name] return unless type && type.kind.fields? - field = type.fields[field_name] - return field if arg_name.nil? + type.fields[field_name] + end + + def deprecations + strong_memoize(:deprecations) do + mapping = {} + + schema.types.each do |type_name, type| + next unless type.kind.fields? - args = field.arguments - is_mutation = field.mutation && field.mutation <= ::Mutations::BaseMutation - args = args['input'].type.unwrap.arguments if is_mutation + type.fields.each do |field_name, field| + mapping["#{type_name}.#{field_name}"] = field.try(:deprecation) + field.arguments.each do |arg_name, arg| + mapping["#{type_name}.#{field_name}.#{arg_name}"] = arg.try(:deprecation) + end + end + end + + mapping.compact + end + end - args[arg_name] + def assert!(claim, message) + raise ViolatedAssumption, "#{message}\n#{SUGGESTED_ACTION}" unless claim end end end diff --git a/lib/gitlab/graphql/docs/renderer.rb b/lib/gitlab/graphql/docs/renderer.rb index 497567f9389..ae0898e6198 100644 --- a/lib/gitlab/graphql/docs/renderer.rb +++ b/lib/gitlab/graphql/docs/renderer.rb @@ -24,6 +24,7 @@ module Gitlab @layout = Haml::Engine.new(File.read(template)) @parsed_schema = GraphQLDocs::Parser.new(schema.graphql_definition, {}).parse @schema = schema + @seen = Set.new end def contents @@ -37,6 +38,16 @@ module Gitlab FileUtils.mkdir_p(@output_dir) File.write(filename, contents) end + + private + + def seen_type?(name) + @seen.include?(name) + end + + def seen_type!(name) + @seen << name + end end end end diff --git a/lib/gitlab/graphql/docs/templates/default.md.haml b/lib/gitlab/graphql/docs/templates/default.md.haml index fe73297d0d9..7d42fb3a9f8 100644 --- a/lib/gitlab/graphql/docs/templates/default.md.haml +++ b/lib/gitlab/graphql/docs/templates/default.md.haml @@ -17,7 +17,9 @@ 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 --> + <!-- vale off --> + <!-- Docs linting disabled after this line. --> + <!-- See https://docs.gitlab.com/ee/development/documentation/testing.html#disable-vale-tests --> \ :plain @@ -26,17 +28,81 @@ 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, owner: 'Query') +- fields_of('Query').each do |field| + = render_full_field(field, heading_level: 3, owner: 'Query') + \ + +:plain + ## `Mutation` type + + The `Mutation` type contains all the mutations you can execute. + + All mutations receive their arguments in a single input object named `input`, and all mutations + support at least a return field `errors` containing a list of error messages. + + All input objects may have a `clientMutationId: String` field, identifying the mutation. + + For example: + + ```graphql + mutation($id: NoteableID!, $body: String!) { + createNote(input: { noteableId: $id, body: $body }) { + errors + } + } + ``` +\ + +- mutations.each do |field| + = render_full_field(field, heading_level: 3, owner: 'Mutation') + \ + +:plain + ## Connections + + Some types in our schema are `Connection` types - they represent a paginated + collection of edges between two nodes in the graph. These follow the + [Relay cursor connections specification](https://relay.dev/graphql/connections.htm). + + ### Pagination arguments {#connection-pagination-arguments} + + All connection fields support the following pagination arguments: + + | Name | Type | Description | + |------|------|-------------| + | `after` | [`String`](#string) | Returns the elements in the list that come after the specified cursor. | + | `before` | [`String`](#string) | Returns the elements in the list that come before the specified cursor. | + | `first` | [`Int`](#int) | Returns the first _n_ elements from the list. | + | `last` | [`Int`](#int) | Returns the last _n_ elements from the list. | + + Since these arguments are common to all connection fields, they are not repeated for each connection. + + ### Connection fields + + All connections have at least the following fields: + + | Name | Type | Description | + |------|------|-------------| + | `pageInfo` | [`PageInfo!`](#pageinfo) | Pagination information. | + | `edges` | `[edge!]` | The edges. | + | `nodes` | `[item!]` | The items in the current page. | + + The precise type of `Edge` and `Item` depends on the kind of connection. A + [`ProjectConnection`](#projectconnection) will have nodes that have the type + [`[Project!]`](#project), and edges that have the type [`ProjectEdge`](#projectedge). + + ### Connection types + + Some of the types in the schema exist solely to model connections. Each connection + has a distinct, named type, with a distinct named edge type. These are listed separately + below. +\ + +- connection_object_types.each do |type| + = render_name_and_description(type, level: 4) + \ + = render_object_fields(type[:fields], owner: type, level_bump: 1) \ - = render_return_type(query) - - unless query[:arguments].empty? - ~ "#### Arguments\n" - ~ "| Name | Type | Description |" - ~ "| ---- | ---- | ----------- |" - - sorted_by_name(query[:arguments]).each do |argument| - = render_field(argument, query[:type][:name]) - \ :plain ## Object types @@ -44,22 +110,20 @@ Object types represent the resources that the GitLab GraphQL API can return. They contain _fields_. Each field has its own type, which will either be one of the basic GraphQL [scalar types](https://graphql.org/learn/schema/#scalar-types) - (e.g.: `String` or `Boolean`) or other object types. + (e.g.: `String` or `Boolean`) or other object types. Fields may have arguments. + Fields with arguments are exactly like top-level queries, and are listed beneath + the table of fields for each object type. For more information, see [Object Types and Fields](https://graphql.org/learn/schema/#object-types-and-fields) on `graphql.org`. \ -- objects.each do |type| - - unless type[:fields].empty? - = render_name_and_description(type) - \ - ~ "| Field | Type | Description |" - ~ "| ----- | ---- | ----------- |" - - sorted_by_name(type[:fields]).each do |field| - = render_field(field, type[:name]) - \ +- object_types.each do |type| + = render_name_and_description(type) + \ + = render_object_fields(type[:fields], owner: type) + \ :plain ## Enumeration types @@ -73,14 +137,13 @@ \ - 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(enum, value) - \ + = render_name_and_description(enum) + \ + ~ "| Value | Description |" + ~ "| ----- | ----------- |" + - enum[:values].each do |value| + = render_enum_value(enum, value) + \ :plain ## Scalar types @@ -133,7 +196,7 @@ ### Interfaces \ -- graphql_interface_types.each do |type| +- interfaces.each do |type| = render_name_and_description(type, level: 4) \ Implementations: @@ -141,8 +204,21 @@ - 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, type[:name]) + = render_object_fields(type[:fields], owner: type, level_bump: 1) + \ + +:plain + ## Input types + + Types that may be used as arguments (all scalar types may also + be used as arguments). + + Only general use input types are listed here. For mutation input types, + see the associated mutation type above. +\ + +- input_types.each do |type| + = render_name_and_description(type) + \ + = render_argument_table(3, type[:input_fields], type[:name]) \ diff --git a/lib/gitlab/graphql/pagination/keyset/connection.rb b/lib/gitlab/graphql/pagination/keyset/connection.rb index e525996ec10..61903c566f0 100644 --- a/lib/gitlab/graphql/pagination/keyset/connection.rb +++ b/lib/gitlab/graphql/pagination/keyset/connection.rb @@ -114,7 +114,7 @@ module Gitlab def limited_nodes strong_memoize(:limited_nodes) do if first && last - raise Gitlab::Graphql::Errors::ArgumentError.new("Can only provide either `first` or `last`, not both") + raise Gitlab::Graphql::Errors::ArgumentError, "Can only provide either `first` or `last`, not both" end if last @@ -158,7 +158,7 @@ module Gitlab def ordered_items strong_memoize(:ordered_items) do unless items.primary_key.present? - raise ArgumentError.new('Relation must have a primary key') + raise ArgumentError, 'Relation must have a primary key' end list = OrderInfo.build_order_list(items) diff --git a/lib/gitlab/graphql/pagination/keyset/generic_keyset_pagination.rb b/lib/gitlab/graphql/pagination/keyset/generic_keyset_pagination.rb index 318c6e1734f..f1b74999897 100644 --- a/lib/gitlab/graphql/pagination/keyset/generic_keyset_pagination.rb +++ b/lib/gitlab/graphql/pagination/keyset/generic_keyset_pagination.rb @@ -10,6 +10,8 @@ module Gitlab extend ActiveSupport::Concern def ordered_items + raise ArgumentError, 'Relation must have a primary key' unless items.primary_key.present? + return super unless Gitlab::Pagination::Keyset::Order.keyset_aware?(items) items @@ -40,6 +42,17 @@ module Gitlab sliced = slice_nodes(sliced, after, :after) if after.present? sliced end + + def items + original_items = super + return original_items if Gitlab::Pagination::Keyset::Order.keyset_aware?(original_items) || Feature.disabled?(:new_graphql_keyset_pagination) + + strong_memoize(:generic_keyset_pagination_items) do + rebuilt_items_with_keyset_order, success = Gitlab::Pagination::Keyset::SimpleOrderBuilder.build(original_items) + + success ? rebuilt_items_with_keyset_order : original_items + end + end end end end diff --git a/lib/gitlab/graphql/pagination/keyset/order_info.rb b/lib/gitlab/graphql/pagination/keyset/order_info.rb index 0494329bfd9..57e85ebe7f6 100644 --- a/lib/gitlab/graphql/pagination/keyset/order_info.rb +++ b/lib/gitlab/graphql/pagination/keyset/order_info.rb @@ -36,24 +36,24 @@ module Gitlab def self.validate_ordering(relation, order_list) if order_list.empty? - raise ArgumentError.new('A minimum of 1 ordering field is required') + raise ArgumentError, 'A minimum of 1 ordering field is required' end if order_list.count > 2 # Keep in mind an order clause for primary key is added if one is not present # lib/gitlab/graphql/pagination/keyset/connection.rb:97 - raise ArgumentError.new('A maximum of 2 ordering fields are allowed') + raise ArgumentError, 'A maximum of 2 ordering fields are allowed' end # make sure the last ordering field is non-nullable attribute_name = order_list.last&.attribute_name if relation.columns_hash[attribute_name].null - raise ArgumentError.new("Column `#{attribute_name}` must not allow NULL") + raise ArgumentError, "Column `#{attribute_name}` must not allow NULL" end if order_list.last.attribute_name != relation.primary_key - raise ArgumentError.new("Last ordering field must be the primary key, `#{relation.primary_key}`") + raise ArgumentError, "Last ordering field must be the primary key, `#{relation.primary_key}`" end end @@ -121,4 +121,4 @@ module Gitlab end end -Gitlab::Graphql::Pagination::Keyset::OrderInfo.prepend_if_ee('EE::Gitlab::Graphql::Pagination::Keyset::OrderInfo') +Gitlab::Graphql::Pagination::Keyset::OrderInfo.prepend_mod_with('Gitlab::Graphql::Pagination::Keyset::OrderInfo') diff --git a/lib/gitlab/graphql/pagination/keyset/query_builder.rb b/lib/gitlab/graphql/pagination/keyset/query_builder.rb index ee9c902c735..a2f53ae83dd 100644 --- a/lib/gitlab/graphql/pagination/keyset/query_builder.rb +++ b/lib/gitlab/graphql/pagination/keyset/query_builder.rb @@ -12,7 +12,7 @@ module Gitlab @before_or_after = before_or_after if order_list.empty? - raise ArgumentError.new('No ordering scopes have been supplied') + raise ArgumentError, 'No ordering scopes have been supplied' end end @@ -49,7 +49,7 @@ module Gitlab end if order_list.count == 1 && attr_values.first.nil? - raise Gitlab::Graphql::Errors::ArgumentError.new('Before/after cursor invalid: `nil` was provided as only sortable value') + raise Gitlab::Graphql::Errors::ArgumentError, 'Before/after cursor invalid: `nil` was provided as only sortable value' end if order_list.count == 1 || attr_values.first.present? diff --git a/lib/gitlab/graphql/present.rb b/lib/gitlab/graphql/present.rb index fdaf075eb25..3608cb4c0e8 100644 --- a/lib/gitlab/graphql/present.rb +++ b/lib/gitlab/graphql/present.rb @@ -10,14 +10,14 @@ module Gitlab end def self.presenter_class - @presenter_class + @presenter_class || superclass.try(:presenter_class) end def self.present(object, attrs) - klass = @presenter_class + klass = presenter_class return object if !klass || object.is_a?(klass) - @presenter_class.new(object, **attrs) + klass.new(object, **attrs) end end diff --git a/lib/gitlab/graphql/present/field_extension.rb b/lib/gitlab/graphql/present/field_extension.rb index 2e211b70d35..050a3a276ea 100644 --- a/lib/gitlab/graphql/present/field_extension.rb +++ b/lib/gitlab/graphql/present/field_extension.rb @@ -13,7 +13,8 @@ module Gitlab # 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) + type = field.owner.kind.abstract? ? object.class : field.owner + object.present(type, attrs) yield(object, arguments) else # This is the legacy code-path, hit if the field has a @resolver_proc diff --git a/lib/gitlab/graphql/queries.rb b/lib/gitlab/graphql/queries.rb index 74f55abccbc..5d3a9245427 100644 --- a/lib/gitlab/graphql/queries.rb +++ b/lib/gitlab/graphql/queries.rb @@ -264,7 +264,7 @@ module Gitlab definitions = [] ::Find.find(root.to_s) do |path| - definitions << Definition.new(path, fragments) if query?(path) + definitions << Definition.new(path, fragments) if query_for_gitlab_schema?(path) end definitions @@ -288,10 +288,11 @@ module Gitlab @known_failures.fetch('filenames', []).any? { |known_failure| path.to_s.ends_with?(known_failure) } end - def self.query?(path) + def self.query_for_gitlab_schema?(path) path.ends_with?('.graphql') && !path.ends_with?('.fragment.graphql') && - !path.ends_with?('typedefs.graphql') + !path.ends_with?('typedefs.graphql') && + !/.*\.customer\.(query|mutation)\.graphql$/.match?(path) end end end diff --git a/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb b/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb index c6f22e0bd4f..b8d2f5b0f29 100644 --- a/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb +++ b/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb @@ -16,7 +16,7 @@ module Gitlab query_string: query.query_string, variables: variables }) - rescue => e + rescue StandardError => e Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e) default_initial_values(query) end @@ -41,7 +41,7 @@ module Gitlab RequestStore.store[:graphql_logs] ||= [] RequestStore.store[:graphql_logs] << memo GraphqlLogger.info(memo.except!(:time_started, :query)) - rescue => e + rescue StandardError => e Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e) end diff --git a/lib/gitlab/graphql/variables.rb b/lib/gitlab/graphql/variables.rb index 1c6fb011012..e17ca56d022 100644 --- a/lib/gitlab/graphql/variables.rb +++ b/lib/gitlab/graphql/variables.rb @@ -32,7 +32,7 @@ module Gitlab raise Invalid, "Unexpected parameter: #{ambiguous_param}" end rescue JSON::ParserError => e - raise Invalid.new(e) + raise Invalid, e end end end |