Commit 50c9ca6d authored by Alex Kalderimis's avatar Alex Kalderimis

Use new deprecation information when generating docs

This makes use of the new deprecation abstraction when generating
the GraphQL documentation to provide richer structure.
parent 55959477
...@@ -82,11 +82,16 @@ module Types ...@@ -82,11 +82,16 @@ module Types
argument :id, ::Types::GlobalIDType[::Issue], required: true, description: 'The global ID of the Issue.' argument :id, ::Types::GlobalIDType[::Issue], required: true, description: 'The global ID of the Issue.'
end end
field :instance_statistics_measurements, Types::Admin::Analytics::UsageTrends::MeasurementType.connection_type, field :instance_statistics_measurements,
type: Types::Admin::Analytics::UsageTrends::MeasurementType.connection_type,
null: true, null: true,
description: 'Get statistics on the instance.', description: 'Get statistics on the instance.',
deprecated: { reason: 'This field was renamed. Use the `usageTrendsMeasurements` field instead', milestone: '13.10' }, resolver: Resolvers::Admin::Analytics::UsageTrends::MeasurementsResolver,
resolver: Resolvers::Admin::Analytics::UsageTrends::MeasurementsResolver deprecated: {
reason: :renamed,
replacement: 'Query.usageTrendsMeasurements',
milestone: '13.10'
}
field :usage_trends_measurements, Types::Admin::Analytics::UsageTrends::MeasurementType.connection_type, field :usage_trends_measurements, Types::Admin::Analytics::UsageTrends::MeasurementType.connection_type,
null: true, null: true,
......
...@@ -7,10 +7,34 @@ module Types ...@@ -7,10 +7,34 @@ module Types
# Deprecated, as we prefer uppercase enums # Deprecated, as we prefer uppercase enums
# https://gitlab.com/groups/gitlab-org/-/epics/1838 # https://gitlab.com/groups/gitlab-org/-/epics/1838
value 'updated_desc', 'Updated at descending order.', value: :updated_desc, deprecated: { reason: 'Use UPDATED_DESC', milestone: '13.5' } value 'updated_desc', 'Updated at descending order.',
value 'updated_asc', 'Updated at ascending order.', value: :updated_asc, deprecated: { reason: 'Use UPDATED_ASC', milestone: '13.5' } value: :updated_desc,
value 'created_desc', 'Created at descending order.', value: :created_desc, deprecated: { reason: 'Use CREATED_DESC', milestone: '13.5' } deprecated: {
value 'created_asc', 'Created at ascending order.', value: :created_asc, deprecated: { reason: 'Use CREATED_ASC', milestone: '13.5' } reason: :renamed,
replacement: 'UPDATED_DESC',
milestone: '13.5'
}
value 'updated_asc', 'Updated at ascending order.',
value: :updated_asc,
deprecated: {
reason: :renamed,
replacement: 'UPDATED_ASC',
milestone: '13.5'
}
value 'created_desc', 'Created at descending order.',
value: :created_desc,
deprecated: {
reason: :renamed,
replacement: 'CREATED_DESC',
milestone: '13.5'
}
value 'created_asc', 'Created at ascending order.',
value: :created_asc,
deprecated: {
reason: :renamed,
replacement: 'CREATED_ASC',
milestone: '13.5'
}
value 'UPDATED_DESC', 'Updated at descending order.', value: :updated_desc value 'UPDATED_DESC', 'Updated at descending order.', value: :updated_desc
value 'UPDATED_ASC', 'Updated at ascending order.', value: :updated_asc value 'UPDATED_ASC', 'Updated at ascending order.', value: :updated_asc
......
---
title: Change how we handle deprecations in GraphQL docs
merge_request: 56698
author:
type: changed
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -40,7 +40,7 @@ module EE ...@@ -40,7 +40,7 @@ module EE
null: true, null: true,
description: "Number of vulnerabilities per severity level, per day, for the projects on the current user's instance security dashboard.", description: "Number of vulnerabilities per severity level, per day, for the projects on the current user's instance security dashboard.",
resolver: ::Resolvers::VulnerabilitiesHistoryResolver, resolver: ::Resolvers::VulnerabilitiesHistoryResolver,
deprecated: { reason: 'Use `vulnerabilitiesCountByDay`', milestone: '13.3' } deprecated: { reason: :discouraged, replacement: 'Query.vulnerabilitiesCountByDay', milestone: '13.3' }
field :geo_node, ::Types::Geo::GeoNodeType, field :geo_node, ::Types::Geo::GeoNodeType,
null: true, null: true,
......
...@@ -27,7 +27,7 @@ module Gitlab ...@@ -27,7 +27,7 @@ module Gitlab
MD MD
end end
def render_name_and_description(object, level = 3) def render_name_and_description(object, owner: nil, level: 3)
content = [] content = []
content << "#{'#' * level} `#{object[:name]}`" content << "#{'#' * level} `#{object[:name]}`"
...@@ -35,10 +35,18 @@ module Gitlab ...@@ -35,10 +35,18 @@ module Gitlab
if object[:description].present? if object[:description].present?
desc = object[:description].strip desc = object[:description].strip
desc += '.' unless desc.ends_with?('.') desc += '.' unless desc.ends_with?('.')
end
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 << desc
end end
content.join("\n\n") content.compact.join("\n\n")
end end
def sorted_by_name(objects) def sorted_by_name(objects)
...@@ -47,19 +55,23 @@ module Gitlab ...@@ -47,19 +55,23 @@ module Gitlab
objects.sort_by { |o| o[:name] } objects.sort_by { |o| o[:name] }
end end
def render_field(field) def render_field(field, owner)
row(render_name(field), render_field_type(field[:type]), render_description(field)) row(
render_name(field, owner),
render_field_type(field[:type]),
render_description(field, owner, :inline)
)
end end
def render_enum_value(value) def render_enum_value(enum, value)
row(render_name(value), render_description(value)) row(render_name(value, enum[:name]), render_description(value, enum[:name], :inline))
end end
def row(*values) def row(*values)
"| #{values.join(' | ')} |" "| #{values.map { |val| val.to_s.gsub(/\n+/, ' ') }.join(' | ')} |"
end end
def render_name(object) 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 rendered_name
...@@ -67,10 +79,51 @@ module Gitlab ...@@ -67,10 +79,51 @@ module Gitlab
# 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) def render_description(object, owner = nil, context = :block)
return object[:description] unless object[:is_deprecated] owner = Array.wrap(owner)
return render_deprecation(object, owner, context) if object[:is_deprecated]
return if object[:description].blank?
desc = object[:description].strip
desc += '.' unless desc.ends_with?('.')
desc
end
def render_deprecation(object, owner, context)
deprecation = schema_deprecation(owner, object[:name])
return deprecation.markdown(context: context) if deprecation
reason = object[:deprecation_reason] || 'Use of this is deprecated.'
"**Deprecated:** #{reason}"
end
def schema_deprecation(type_name, field_name)
schema_field(type_name, field_name)&.deprecation
end
def schema_field(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
return if type_name.nil? || field_name.nil?
type = schema.types[type_name]
return unless type && type.kind.fields?
field = type.fields[field_name]
return field if arg_name.nil?
args = field.arguments
is_mutation = field.mutation && field.mutation <= ::Mutations::BaseMutation
args = args['input'].type.unwrap.arguments if is_mutation
"**Deprecated:** #{object[:deprecation_reason]}" args[arg_name]
end end
def render_field_type(type) def render_field_type(type)
......
...@@ -10,17 +10,20 @@ module Gitlab ...@@ -10,17 +10,20 @@ module Gitlab
# It uses graphql-docs helpers and schema parser, more information in https://github.com/gjtorikian/graphql-docs. # It uses graphql-docs helpers and schema parser, more information in https://github.com/gjtorikian/graphql-docs.
# #
# Arguments: # Arguments:
# schema - the GraphQL schema definition. For GitLab should be: GitlabSchema.graphql_definition # schema - the GraphQL schema definition. For GitLab should be: GitlabSchema
# output_dir: The folder where the markdown files will be saved # output_dir: The folder where the markdown files will be saved
# template: The path of the haml template to be parsed # template: The path of the haml template to be parsed
class Renderer class Renderer
include Gitlab::Graphql::Docs::Helper include Gitlab::Graphql::Docs::Helper
attr_reader :schema
def initialize(schema, output_dir:, template:) def initialize(schema, output_dir:, template:)
@output_dir = output_dir @output_dir = output_dir
@template = template @template = template
@layout = Haml::Engine.new(File.read(template)) @layout = Haml::Engine.new(File.read(template))
@parsed_schema = GraphQLDocs::Parser.new(schema, {}).parse @parsed_schema = GraphQLDocs::Parser.new(schema.graphql_definition, {}).parse
@schema = schema
end end
def contents def contents
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
\ \
- sorted_by_name(queries).each do |query| - sorted_by_name(queries).each do |query|
= render_name_and_description(query) = render_name_and_description(query, owner: 'Query')
\ \
= render_return_type(query) = render_return_type(query)
- unless query[:arguments].empty? - unless query[:arguments].empty?
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
~ "| Name | Type | Description |" ~ "| Name | Type | Description |"
~ "| ---- | ---- | ----------- |" ~ "| ---- | ---- | ----------- |"
- sorted_by_name(query[:arguments]).each do |argument| - sorted_by_name(query[:arguments]).each do |argument|
= render_field(argument) = render_field(argument, query[:type][:name])
\ \
:plain :plain
...@@ -58,7 +58,7 @@ ...@@ -58,7 +58,7 @@
~ "| Field | Type | Description |" ~ "| Field | Type | Description |"
~ "| ----- | ---- | ----------- |" ~ "| ----- | ---- | ----------- |"
- sorted_by_name(type[:fields]).each do |field| - sorted_by_name(type[:fields]).each do |field|
= render_field(field) = render_field(field, type[:name])
\ \
:plain :plain
...@@ -79,7 +79,7 @@ ...@@ -79,7 +79,7 @@
~ "| Value | Description |" ~ "| Value | Description |"
~ "| ----- | ----------- |" ~ "| ----- | ----------- |"
- sorted_by_name(enum[:values]).each do |value| - sorted_by_name(enum[:values]).each do |value|
= render_enum_value(value) = render_enum_value(enum, value)
\ \
:plain :plain
...@@ -121,7 +121,7 @@ ...@@ -121,7 +121,7 @@
\ \
- graphql_union_types.each do |type| - graphql_union_types.each do |type|
= render_name_and_description(type, 4) = render_name_and_description(type, level: 4)
\ \
One of: One of:
\ \
...@@ -134,7 +134,7 @@ ...@@ -134,7 +134,7 @@
\ \
- graphql_interface_types.each do |type| - graphql_interface_types.each do |type|
= render_name_and_description(type, 4) = render_name_and_description(type, level: 4)
\ \
Implementations: Implementations:
\ \
...@@ -144,5 +144,5 @@ ...@@ -144,5 +144,5 @@
~ "| Field | Type | Description |" ~ "| Field | Type | Description |"
~ "| ----- | ---- | ----------- |" ~ "| ----- | ---- | ----------- |"
- sorted_by_name(type[:fields] + type[:connections]).each do |field| - sorted_by_name(type[:fields] + type[:connections]).each do |field|
= render_field(field) = render_field(field, type[:name])
\ \
...@@ -110,7 +110,7 @@ namespace :gitlab do ...@@ -110,7 +110,7 @@ namespace :gitlab do
desc 'GitLab | GraphQL | Generate GraphQL docs' desc 'GitLab | GraphQL | Generate GraphQL docs'
task compile_docs: [:environment, :enable_feature_flags] do task compile_docs: [:environment, :enable_feature_flags] do
renderer = Gitlab::Graphql::Docs::Renderer.new(GitlabSchema.graphql_definition, render_options) renderer = Gitlab::Graphql::Docs::Renderer.new(GitlabSchema, render_options)
renderer.write renderer.write
...@@ -119,7 +119,7 @@ namespace :gitlab do ...@@ -119,7 +119,7 @@ namespace :gitlab do
desc 'GitLab | GraphQL | Check if GraphQL docs are up to date' desc 'GitLab | GraphQL | Check if GraphQL docs are up to date'
task check_docs: [:environment, :enable_feature_flags] do task check_docs: [:environment, :enable_feature_flags] do
renderer = Gitlab::Graphql::Docs::Renderer.new(GitlabSchema.graphql_definition, render_options) renderer = Gitlab::Graphql::Docs::Renderer.new(GitlabSchema, render_options)
doc = File.read(Rails.root.join(OUTPUT_DIR, 'index.md')) doc = File.read(Rails.root.join(OUTPUT_DIR, 'index.md'))
......
...@@ -4,29 +4,32 @@ require 'spec_helper' ...@@ -4,29 +4,32 @@ require 'spec_helper'
RSpec.describe Gitlab::Graphql::Docs::Renderer do RSpec.describe Gitlab::Graphql::Docs::Renderer do
describe '#contents' do describe '#contents' do
# Returns a Schema that uses the given `type` let_it_be(:template) { Rails.root.join('lib/gitlab/graphql/docs/templates/default.md.haml') }
def mock_schema(type, field_description)
query_type = Class.new(Types::BaseObject) do
graphql_name 'Query'
field :foo, type, null: true do let(:query_type) do
description field_description Class.new(Types::BaseObject) { graphql_name 'Query' }.tap do |t|
# this keeps type and field_description in scope.
t.field :foo, type, null: true, description: field_description do
argument :id, GraphQL::ID_TYPE, required: false, description: 'ID of the object.' argument :id, GraphQL::ID_TYPE, required: false, description: 'ID of the object.'
end end
end end
end
GraphQL::Schema.define( let(:mock_schema) do
query: query_type, Class.new(GraphQL::Schema) do
resolve_type: ->(obj, ctx) { raise 'Not a real schema' } def resolve_type(obj, ctx)
) raise 'Not a real schema'
end
end
end end
let_it_be(:template) { Rails.root.join('lib/gitlab/graphql/docs/templates/default.md.haml') }
let(:field_description) { 'List of objects.' } let(:field_description) { 'List of objects.' }
subject(:contents) do subject(:contents) do
mock_schema.query(query_type)
described_class.new( described_class.new(
mock_schema(type, field_description).graphql_definition, mock_schema,
output_dir: nil, output_dir: nil,
template: template template: template
).contents ).contents
...@@ -136,6 +139,22 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do ...@@ -136,6 +139,22 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
null: false, null: false,
deprecated: { reason: 'This is deprecated', milestone: '1.10' }, deprecated: { reason: 'This is deprecated', milestone: '1.10' },
description: 'A description.' description: 'A description.'
field :foo_with_args,
type: GraphQL::STRING_TYPE,
null: false,
deprecated: { reason: 'Do not use', milestone: '1.10' },
description: 'A description.' do
argument :fooity, ::GraphQL::INT_TYPE, required: false, description: 'X'
end
field :bar,
type: GraphQL::STRING_TYPE,
null: false,
description: 'A description.',
deprecated: {
reason: :renamed,
milestone: '1.10',
replacement: 'Query.boom'
}
end end
end end
...@@ -145,7 +164,40 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do ...@@ -145,7 +164,40 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
| Field | Type | Description | | Field | Type | Description |
| ----- | ---- | ----------- | | ----- | ---- | ----------- |
| `foo` **{warning-solid}** | [`String!`](#string) | **Deprecated:** This is deprecated. Deprecated in 1.10. | | `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)
end
end
context 'when a Query.field is deprecated' do
let(:type) { ::GraphQL::INT_TYPE }
before do
query_type.field(
name: :bar,
type: type,
null: true,
description: 'A bar',
deprecated: { reason: :renamed, milestone: '10.11', replacement: 'Query.foo' }
)
end
it 'includes the deprecation' do
expectation = <<~DOC
### `bar`
A bar.
WARNING:
**Deprecated** in 10.11.
This was renamed.
Use: `Query.foo`.
Returns [`Int`](#int).
DOC DOC
is_expected.to include(expectation) is_expected.to include(expectation)
......
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