Commit 3bd11feb authored by Jan Provaznik's avatar Jan Provaznik

Merge branch 'ajk-gitlab-docs-deprecation-use-schema-types' into 'master'

Use Schema types when generating docs for GraphQL deprecations

See merge request gitlab-org/gitlab!56864
parents ee0b4e88 ffdd9a0f
......@@ -82,11 +82,16 @@ module Types
argument :id, ::Types::GlobalIDType[::Issue], required: true, description: 'The global ID of the Issue.'
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,
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,
null: true,
......
......@@ -7,10 +7,34 @@ module Types
# Deprecated, as we prefer uppercase enums
# 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_asc', 'Updated at ascending order.', value: :updated_asc, deprecated: { reason: 'Use UPDATED_ASC', milestone: '13.5' }
value 'created_desc', 'Created at descending order.', value: :created_desc, deprecated: { reason: 'Use CREATED_DESC', milestone: '13.5' }
value 'created_asc', 'Created at ascending order.', value: :created_asc, deprecated: { reason: 'Use CREATED_ASC', milestone: '13.5' }
value 'updated_desc', 'Updated at descending order.',
value: :updated_desc,
deprecated: {
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_ASC', 'Updated at ascending order.', value: :updated_asc
......
......@@ -24,7 +24,7 @@ module Types
description: 'State of the user.'
field :email, GraphQL::STRING_TYPE, null: true,
description: 'User email.', method: :public_email,
deprecated: { reason: 'Use public_email', milestone: '13.7' }
deprecated: { reason: :renamed, replacement: 'User.publicEmail', milestone: '13.7' }
field :public_email, GraphQL::STRING_TYPE, null: true,
description: "User's public email."
field :avatar_url, GraphQL::STRING_TYPE, null: true,
......
---
title: Change the way deprecation information is presented in GraphQL documentation
merge_request: 56864
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
null: true,
description: "Number of vulnerabilities per severity level, per day, for the projects on the current user's instance security dashboard.",
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,
null: true,
......
......@@ -27,7 +27,10 @@ module Gitlab
MD
end
def render_name_and_description(object, level = 3)
# Template methods:
# Methods that return chunks of Markdown for insertion into the document
def render_name_and_description(object, owner: nil, level: 3)
content = []
content << "#{'#' * level} `#{object[:name]}`"
......@@ -35,10 +38,22 @@ module Gitlab
if object[:description].present?
desc = object[:description].strip
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
end
content.join("\n\n")
content.compact.join("\n\n")
end
def render_return_type(query)
"Returns #{render_field_type(query[:type])}.\n"
end
def sorted_by_name(objects)
......@@ -47,39 +62,25 @@ module Gitlab
objects.sort_by { |o| o[:name] }
end
def render_field(field)
row(render_name(field), render_field_type(field[:type]), render_description(field))
def render_field(field, owner)
render_row(
render_name(field, owner),
render_field_type(field[:type]),
render_description(field, owner, :inline)
)
end
def render_enum_value(value)
row(render_name(value), render_description(value))
def render_enum_value(enum, value)
render_row(render_name(value, enum[:name]), render_description(value, enum[:name], :inline))
end
def row(*values)
"| #{values.join(' | ')} |"
def render_union_member(member)
"- [`#{member}`](##{member.downcase})"
end
def render_name(object)
rendered_name = "`#{object[:name]}`"
rendered_name += ' **{warning-solid}**' if object[:is_deprecated]
rendered_name
end
# QUERIES:
# 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)
return object[:description] unless object[:is_deprecated]
"**Deprecated:** #{object[:deprecation_reason]}"
end
def render_field_type(type)
"[`#{type[:info]}`](##{type[:name].downcase})"
end
def render_return_type(query)
"Returns #{render_field_type(query[:type])}.\n"
end
# 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.
......@@ -103,6 +104,83 @@ module Gitlab
!enum_type[:name].in?(%w[__DirectiveLocation __TypeKind])
end
end
private # DO NOT CALL THESE METHODS IN TEMPLATES
# Template methods
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
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?
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 render_field_type(type)
"[`#{type[:info]}`](##{type[:name].downcase})"
end
# Queries
# 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
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
args[arg_name]
end
end
end
end
......
......@@ -10,17 +10,20 @@ module Gitlab
# It uses graphql-docs helpers and schema parser, more information in https://github.com/gjtorikian/graphql-docs.
#
# 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
# template: The path of the haml template to be parsed
class Renderer
include Gitlab::Graphql::Docs::Helper
attr_reader :schema
def initialize(schema, output_dir:, template:)
@output_dir = output_dir
@template = 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
def contents
......
......@@ -27,7 +27,7 @@
\
- sorted_by_name(queries).each do |query|
= render_name_and_description(query)
= render_name_and_description(query, owner: 'Query')
\
= render_return_type(query)
- unless query[:arguments].empty?
......@@ -35,7 +35,7 @@
~ "| Name | Type | Description |"
~ "| ---- | ---- | ----------- |"
- sorted_by_name(query[:arguments]).each do |argument|
= render_field(argument)
= render_field(argument, query[:type][:name])
\
:plain
......@@ -58,7 +58,7 @@
~ "| Field | Type | Description |"
~ "| ----- | ---- | ----------- |"
- sorted_by_name(type[:fields]).each do |field|
= render_field(field)
= render_field(field, type[:name])
\
:plain
......@@ -79,7 +79,7 @@
~ "| Value | Description |"
~ "| ----- | ----------- |"
- sorted_by_name(enum[:values]).each do |value|
= render_enum_value(value)
= render_enum_value(enum, value)
\
:plain
......@@ -121,12 +121,12 @@
\
- graphql_union_types.each do |type|
= render_name_and_description(type, 4)
= render_name_and_description(type, level: 4)
\
One of:
\
- type[:possible_types].each do |type_name|
~ "- [`#{type_name}`](##{type_name.downcase})"
- type[:possible_types].each do |member|
= render_union_member(member)
\
:plain
......@@ -134,7 +134,7 @@
\
- graphql_interface_types.each do |type|
= render_name_and_description(type, 4)
= render_name_and_description(type, level: 4)
\
Implementations:
\
......@@ -144,5 +144,5 @@
~ "| Field | Type | Description |"
~ "| ----- | ---- | ----------- |"
- 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
desc 'GitLab | GraphQL | Generate GraphQL docs'
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
......@@ -119,7 +119,7 @@ namespace :gitlab do
desc 'GitLab | GraphQL | Check if GraphQL docs are up to date'
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'))
......
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Graphql::Docs::Renderer do
describe '#contents' do
# Returns a Schema that uses the given `type`
def mock_schema(type, field_description)
query_type = Class.new(Types::BaseObject) do
graphql_name 'Query'
let(:template) { Rails.root.join('lib/gitlab/graphql/docs/templates/default.md.haml') }
field :foo, type, null: true do
description field_description
let(:query_type) do
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.'
end
end
end
GraphQL::Schema.define(
query: query_type,
resolve_type: ->(obj, ctx) { raise 'Not a real schema' }
)
let(:mock_schema) do
Class.new(GraphQL::Schema) do
def resolve_type(obj, ctx)
raise 'Not a real schema'
end
end
end
let_it_be(:template) { Rails.root.join('lib/gitlab/graphql/docs/templates/default.md.haml') }
let(:field_description) { 'List of objects.' }
subject(:contents) do
mock_schema.query(query_type)
described_class.new(
mock_schema(type, field_description).graphql_definition,
mock_schema,
output_dir: nil,
template: template
).contents
......@@ -136,6 +139,22 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
null: false,
deprecated: { reason: 'This is deprecated', milestone: '1.10' },
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
......@@ -145,7 +164,40 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
| 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
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