Commit 44182607 authored by Alex Kalderimis's avatar Alex Kalderimis

Add missing parts to the GraphQL schema

This adds:

- mutations
- arguments
- input types

And ensures we can link to all fields, whether they are in a
field table or listed as a full field with arguments. Deprecations
are changed to link to the replacements.
parent b24494e7
---
title: Add missing parts of GraphQL schema to GraphQL documentation
merge_request: 55944
author:
type: changed
......@@ -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
......
......@@ -5,6 +5,24 @@ return if Rails.env.production?
module Gitlab
module Graphql
module Docs
ViolatedAssumption = Class.new(StandardError)
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
# 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
......@@ -30,30 +48,83 @@ module Gitlab
# Template methods:
# Methods that return chunks of Markdown for insertion into the document
def render_full_field(field, heading_level: 3, owner: nil)
conn = connection?(field)
args = field[:arguments].reject { |f| conn && CONNECTION_ARGS.include?(f[:name]) }
arg_owner = [owner, field[:name]]
[
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)
].compact.join("\n\n")
end
def render_argument_table(level, args, owner)
arg_header = ('#' * level) + ARG_HEADER
render_field_table(arg_header, args, owner)
end
def render_field_table(header, fields, owner)
return if fields.empty?
fields = sorted_by_name(fields)
header + fields.map { |f| render_field(f, owner) }.join("\n")
end
def render_name_and_description(object, owner: nil, level: 3)
content = []
content << "#{'#' * level} `#{object[:name]}`"
heading = '#' * level
name = [owner, object[:name]].compact.join('.')
if object[:description].present?
desc = object[:description].strip
desc += '.' unless desc.ends_with?('.')
end
content << "#{heading} `#{name}`"
content << render_description(object, owner, :block)
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
content.compact.join("\n\n")
end
def render_object_fields(fields, owner:, level_bump: 0)
return if fields.empty?
(with_args, no_args) = fields.partition { |f| args?(f) }
type_name = owner[:name] if owner
header_prefix = '#' * level_bump
[
simple_fields(no_args, type_name, header_prefix),
fields_with_arguments(with_args, type_name, header_prefix)
].compact.join("\n\n")
end
def connection?(field)
type_name = field.dig(:type, :name)
type_name.present? && type_name.ends_with?('Connection')
end
def simple_fields(fields, type_name, header_prefix)
render_field_table(header_prefix + FIELD_HEADER, fields, type_name)
end
def 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
content.compact.join("\n\n")
<<~MD.chomp
#{header_prefix}#### fields with arguments
#{sections.join("\n\n")}
MD
end
def render_return_type(query)
"Returns #{render_field_type(query[:type])}.\n"
"Returns #{render_field_type(query[:type])}."
end
def sorted_by_name(objects)
......@@ -82,33 +153,91 @@ 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[:edge] || t[:connection] }
end
object_types.each do |type|
type[:fields] += type[:connections]
end
def object_types
objects.reject { |t| t[:edge] || t[:connection] }
end
def interfaces
graphql_interface_types.map { |t| t.merge(fields: t[:fields] + t[:connections]) }
end
# 'queries' are the fields of the Query type
def queries
graphql_operation_types.find { |type| type[:name] == 'Query' }.to_h.values_at(:fields, :connections).flatten
graphql_operation_types
.find { |type| type[:name] == 'Query' }
.values_at(:fields, :connections)
.flatten
.then { |fields| sorted_by_name(fields) }
end
# We ignore the built-in enum types.
def enums
graphql_enum_types.select do |enum_type|
!enum_type[:name].in?(%w[__DirectiveLocation __TypeKind])
# 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|
fields = t[:input_fields]
field = fields.first
raise ViolatedAssumption, "Expected one input field to #{t[:name]}" if fields.size != 1
raise ViolatedAssumption, "Expected the input of #{t[:name]} to be named 'input'" if field[:name] != 'input'
input_type_name = field[:type][:name]
input_type = graphql_input_object_types.find { |t| t[:name] == input_type_name }
raise ViolatedAssumption, "Cannot find #{input_type_name}" unless input_type
arguments = input_type[:input_fields]
seen_type(input_type_name)
t.merge(arguments: arguments, fields: t[:return_fields])
end
end
# We assume that the mutations have been processed first, marking their
# inputs as `seen?`
def input_types
mutations # ensure that mutations have seen their inputs first
graphql_input_object_types.reject { |t| seen?(t[:name]) }
end
# We ignore the built-in enum types, and sort values by name
def enums
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
# We are ignoring connections and built in types for now,
# they should be added when queries are generated.
def objects
@objects ||= graphql_object_types
.reject { |object_type| object_type[:name]["__"] } # We ignore introspection types.
.map do |type|
type.merge(
edge: type[:name].ends_with?('Edge'),
connection: type[:name].ends_with?('Connection'),
fields: type[:fields] + type[:connections]
)
end
end
private # DO NOT CALL THESE METHODS IN TEMPLATES
# Template methods
def render_connection_note(field)
return unless connection?(field)
<<~MD.chomp
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
end
def render_row(*values)
"| #{values.map { |val| val.to_s.squish }.join(' | ')} |"
end
......@@ -116,17 +245,50 @@ module Gitlab
def render_name(object, owner = nil)
rendered_name = "`#{object[:name]}`"
rendered_name += ' **{warning-solid}**' if object[:is_deprecated]
rendered_name
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?
content = []
if object[:is_deprecated] && context == :block
owner = Array.wrap(owner)
deprecation = schema_deprecation(owner, object[:name])
content << (deprecation&.original_description || render_description_of(object))
content << render_deprecation(object, owner, :block)
elsif object[:is_deprecated]
content << render_deprecation(object, owner, context)
else
content << render_description_of(object)
end
sep = context == :block ? "\n\n" : ' '
content.compact.join(sep).presence
end
def render_description_of(object)
desc = if object[:edge]
base = object[:name].chomp('Edge')
"The edge type for [`#{base}`](##{base.downcase})."
elsif object[: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?('.')
desc
end
......@@ -145,6 +307,14 @@ module Gitlab
# Queries
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)
......@@ -181,6 +351,13 @@ module Gitlab
args[arg_name]
end
def render_input_type(query)
input_field = query[:input_fields]&.first
return unless input_field
"Input type: `#{input_field[:type][:name]}`."
end
end
end
end
......
......@@ -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,14 @@ module Gitlab
FileUtils.mkdir_p(@output_dir)
File.write(filename, contents)
end
def seen?(name)
@seen.include?(name)
end
def seen_type(name)
@seen << name
end
end
end
end
......
......@@ -26,17 +26,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')
\
= 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])
\
- queries.each do |query|
= render_full_field(query, heading_level: 3, owner: 'Query')
\
:plain
## `Mutation` type
The `Mutation` type contains the API's top-level entry points for all executable mutations.
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
[`UserConnection`](#userconnection) will have nodes that have the type
[`[User!]`](#user), and edges that have the type [`UserEdge`](#useredge).
### 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)
\
:plain
## Object types
......@@ -44,22 +108,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 +135,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 +194,7 @@
### Interfaces
\
- graphql_interface_types.each do |type|
- interfaces.each do |type|
= render_name_and_description(type, level: 4)
\
Implementations:
......@@ -141,8 +202,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])
\
......@@ -164,7 +164,7 @@ RSpec.describe ::Gitlab::Graphql::Deprecation do
context 'when the context is :inline' do
it 'renders on one line' do
expectation = '**Deprecated** in 10.10. This was renamed. Use: `X.y`.'
expectation = '**Deprecated** in 10.10. This was renamed. Use: [`X.y`](#xy).'
expect(deprecation.markdown).to eq(expectation)
expect(deprecation.markdown(context: :inline)).to eq(expectation)
......@@ -177,7 +177,7 @@ RSpec.describe ::Gitlab::Graphql::Deprecation do
WARNING:
**Deprecated** in 10.10.
This was renamed.
Use: `X.y`.
Use: [`X.y`](#xy).
MD
expect(deprecation.markdown(context: :block)).to eq(expectation)
......
......@@ -4,7 +4,16 @@ require 'fast_spec_helper'
RSpec.describe Gitlab::Graphql::Docs::Renderer do
describe '#contents' do
shared_examples 'renders correctly as GraphQL documentation' do
it 'contains the expected section' do
# duplicative - but much better error messages!
section.lines.each { |line| expect(contents).to include(line) }
expect(contents).to include(section)
end
end
let(:template) { Rails.root.join('lib/gitlab/graphql/docs/templates/default.md.haml') }
let(:field_description) { 'List of objects.' }
let(:query_type) do
Class.new(Types::BaseObject) { graphql_name 'Query' }.tap do |t|
......@@ -23,8 +32,6 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
end
end
let(:field_description) { 'List of objects.' }
subject(:contents) do
mock_schema.query(query_type)
......@@ -41,12 +48,15 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
it 'contains the expected sections' do
expect(contents.lines.map(&:chomp)).to include(
'## `Query` type',
'## `Mutation` type',
'## Connections',
'## Object types',
'## Enumeration types',
'## Scalar types',
'## Abstract types',
'### Unions',
'### Interfaces'
'### Interfaces',
'### Input types'
)
end
end
......@@ -66,9 +76,11 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
expectation = <<~DOC
### `ArrayTest`
| Field | Type | Description |
| ----- | ---- | ----------- |
| `foo` | [`#{type_name}`](##{inner_type}) | A description. |
#### fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="arraytestfoo"></a>`foo` | [`#{type_name}`](##{inner_type}) | A description. |
DOC
is_expected.to include(expectation)
......@@ -77,17 +89,17 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
describe 'a top level query field' do
let(:expectation) do
<<~DOC
### `foo`
### `Query.foo`
List of objects.
Returns [`ArrayTest`](#arraytest).
#### Arguments
#### arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| `id` | [`ID`](#id) | ID of the object. |
| <a id="queryfooid"></a>`id` | [`ID`](#id) | ID of the object. |
DOC
end
......@@ -119,10 +131,12 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
expectation = <<~DOC
### `OrderingTest`
| Field | Type | Description |
| ----- | ---- | ----------- |
| `bar` | [`String!`](#string) | A description of bar field. |
| `foo` | [`String!`](#string) | A description of foo field. |
#### fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="orderingtestbar"></a>`bar` | [`String!`](#string) | A description of bar field. |
| <a id="orderingtestfoo"></a>`foo` | [`String!`](#string) | A description of foo field. |
DOC
is_expected.to include(expectation)
......@@ -133,6 +147,7 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
let(:type) do
Class.new(Types::BaseObject) do
graphql_name 'DeprecatedTest'
description 'A thing we used to use, but no longer support'
field :foo,
type: GraphQL::STRING_TYPE,
......@@ -142,9 +157,9 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
field :foo_with_args,
type: GraphQL::STRING_TYPE,
null: false,
deprecated: { reason: 'Do not use', milestone: '1.10' },
deprecated: { reason: 'Do not use', milestone: '1.10', replacement: 'X.y' },
description: 'A description.' do
argument :fooity, ::GraphQL::INT_TYPE, required: false, description: 'X'
argument :arg, GraphQL::INT_TYPE, required: false, description: 'Argity'
end
field :bar,
type: GraphQL::STRING_TYPE,
......@@ -158,24 +173,44 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
end
end
it 'includes the deprecation' do
expectation = <<~DOC
let(:section) do
<<~DOC
### `DeprecatedTest`
| Field | Type | Description |
| ----- | ---- | ----------- |
| `bar` **{warning-solid}** | [`String!`](#string) | **Deprecated** in 1.10. This was renamed. Use: `Query.boom`. |
| `foo` **{warning-solid}** | [`String!`](#string) | **Deprecated** in 1.10. This is deprecated. |
| `fooWithArgs` **{warning-solid}** | [`String!`](#string) | **Deprecated** in 1.10. Do not use. |
DOC
A thing we used to use, but no longer support.
is_expected.to include(expectation)
#### fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="deprecatedtestbar"></a>`bar` **{warning-solid}** | [`String!`](#string) | **Deprecated** in 1.10. This was renamed. Use: [`Query.boom`](#queryboom). |
| <a id="deprecatedtestfoo"></a>`foo` **{warning-solid}** | [`String!`](#string) | **Deprecated** in 1.10. This is deprecated. |
#### fields with arguments
##### `DeprecatedTest.fooWithArgs`
A description.
WARNING:
**Deprecated** in 1.10.
Do not use.
Use: [`X.y`](#xy).
Returns [`String!`](#string).
###### arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="deprecatedtestfoowithargsarg"></a>`arg` | [`Int`](#int) | Argity. |
DOC
end
it_behaves_like 'renders correctly as GraphQL documentation'
end
context 'when a Query.field is deprecated' do
let(:type) { ::GraphQL::INT_TYPE }
before do
query_type.field(
name: :bar,
......@@ -186,28 +221,30 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
)
end
it 'includes the deprecation' do
expectation = <<~DOC
### `bar`
let(:type) { ::GraphQL::INT_TYPE }
let(:section) do
<<~DOC
### `Query.bar`
A bar.
WARNING:
**Deprecated** in 10.11.
This was renamed.
Use: `Query.foo`.
Use: [`Query.foo`](#queryfoo).
Returns [`Int`](#int).
DOC
is_expected.to include(expectation)
end
it_behaves_like 'renders correctly as GraphQL documentation'
end
context 'when a field has an Enumeration type' do
let(:type) do
enum_type = Class.new(Types::BaseEnum) do
graphql_name 'MyEnum'
description 'A test of an enum.'
value 'BAZ',
description: 'A description of BAZ.'
......@@ -223,18 +260,20 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
end
end
it 'includes the description of the Enumeration' do
expectation = <<~DOC
let(:section) do
<<~DOC
### `MyEnum`
A test of an enum.
| Value | Description |
| ----- | ----------- |
| `BAR` **{warning-solid}** | **Deprecated:** This is deprecated. Deprecated in 1.10. |
| `BAZ` | A description of BAZ. |
| <a id="myenumbar"></a>`BAR` **{warning-solid}** | **Deprecated:** This is deprecated. Deprecated in 1.10. |
| <a id="myenumbaz"></a>`BAZ` | A description of BAZ. |
DOC
is_expected.to include(expectation)
end
it_behaves_like 'renders correctly as GraphQL documentation'
end
context 'when a field has a global ID type' do
......@@ -247,26 +286,36 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
end
end
it 'includes the field and the description of the ID, so we can link to it' do
type_section = <<~DOC
### `IDTest`
describe 'section for IDTest' do
let(:section) do
<<~DOC
### `IDTest`
A test for rendering IDs.
A test for rendering IDs.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `foo` | [`UserID`](#userid) | A user foo. |
DOC
#### fields
id_section = <<~DOC
### `UserID`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="idtestfoo"></a>`foo` | [`UserID`](#userid) | A user foo. |
DOC
end
it_behaves_like 'renders correctly as GraphQL documentation'
end
A `UserID` is a global ID. It is encoded as a string.
describe 'section for UserID' do
let(:section) do
<<~DOC
### `UserID`
An example `UserID` is: `"gid://gitlab/User/1"`.
DOC
A `UserID` is a global ID. It is encoded as a string.
is_expected.to include(type_section, id_section)
An example `UserID` is: `"gid://gitlab/User/1"`.
DOC
end
it_behaves_like 'renders correctly as GraphQL documentation'
end
end
......@@ -297,7 +346,7 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
interface.orphan_types african_swallow
Class.new(::Types::BaseObject) do
graphql_name 'AbstactTypeTest'
graphql_name 'AbstractTypeTest'
description 'A test for abstract types.'
field :foo, union, null: true, description: 'The foo.'
......@@ -307,14 +356,16 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
it 'lists the fields correctly, and includes descriptions of all the types' do
type_section = <<~DOC
### `AbstactTypeTest`
### `AbstractTypeTest`
A test for abstract types.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `flying` | [`Flying`](#flying) | A flying thing. |
| `foo` | [`UserOrGroup`](#userorgroup) | The foo. |
#### fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="abstracttypetestflying"></a>`flying` | [`Flying`](#flying) | A flying thing. |
| <a id="abstracttypetestfoo"></a>`foo` | [`UserOrGroup`](#userorgroup) | The foo. |
DOC
union_section = <<~DOC
......@@ -337,9 +388,11 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
- [`AfricanSwallow`](#africanswallow)
| Field | Type | Description |
| ----- | ---- | ----------- |
| `flightSpeed` | [`Int`](#int) | Speed in mph. |
##### fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="flyingflightspeed"></a>`flightSpeed` | [`Int`](#int) | Speed in mph. |
DOC
implementation_section = <<~DOC
......@@ -347,9 +400,11 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
A swallow from Africa.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `flightSpeed` | [`Int`](#int) | Speed in mph. |
#### fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="africanswallowflightspeed"></a>`flightSpeed` | [`Int`](#int) | Speed in mph. |
DOC
is_expected.to include(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment