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 ...@@ -41,7 +41,7 @@ module Gitlab
parts = [ parts = [
"#{deprecated_in(format: :markdown)}.", "#{deprecated_in(format: :markdown)}.",
reason_text, reason_text,
replacement.then { |r| "Use: `#{r}`." if r } replacement.then { |r| "Use: [`#{r}`](##{r.downcase.tr('.', '')})." if r }
].compact ].compact
case context case context
......
...@@ -5,6 +5,24 @@ return if Rails.env.production? ...@@ -5,6 +5,24 @@ return if Rails.env.production?
module Gitlab module Gitlab
module Graphql module Graphql
module Docs 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 # Helper with functions to be used by HAML templates
# This includes graphql-docs gem helpers class. # 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 # 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 ...@@ -30,30 +48,83 @@ module Gitlab
# Template methods: # Template methods:
# Methods that return chunks of Markdown for insertion into the document # 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) def render_name_and_description(object, owner: nil, level: 3)
content = [] content = []
content << "#{'#' * level} `#{object[:name]}`" heading = '#' * level
name = [owner, object[:name]].compact.join('.')
if object[:description].present? content << "#{heading} `#{name}`"
desc = object[:description].strip content << render_description(object, owner, :block)
desc += '.' unless desc.ends_with?('.')
end
if object[:is_deprecated] content.compact.join("\n\n")
owner = Array.wrap(owner) end
deprecation = schema_deprecation(owner, object[:name])
content << (deprecation&.original_description || desc) def render_object_fields(fields, owner:, level_bump: 0)
content << render_deprecation(object, owner, :block) return if fields.empty?
else
content << desc (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 end
content.compact.join("\n\n") <<~MD.chomp
#{header_prefix}#### fields with arguments
#{sections.join("\n\n")}
MD
end end
def render_return_type(query) def render_return_type(query)
"Returns #{render_field_type(query[:type])}.\n" "Returns #{render_field_type(query[:type])}."
end end
def sorted_by_name(objects) def sorted_by_name(objects)
...@@ -82,33 +153,91 @@ module Gitlab ...@@ -82,33 +153,91 @@ module Gitlab
# Methods that return parts of the schema, or related information: # Methods that return parts of the schema, or related information:
# We are ignoring connections and built in types for now, def connection_object_types
# they should be added when queries are generated. objects.select { |t| t[:edge] || t[:connection] }
def objects end
object_types = graphql_object_types.select do |object_type|
!object_type[:name]["__"]
end
object_types.each do |type| def object_types
type[:fields] += type[:connections] objects.reject { |t| t[:edge] || t[:connection] }
end end
def interfaces
graphql_interface_types.map { |t| t.merge(fields: t[:fields] + t[:connections]) }
end end
# 'queries' are the fields of the Query type
def queries 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 end
# We ignore the built-in enum types. # Place the arguments of the input types on the mutation itself.
def enums # see: `#input_types` - this method must not call `#input_types` to
graphql_enum_types.select do |enum_type| # avoid mutual recursion
!enum_type[:name].in?(%w[__DirectiveLocation __TypeKind]) 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
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 private # DO NOT CALL THESE METHODS IN TEMPLATES
# Template methods # 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) def render_row(*values)
"| #{values.map { |val| val.to_s.squish }.join(' | ')} |" "| #{values.map { |val| val.to_s.squish }.join(' | ')} |"
end end
...@@ -116,17 +245,50 @@ module Gitlab ...@@ -116,17 +245,50 @@ module Gitlab
def render_name(object, owner = nil) def render_name(object, owner = nil)
rendered_name = "`#{object[:name]}`" rendered_name = "`#{object[:name]}`"
rendered_name += ' **{warning-solid}**' if object[:is_deprecated] 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 end
# Returns the object description. If the object has been deprecated, # Returns the object description. If the object has been deprecated,
# the deprecation reason will be returned in place of the description. # the deprecation reason will be returned in place of the description.
def render_description(object, owner = nil, context = :block) def render_description(object, owner = nil, context = :block)
owner = Array.wrap(owner) owner = Array.wrap(owner)
return render_deprecation(object, owner, context) if object[:is_deprecated] content = []
return if object[:description].blank?
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 += '.' unless desc.ends_with?('.')
desc desc
end end
...@@ -145,6 +307,14 @@ module Gitlab ...@@ -145,6 +307,14 @@ module Gitlab
# Queries # 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 # returns the deprecation information for a field or argument
# See: Gitlab::Graphql::Deprecation # See: Gitlab::Graphql::Deprecation
def schema_deprecation(type_name, field_name) def schema_deprecation(type_name, field_name)
...@@ -181,6 +351,13 @@ module Gitlab ...@@ -181,6 +351,13 @@ module Gitlab
args[arg_name] args[arg_name]
end 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 end
end end
......
...@@ -24,6 +24,7 @@ module Gitlab ...@@ -24,6 +24,7 @@ module Gitlab
@layout = Haml::Engine.new(File.read(template)) @layout = Haml::Engine.new(File.read(template))
@parsed_schema = GraphQLDocs::Parser.new(schema.graphql_definition, {}).parse @parsed_schema = GraphQLDocs::Parser.new(schema.graphql_definition, {}).parse
@schema = schema @schema = schema
@seen = Set.new
end end
def contents def contents
...@@ -37,6 +38,14 @@ module Gitlab ...@@ -37,6 +38,14 @@ module Gitlab
FileUtils.mkdir_p(@output_dir) FileUtils.mkdir_p(@output_dir)
File.write(filename, contents) File.write(filename, contents)
end end
def seen?(name)
@seen.include?(name)
end
def seen_type(name)
@seen << name
end
end end
end end
end end
......
...@@ -26,17 +26,81 @@ ...@@ -26,17 +26,81 @@
The `Query` type contains the API's top-level entry points for all executable queries. The `Query` type contains the API's top-level entry points for all executable queries.
\ \
- sorted_by_name(queries).each do |query| - queries.each do |query|
= render_name_and_description(query, owner: 'Query') = render_full_field(query, heading_level: 3, owner: 'Query')
\ \
= render_return_type(query)
- unless query[:arguments].empty? :plain
~ "#### Arguments\n" ## `Mutation` type
~ "| Name | Type | Description |"
~ "| ---- | ---- | ----------- |" The `Mutation` type contains the API's top-level entry points for all executable mutations.
- sorted_by_name(query[:arguments]).each do |argument|
= render_field(argument, query[:type][:name]) 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 :plain
## Object types ## Object types
...@@ -44,22 +108,20 @@ ...@@ -44,22 +108,20 @@
Object types represent the resources that the GitLab GraphQL API can return. 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 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) 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 For more information, see
[Object Types and Fields](https://graphql.org/learn/schema/#object-types-and-fields) [Object Types and Fields](https://graphql.org/learn/schema/#object-types-and-fields)
on `graphql.org`. on `graphql.org`.
\ \
- objects.each do |type| - object_types.each do |type|
- unless type[:fields].empty? = render_name_and_description(type)
= render_name_and_description(type) \
\ = render_object_fields(type[:fields], owner: type)
~ "| Field | Type | Description |" \
~ "| ----- | ---- | ----------- |"
- sorted_by_name(type[:fields]).each do |field|
= render_field(field, type[:name])
\
:plain :plain
## Enumeration types ## Enumeration types
...@@ -73,14 +135,13 @@ ...@@ -73,14 +135,13 @@
\ \
- enums.each do |enum| - enums.each do |enum|
- unless enum[:values].empty? = render_name_and_description(enum)
= render_name_and_description(enum) \
\ ~ "| Value | Description |"
~ "| Value | Description |" ~ "| ----- | ----------- |"
~ "| ----- | ----------- |" - enum[:values].each do |value|
- sorted_by_name(enum[:values]).each do |value| = render_enum_value(enum, value)
= render_enum_value(enum, value) \
\
:plain :plain
## Scalar types ## Scalar types
...@@ -133,7 +194,7 @@ ...@@ -133,7 +194,7 @@
### Interfaces ### Interfaces
\ \
- graphql_interface_types.each do |type| - interfaces.each do |type|
= render_name_and_description(type, level: 4) = render_name_and_description(type, level: 4)
\ \
Implementations: Implementations:
...@@ -141,8 +202,21 @@ ...@@ -141,8 +202,21 @@
- type[:implemented_by].each do |type_name| - type[:implemented_by].each do |type_name|
~ "- [`#{type_name}`](##{type_name.downcase})" ~ "- [`#{type_name}`](##{type_name.downcase})"
\ \
~ "| Field | Type | Description |" = render_object_fields(type[:fields], owner: type, level_bump: 1)
~ "| ----- | ---- | ----------- |" \
- sorted_by_name(type[:fields] + type[:connections]).each do |field|
= render_field(field, type[:name]) :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 ...@@ -164,7 +164,7 @@ RSpec.describe ::Gitlab::Graphql::Deprecation do
context 'when the context is :inline' do context 'when the context is :inline' do
it 'renders on one line' 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).to eq(expectation)
expect(deprecation.markdown(context: :inline)).to eq(expectation) expect(deprecation.markdown(context: :inline)).to eq(expectation)
...@@ -177,7 +177,7 @@ RSpec.describe ::Gitlab::Graphql::Deprecation do ...@@ -177,7 +177,7 @@ RSpec.describe ::Gitlab::Graphql::Deprecation do
WARNING: WARNING:
**Deprecated** in 10.10. **Deprecated** in 10.10.
This was renamed. This was renamed.
Use: `X.y`. Use: [`X.y`](#xy).
MD MD
expect(deprecation.markdown(context: :block)).to eq(expectation) expect(deprecation.markdown(context: :block)).to eq(expectation)
......
...@@ -4,7 +4,16 @@ require 'fast_spec_helper' ...@@ -4,7 +4,16 @@ require 'fast_spec_helper'
RSpec.describe Gitlab::Graphql::Docs::Renderer do RSpec.describe Gitlab::Graphql::Docs::Renderer do
describe '#contents' 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(:template) { Rails.root.join('lib/gitlab/graphql/docs/templates/default.md.haml') }
let(:field_description) { 'List of objects.' }
let(:query_type) do let(:query_type) do
Class.new(Types::BaseObject) { graphql_name 'Query' }.tap do |t| Class.new(Types::BaseObject) { graphql_name 'Query' }.tap do |t|
...@@ -23,8 +32,6 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do ...@@ -23,8 +32,6 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
end end
end end
let(:field_description) { 'List of objects.' }
subject(:contents) do subject(:contents) do
mock_schema.query(query_type) mock_schema.query(query_type)
...@@ -41,12 +48,15 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do ...@@ -41,12 +48,15 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
it 'contains the expected sections' do it 'contains the expected sections' do
expect(contents.lines.map(&:chomp)).to include( expect(contents.lines.map(&:chomp)).to include(
'## `Query` type', '## `Query` type',
'## `Mutation` type',
'## Connections',
'## Object types', '## Object types',
'## Enumeration types', '## Enumeration types',
'## Scalar types', '## Scalar types',
'## Abstract types', '## Abstract types',
'### Unions', '### Unions',
'### Interfaces' '### Interfaces',
'### Input types'
) )
end end
end end
...@@ -66,9 +76,11 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do ...@@ -66,9 +76,11 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
expectation = <<~DOC expectation = <<~DOC
### `ArrayTest` ### `ArrayTest`
| Field | Type | Description | #### fields
| ----- | ---- | ----------- |
| `foo` | [`#{type_name}`](##{inner_type}) | A description. | | Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="arraytestfoo"></a>`foo` | [`#{type_name}`](##{inner_type}) | A description. |
DOC DOC
is_expected.to include(expectation) is_expected.to include(expectation)
...@@ -77,17 +89,17 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do ...@@ -77,17 +89,17 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
describe 'a top level query field' do describe 'a top level query field' do
let(:expectation) do let(:expectation) do
<<~DOC <<~DOC
### `foo` ### `Query.foo`
List of objects. List of objects.
Returns [`ArrayTest`](#arraytest). Returns [`ArrayTest`](#arraytest).
#### Arguments #### arguments
| Name | Type | Description | | Name | Type | Description |
| ---- | ---- | ----------- | | ---- | ---- | ----------- |
| `id` | [`ID`](#id) | ID of the object. | | <a id="queryfooid"></a>`id` | [`ID`](#id) | ID of the object. |
DOC DOC
end end
...@@ -119,10 +131,12 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do ...@@ -119,10 +131,12 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
expectation = <<~DOC expectation = <<~DOC
### `OrderingTest` ### `OrderingTest`
| Field | Type | Description | #### fields
| ----- | ---- | ----------- |
| `bar` | [`String!`](#string) | A description of bar field. | | Name | Type | Description |
| `foo` | [`String!`](#string) | A description of foo field. | | ---- | ---- | ----------- |
| <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 DOC
is_expected.to include(expectation) is_expected.to include(expectation)
...@@ -133,6 +147,7 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do ...@@ -133,6 +147,7 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
let(:type) do let(:type) do
Class.new(Types::BaseObject) do Class.new(Types::BaseObject) do
graphql_name 'DeprecatedTest' graphql_name 'DeprecatedTest'
description 'A thing we used to use, but no longer support'
field :foo, field :foo,
type: GraphQL::STRING_TYPE, type: GraphQL::STRING_TYPE,
...@@ -142,9 +157,9 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do ...@@ -142,9 +157,9 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
field :foo_with_args, field :foo_with_args,
type: GraphQL::STRING_TYPE, type: GraphQL::STRING_TYPE,
null: false, 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 description: 'A description.' do
argument :fooity, ::GraphQL::INT_TYPE, required: false, description: 'X' argument :arg, GraphQL::INT_TYPE, required: false, description: 'Argity'
end end
field :bar, field :bar,
type: GraphQL::STRING_TYPE, type: GraphQL::STRING_TYPE,
...@@ -158,24 +173,44 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do ...@@ -158,24 +173,44 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
end end
end end
it 'includes the deprecation' do let(:section) do
expectation = <<~DOC <<~DOC
### `DeprecatedTest` ### `DeprecatedTest`
| Field | Type | Description | A thing we used to use, but no longer support.
| ----- | ---- | ----------- |
| `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
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 end
it_behaves_like 'renders correctly as GraphQL documentation'
end end
context 'when a Query.field is deprecated' do context 'when a Query.field is deprecated' do
let(:type) { ::GraphQL::INT_TYPE }
before do before do
query_type.field( query_type.field(
name: :bar, name: :bar,
...@@ -186,28 +221,30 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do ...@@ -186,28 +221,30 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
) )
end end
it 'includes the deprecation' do let(:type) { ::GraphQL::INT_TYPE }
expectation = <<~DOC let(:section) do
### `bar` <<~DOC
### `Query.bar`
A bar. A bar.
WARNING: WARNING:
**Deprecated** in 10.11. **Deprecated** in 10.11.
This was renamed. This was renamed.
Use: `Query.foo`. Use: [`Query.foo`](#queryfoo).
Returns [`Int`](#int). Returns [`Int`](#int).
DOC DOC
is_expected.to include(expectation)
end end
it_behaves_like 'renders correctly as GraphQL documentation'
end end
context 'when a field has an Enumeration type' do context 'when a field has an Enumeration type' do
let(:type) do let(:type) do
enum_type = Class.new(Types::BaseEnum) do enum_type = Class.new(Types::BaseEnum) do
graphql_name 'MyEnum' graphql_name 'MyEnum'
description 'A test of an enum.'
value 'BAZ', value 'BAZ',
description: 'A description of BAZ.' description: 'A description of BAZ.'
...@@ -223,18 +260,20 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do ...@@ -223,18 +260,20 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
end end
end end
it 'includes the description of the Enumeration' do let(:section) do
expectation = <<~DOC <<~DOC
### `MyEnum` ### `MyEnum`
A test of an enum.
| Value | Description | | Value | Description |
| ----- | ----------- | | ----- | ----------- |
| `BAR` **{warning-solid}** | **Deprecated:** This is deprecated. Deprecated in 1.10. | | <a id="myenumbar"></a>`BAR` **{warning-solid}** | **Deprecated:** This is deprecated. Deprecated in 1.10. |
| `BAZ` | A description of BAZ. | | <a id="myenumbaz"></a>`BAZ` | A description of BAZ. |
DOC DOC
is_expected.to include(expectation)
end end
it_behaves_like 'renders correctly as GraphQL documentation'
end end
context 'when a field has a global ID type' do context 'when a field has a global ID type' do
...@@ -247,26 +286,36 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do ...@@ -247,26 +286,36 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
end end
end end
it 'includes the field and the description of the ID, so we can link to it' do describe 'section for IDTest' do
type_section = <<~DOC let(:section) do
### `IDTest` <<~DOC
### `IDTest`
A test for rendering IDs. A test for rendering IDs.
| Field | Type | Description | #### fields
| ----- | ---- | ----------- |
| `foo` | [`UserID`](#userid) | A user foo. |
DOC
id_section = <<~DOC | Name | Type | Description |
### `UserID` | ---- | ---- | ----------- |
| <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"`. A `UserID` is a global ID. It is encoded as a string.
DOC
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
end end
...@@ -297,7 +346,7 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do ...@@ -297,7 +346,7 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
interface.orphan_types african_swallow interface.orphan_types african_swallow
Class.new(::Types::BaseObject) do Class.new(::Types::BaseObject) do
graphql_name 'AbstactTypeTest' graphql_name 'AbstractTypeTest'
description 'A test for abstract types.' description 'A test for abstract types.'
field :foo, union, null: true, description: 'The foo.' field :foo, union, null: true, description: 'The foo.'
...@@ -307,14 +356,16 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do ...@@ -307,14 +356,16 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
it 'lists the fields correctly, and includes descriptions of all the types' do it 'lists the fields correctly, and includes descriptions of all the types' do
type_section = <<~DOC type_section = <<~DOC
### `AbstactTypeTest` ### `AbstractTypeTest`
A test for abstract types. A test for abstract types.
| Field | Type | Description | #### fields
| ----- | ---- | ----------- |
| `flying` | [`Flying`](#flying) | A flying thing. | | Name | Type | Description |
| `foo` | [`UserOrGroup`](#userorgroup) | The foo. | | ---- | ---- | ----------- |
| <a id="abstracttypetestflying"></a>`flying` | [`Flying`](#flying) | A flying thing. |
| <a id="abstracttypetestfoo"></a>`foo` | [`UserOrGroup`](#userorgroup) | The foo. |
DOC DOC
union_section = <<~DOC union_section = <<~DOC
...@@ -337,9 +388,11 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do ...@@ -337,9 +388,11 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
- [`AfricanSwallow`](#africanswallow) - [`AfricanSwallow`](#africanswallow)
| Field | Type | Description | ##### fields
| ----- | ---- | ----------- |
| `flightSpeed` | [`Int`](#int) | Speed in mph. | | Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="flyingflightspeed"></a>`flightSpeed` | [`Int`](#int) | Speed in mph. |
DOC DOC
implementation_section = <<~DOC implementation_section = <<~DOC
...@@ -347,9 +400,11 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do ...@@ -347,9 +400,11 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
A swallow from Africa. A swallow from Africa.
| Field | Type | Description | #### fields
| ----- | ---- | ----------- |
| `flightSpeed` | [`Int`](#int) | Speed in mph. | | Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="africanswallowflightspeed"></a>`flightSpeed` | [`Int`](#int) | Speed in mph. |
DOC DOC
is_expected.to include( 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