Commit 22743397 authored by Brett Walker's avatar Brett Walker Committed by Heinrich Lee Yu

Start removing resolve procs from GraphQL Types

so that we can start using the new GraphQL
interpreter (https://graphql-ruby.org/queries/interpreter.html#compatibility)
parent a592e3fe
...@@ -36,8 +36,7 @@ module Types ...@@ -36,8 +36,7 @@ module Types
end end
field :author, Types::UserType, null: false, field :author, Types::UserType, null: false,
description: 'User that created the issue', description: 'User that created the issue'
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find }
field :assignees, Types::UserType.connection_type, null: true, field :assignees, Types::UserType.connection_type, null: true,
description: 'Assignees of the issue' description: 'Assignees of the issue'
...@@ -45,16 +44,14 @@ module Types ...@@ -45,16 +44,14 @@ module Types
field :labels, Types::LabelType.connection_type, null: true, field :labels, Types::LabelType.connection_type, null: true,
description: 'Labels of the issue' description: 'Labels of the issue'
field :milestone, Types::MilestoneType, null: true, field :milestone, Types::MilestoneType, null: true,
description: 'Milestone of the issue', description: 'Milestone of the issue'
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find }
field :due_date, Types::TimeType, null: true, field :due_date, Types::TimeType, null: true,
description: 'Due date of the issue' description: 'Due date of the issue'
field :confidential, GraphQL::BOOLEAN_TYPE, null: false, field :confidential, GraphQL::BOOLEAN_TYPE, null: false,
description: 'Indicates the issue is confidential' description: 'Indicates the issue is confidential'
field :discussion_locked, GraphQL::BOOLEAN_TYPE, null: false, field :discussion_locked, GraphQL::BOOLEAN_TYPE, null: false,
description: 'Indicates discussion is locked on the issue', description: 'Indicates discussion is locked on the issue'
resolve: -> (obj, _args, _ctx) { !!obj.discussion_locked }
field :upvotes, GraphQL::INT_TYPE, null: false, field :upvotes, GraphQL::INT_TYPE, null: false,
description: 'Number of upvotes the issue has received' description: 'Number of upvotes the issue has received'
...@@ -108,6 +105,18 @@ module Types ...@@ -108,6 +105,18 @@ module Types
field :severity, Types::IssuableSeverityEnum, null: true, field :severity, Types::IssuableSeverityEnum, null: true,
description: 'Severity level of the incident' description: 'Severity level of the incident'
def author
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find
end
def milestone
Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, object.milestone_id).find
end
def discussion_locked
!!object.discussion_locked
end
end end
end end
......
...@@ -12,7 +12,10 @@ module Types ...@@ -12,7 +12,10 @@ module Types
authorize :read_project authorize :read_project
field :project, Types::ProjectType, null: true, field :project, Types::ProjectType, null: true,
description: 'Project that User is a member of', description: 'Project that User is a member of'
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, obj.source_id).find }
def project
Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.source_id).find
end
end end
end end
...@@ -24,16 +24,14 @@ module Types ...@@ -24,16 +24,14 @@ module Types
field :project, Types::ProjectType, field :project, Types::ProjectType,
description: 'The project the snippet is associated with', description: 'The project the snippet is associated with',
null: true, null: true,
authorize: :read_project, authorize: :read_project
resolve: -> (snippet, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, snippet.project_id).find }
# Author can be nil in some scenarios. For example, # Author can be nil in some scenarios. For example,
# when the admin setting restricted visibility # when the admin setting restricted visibility
# level is set to public # level is set to public
field :author, Types::UserType, field :author, Types::UserType,
description: 'The owner of the snippet', description: 'The owner of the snippet',
null: true, null: true
resolve: -> (snippet, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, snippet.author_id).find }
field :file_name, GraphQL::STRING_TYPE, field :file_name, GraphQL::STRING_TYPE,
description: 'File Name of the snippet', description: 'File Name of the snippet',
...@@ -86,5 +84,13 @@ module Types ...@@ -86,5 +84,13 @@ module Types
null: true null: true
markdown_field :description_html, null: true, method: :description markdown_field :description_html, null: true, method: :description
def author
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find
end
def project
Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find
end
end end
end end
...@@ -12,13 +12,19 @@ module Gitlab ...@@ -12,13 +12,19 @@ module Gitlab
end end
method_name = kwargs.delete(:method) || name.to_s.sub(/_html$/, '') method_name = kwargs.delete(:method) || name.to_s.sub(/_html$/, '')
kwargs[:resolve] = Gitlab::Graphql::MarkdownField::Resolver.new(method_name.to_sym).proc resolver_method = "#{name}_resolver".to_sym
kwargs[:resolver_method] = resolver_method
kwargs[:description] ||= "The GitLab Flavored Markdown rendering of `#{method_name}`" kwargs[:description] ||= "The GitLab Flavored Markdown rendering of `#{method_name}`"
# Adding complexity to rendered notes since that could cause queries. # Adding complexity to rendered notes since that could cause queries.
kwargs[:complexity] ||= 5 kwargs[:complexity] ||= 5
field name, GraphQL::STRING_TYPE, **kwargs field name, GraphQL::STRING_TYPE, **kwargs
define_method resolver_method do
# We need to `dup` the context so the MarkdownHelper doesn't modify it
::MarkupHelper.markdown_field(object, method_name.to_sym, context.to_h.dup)
end
end end
end end
end end
......
# frozen_string_literal: true
module Gitlab
module Graphql
module MarkdownField
class Resolver
attr_reader :method_name
def initialize(method_name)
@method_name = method_name
end
def proc
-> (object, _args, ctx) do
# We need to `dup` the context so the MarkdownHelper doesn't modify it
::MarkupHelper.markdown_field(object, method_name, ctx.to_h.dup)
end
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Graphql::MarkdownField::Resolver do
include Gitlab::Routing
let(:resolver) { described_class.new(:note) }
describe '#proc' do
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project) }
let(:note) do
create(:note,
note: "Referencing #{issue.to_reference(full: true)}")
end
it 'renders markdown correctly' do
expect(resolver.proc.call(note, {}, {})).to include(issue_path(issue))
end
context 'when the issue is not publicly accessible' do
let(:project) { create(:project, :private) }
it 'hides the references from users that are not allowed to see the reference' do
expect(resolver.proc.call(note, {}, {})).not_to include(issue_path(issue))
end
it 'shows the reference to users that are allowed to see it' do
expect(resolver.proc.call(note, {}, { current_user: project.owner }))
.to include(issue_path(issue))
end
end
end
end
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::Graphql::MarkdownField do RSpec.describe Gitlab::Graphql::MarkdownField do
include Gitlab::Routing
describe '.markdown_field' do describe '.markdown_field' do
it 'creates the field with some default attributes' do it 'creates the field with some default attributes' do
field = class_with_markdown_field(:test_html, null: true, method: :hello).fields['testHtml'] field = class_with_markdown_field(:test_html, null: true, method: :hello).fields['testHtml']
...@@ -13,7 +15,7 @@ RSpec.describe Gitlab::Graphql::MarkdownField do ...@@ -13,7 +15,7 @@ RSpec.describe Gitlab::Graphql::MarkdownField do
end end
context 'developer warnings' do context 'developer warnings' do
let(:expected_error) { /Only `method` is allowed to specify the markdown field/ } let_it_be(:expected_error) { /Only `method` is allowed to specify the markdown field/ }
it 'raises when passing a resolver' do it 'raises when passing a resolver' do
expect { class_with_markdown_field(:test_html, null: true, resolver: 'not really') } expect { class_with_markdown_field(:test_html, null: true, resolver: 'not really') }
...@@ -27,30 +29,61 @@ RSpec.describe Gitlab::Graphql::MarkdownField do ...@@ -27,30 +29,61 @@ RSpec.describe Gitlab::Graphql::MarkdownField do
end end
context 'resolving markdown' do context 'resolving markdown' do
let(:note) { build(:note, note: '# Markdown!') } let_it_be(:note) { build(:note, note: '# Markdown!') }
let(:thing_with_markdown) { double('markdown thing', object: note) } let_it_be(:expected_markdown) { '<h1 data-sourcepos="1:1-1:11" dir="auto">Markdown!</h1>' }
let(:expected_markdown) { '<h1 data-sourcepos="1:1-1:11" dir="auto">Markdown!</h1>' } let_it_be(:query_type) { GraphQL::ObjectType.new }
let(:query_type) { GraphQL::ObjectType.new } let_it_be(:schema) { GraphQL::Schema.define(query: query_type, mutation: nil)}
let(:schema) { GraphQL::Schema.define(query: query_type, mutation: nil)} let_it_be(:query) { GraphQL::Query.new(schema, document: nil, context: {}, variables: {}) }
let(:context) { GraphQL::Query::Context.new(query: OpenStruct.new(schema: schema), values: nil, object: nil) } let_it_be(:context) { GraphQL::Query::Context.new(query: query, values: {}, object: nil) }
let(:type_class) { class_with_markdown_field(:note_html, null: false) }
let(:type_instance) { type_class.authorized_new(note, context) }
let(:field) { type_class.fields['noteHtml'] }
it 'renders markdown from the same property as the field name without the `_html` suffix' do it 'renders markdown from the same property as the field name without the `_html` suffix' do
field = class_with_markdown_field(:note_html, null: false).fields['noteHtml'] expect(field.to_graphql.resolve(type_instance, {}, context)).to eq(expected_markdown)
end
context 'when a `method` argument is passed' do
let(:type_class) { class_with_markdown_field(:test_html, null: false, method: :note) }
let(:field) { type_class.fields['testHtml'] }
expect(field.to_graphql.resolve(thing_with_markdown, {}, context)).to eq(expected_markdown) it 'renders markdown from a specific property' do
expect(field.to_graphql.resolve(type_instance, {}, context)).to eq(expected_markdown)
end end
end
describe 'basic verification that references work' do
let_it_be(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project) }
let(:note) { build(:note, note: "Referencing #{issue.to_reference(full: true)}") }
it 'renders markdown from a specific property when a `method` argument is passed' do it 'renders markdown correctly' do
field = class_with_markdown_field(:test_html, null: false, method: :note).fields['testHtml'] expect(field.to_graphql.resolve(type_instance, {}, context)).to include(issue_path(issue))
end
expect(field.to_graphql.resolve(thing_with_markdown, {}, context)).to eq(expected_markdown) context 'when the issue is not publicly accessible' do
let_it_be(:project) { create(:project, :private) }
it 'hides the references from users that are not allowed to see the reference' do
expect(field.to_graphql.resolve(type_instance, {}, context)).not_to include(issue_path(issue))
end
it 'shows the reference to users that are allowed to see it' do
context = GraphQL::Query::Context.new(query: query, values: { current_user: project.owner }, object: nil)
type_instance = type_class.authorized_new(note, context)
expect(field.to_graphql.resolve(type_instance, {}, context)).to include(issue_path(issue))
end
end
end end
end end
end end
def class_with_markdown_field(name, **args) def class_with_markdown_field(name, **args)
Class.new(GraphQL::Schema::Object) do Class.new(Types::BaseObject) do
prepend Gitlab::Graphql::MarkdownField prepend Gitlab::Graphql::MarkdownField
graphql_name 'MarkdownFieldTest'
markdown_field name, **args markdown_field name, **args
end end
......
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