Upgrade GraphQL gem to 1.8.17
- Due to https://github.com/exAspArk/batch-loader/pull/32, we changed BatchLoader.for into BatchLoader::GraphQL.for - since our results are wrapped in a BatchLoader::GraphQL, calling `sync` during authorization is required to get real object - `graphql` now has it's own authorization system. Our `authorized?` method conflicted and required renaming
This commit is contained in:
parent
29e3a08b8f
commit
aa7b1cfc5b
28 changed files with 74 additions and 58 deletions
2
Gemfile
2
Gemfile
|
@ -83,7 +83,7 @@ gem 'grape-entity', '~> 0.7.1'
|
|||
gem 'rack-cors', '~> 1.0.0', require: 'rack/cors'
|
||||
|
||||
# GraphQL API
|
||||
gem 'graphql', '= 1.8.4'
|
||||
gem 'graphql', '= 1.8.17'
|
||||
gem 'graphiql-rails', '~> 1.4.10'
|
||||
gem 'apollo_upload_server', '~> 2.0.0.beta3'
|
||||
gem 'graphql-docs', '~> 1.6.0', group: [:development, :test]
|
||||
|
|
|
@ -377,7 +377,7 @@ GEM
|
|||
graphiql-rails (1.4.10)
|
||||
railties
|
||||
sprockets-rails
|
||||
graphql (1.8.4)
|
||||
graphql (1.8.17)
|
||||
graphql-docs (1.6.0)
|
||||
commonmarker (~> 0.16)
|
||||
escape_utils (~> 1.2)
|
||||
|
@ -1110,7 +1110,7 @@ DEPENDENCIES
|
|||
grape-path-helpers (~> 1.1)
|
||||
grape_logging (~> 1.7)
|
||||
graphiql-rails (~> 1.4.10)
|
||||
graphql (= 1.8.4)
|
||||
graphql (= 1.8.17)
|
||||
graphql-docs (~> 1.6.0)
|
||||
grpc (~> 1.19.0)
|
||||
haml_lint (~> 0.31.0)
|
||||
|
|
|
@ -18,8 +18,6 @@ module Mutations
|
|||
required: true,
|
||||
description: copy_field_description(Types::Notes::NoteType, :body)
|
||||
|
||||
private
|
||||
|
||||
def resolve(args)
|
||||
noteable = authorized_find!(id: args[:noteable_id])
|
||||
|
||||
|
@ -37,6 +35,8 @@ module Mutations
|
|||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_note_params(noteable, args)
|
||||
{
|
||||
noteable: noteable,
|
||||
|
|
|
@ -11,7 +11,7 @@ module Resolvers
|
|||
end
|
||||
|
||||
def model_by_full_path(model, full_path)
|
||||
BatchLoader.for(full_path).batch(key: model) do |full_paths, loader, args|
|
||||
BatchLoader::GraphQL.for(full_path).batch(key: model) do |full_paths, loader, args|
|
||||
# `with_route` avoids an N+1 calculating full_path
|
||||
args[:key].where_full_path_in(full_paths).with_route.each do |model_instance|
|
||||
loader.call(model_instance.full_path, model_instance)
|
||||
|
|
|
@ -41,13 +41,11 @@ module Resolvers
|
|||
|
||||
type Types::IssueType, null: true
|
||||
|
||||
alias_method :project, :object
|
||||
|
||||
def resolve(**args)
|
||||
# The project could have been loaded in batch by `BatchLoader`.
|
||||
# At this point we need the `id` of the project to query for issues, so
|
||||
# make sure it's loaded and not `nil` before continuing.
|
||||
project.sync if project.respond_to?(:sync)
|
||||
project = object.respond_to?(:sync) ? object.sync : object
|
||||
return Issue.none if project.nil?
|
||||
|
||||
# Will need to be be made group & namespace aware with
|
||||
|
|
|
@ -25,8 +25,10 @@ module Resolvers
|
|||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def batch_load(iid)
|
||||
BatchLoader.for(iid.to_s).batch(key: project) do |iids, loader, args|
|
||||
args[:key].merge_requests.where(iid: iids).each do |mr|
|
||||
BatchLoader::GraphQL.for(iid.to_s).batch(key: project) do |iids, loader, args|
|
||||
arg_key = args[:key].respond_to?(:sync) ? args[:key].sync : args[:key]
|
||||
|
||||
arg_key.merge_requests.where(iid: iids).each do |mr|
|
||||
loader.call(mr.iid.to_s, mr)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,13 +9,11 @@ module Resolvers
|
|||
|
||||
type Types::ProjectType, null: true
|
||||
|
||||
alias_method :namespace, :object
|
||||
|
||||
def resolve(include_subgroups:)
|
||||
# The namespace could have been loaded in batch by `BatchLoader`.
|
||||
# At this point we need the `id` or the `full_path` of the namespace
|
||||
# to query for projects, so make sure it's loaded and not `nil` before continuing.
|
||||
namespace.sync if namespace.respond_to?(:sync)
|
||||
namespace = object.respond_to?(:sync) ? object.sync : object
|
||||
return Project.none if namespace.nil?
|
||||
|
||||
if include_subgroups
|
||||
|
|
|
@ -54,14 +54,14 @@ module Gitlab
|
|||
# The field is a built-in/scalar type, or a list of scalars
|
||||
# authorize using the parent's object
|
||||
parent_typed_object.object
|
||||
elsif resolved_type.respond_to?(:object)
|
||||
# The field is a type representing a single object, we'll authorize
|
||||
# against the object directly
|
||||
resolved_type.object
|
||||
elsif @field.connection? || resolved_type.is_a?(Array)
|
||||
# The field is a connection or a list of non-built-in types, we'll
|
||||
# authorize each element when rendering
|
||||
nil
|
||||
elsif resolved_type.respond_to?(:object)
|
||||
# The field is a type representing a single object, we'll authorize
|
||||
# against the object directly
|
||||
resolved_type.object
|
||||
else
|
||||
# Resolved type is a single object that might not be loaded yet by
|
||||
# the batchloader, we'll authorize that
|
||||
|
|
|
@ -29,19 +29,25 @@ module Gitlab
|
|||
|
||||
def authorized_find!(*args)
|
||||
object = find_object(*args)
|
||||
object = object.sync if object.respond_to?(:sync)
|
||||
|
||||
authorize!(object)
|
||||
|
||||
object
|
||||
end
|
||||
|
||||
def authorize!(object)
|
||||
unless authorized?(object)
|
||||
unless authorized_resource?(object)
|
||||
raise Gitlab::Graphql::Errors::ResourceNotAvailable,
|
||||
"The resource that you are attempting to access does not exist or you don't have permission to perform this action"
|
||||
end
|
||||
end
|
||||
|
||||
def authorized?(object)
|
||||
# this was named `#authorized?`, however it conflicts with the native
|
||||
# graphql gem version
|
||||
# TODO consider adopting the gem's built in authorization system
|
||||
# https://gitlab.com/gitlab-org/gitlab-ee/issues/13984
|
||||
def authorized_resource?(object)
|
||||
# Sanity check. We don't want to accidentally allow a developer to authorize
|
||||
# without first adding permissions to authorize against
|
||||
if self.class.required_permissions.empty?
|
||||
|
|
|
@ -9,7 +9,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def find
|
||||
BatchLoader.for(blob_id).batch(key: repository) do |blob_ids, loader, batch_args|
|
||||
BatchLoader::GraphQL.for(blob_id).batch(key: repository) do |blob_ids, loader, batch_args|
|
||||
Gitlab::Git::Blob.batch_lfs_pointers(batch_args[:key], blob_ids).each do |loaded_blob|
|
||||
loader.call(loaded_blob.id, loaded_blob.lfs_oid)
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ module Gitlab
|
|||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def find
|
||||
BatchLoader.for({ model: model_class, id: model_id.to_i }).batch do |loader_info, loader|
|
||||
BatchLoader::GraphQL.for({ model: model_class, id: model_id.to_i }).batch do |loader_info, loader|
|
||||
per_model = loader_info.group_by { |info| info[:model] }
|
||||
per_model.each do |model, info|
|
||||
ids = info.map { |i| i[:id] }
|
||||
|
|
|
@ -11,7 +11,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def find
|
||||
BatchLoader.for(project_id).batch do |project_ids, loader|
|
||||
BatchLoader::GraphQL.for(project_id).batch do |project_ids, loader|
|
||||
ProjectStatistics.for_project_ids(project_ids).each do |statistics|
|
||||
loader.call(statistics.project_id, statistics)
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def find
|
||||
BatchLoader.for(namespace_id).batch do |namespace_ids, loader|
|
||||
BatchLoader::GraphQL.for(namespace_id).batch do |namespace_ids, loader|
|
||||
Namespace::RootStorageStatistics.for_namespace_ids(namespace_ids).each do |statistics|
|
||||
loader.call(statistics.namespace_id, statistics)
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def find_last
|
||||
BatchLoader.for(sha).batch(key: project) do |shas, loader, args|
|
||||
BatchLoader::GraphQL.for(sha).batch(key: project) do |shas, loader, args|
|
||||
pipelines = args[:key].ci_pipelines.latest_for_shas(shas)
|
||||
|
||||
pipelines.each do |pipeline|
|
||||
|
|
|
@ -129,7 +129,7 @@ describe GitlabSchema do
|
|||
|
||||
result = described_class.object_from_id(user.to_global_id.to_s)
|
||||
|
||||
expect(result.__sync).to eq(user)
|
||||
expect(result.sync).to eq(user)
|
||||
end
|
||||
|
||||
it 'batchloads the queries' do
|
||||
|
@ -138,7 +138,7 @@ describe GitlabSchema do
|
|||
|
||||
expect do
|
||||
[described_class.object_from_id(user1.to_global_id),
|
||||
described_class.object_from_id(user2.to_global_id)].map(&:__sync)
|
||||
described_class.object_from_id(user2.to_global_id)].map(&:sync)
|
||||
end.not_to exceed_query_limit(1)
|
||||
end
|
||||
end
|
||||
|
@ -149,7 +149,7 @@ describe GitlabSchema do
|
|||
|
||||
result = described_class.object_from_id(note.to_global_id)
|
||||
|
||||
expect(result.__sync).to eq(note)
|
||||
expect(result.sync).to eq(note)
|
||||
end
|
||||
|
||||
it 'batchloads the queries' do
|
||||
|
@ -158,7 +158,7 @@ describe GitlabSchema do
|
|||
|
||||
expect do
|
||||
[described_class.object_from_id(note1.to_global_id),
|
||||
described_class.object_from_id(note2.to_global_id)].map(&:__sync)
|
||||
described_class.object_from_id(note2.to_global_id)].map(&:sync)
|
||||
end.not_to exceed_query_limit(1)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,6 +14,6 @@ describe Mutations::ResolvesProject do
|
|||
project = create(:project)
|
||||
|
||||
expect(Resolvers::ProjectResolver).to receive(:new).with(object: nil, context: context).and_call_original
|
||||
expect(mutation.resolve_project(full_path: project.full_path)).to eq(project)
|
||||
expect(mutation.resolve_project(full_path: project.full_path).sync).to eq(project)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ describe Resolvers::GroupResolver do
|
|||
it 'batch-resolves groups by full path' do
|
||||
paths = [group1.full_path, group2.full_path]
|
||||
|
||||
result = batch(max_queries: 1) do
|
||||
result = batch_sync(max_queries: 1) do
|
||||
paths.map { |path| resolve_group(path) }
|
||||
end
|
||||
|
||||
|
@ -20,7 +20,7 @@ describe Resolvers::GroupResolver do
|
|||
end
|
||||
|
||||
it 'resolves an unknown full_path to nil' do
|
||||
result = batch { resolve_group('unknown/project') }
|
||||
result = batch_sync { resolve_group('unknown/project') }
|
||||
|
||||
expect(result).to be_nil
|
||||
end
|
||||
|
|
|
@ -110,7 +110,7 @@ describe Resolvers::IssuesResolver do
|
|||
|
||||
context "when passing a non existent, batch loaded project" do
|
||||
let(:project) do
|
||||
BatchLoader.for("non-existent-path").batch do |_fake_paths, loader, _|
|
||||
BatchLoader::GraphQL.for("non-existent-path").batch do |_fake_paths, loader, _|
|
||||
loader.call("non-existent-path", nil)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,7 +17,7 @@ describe Resolvers::MergeRequestsResolver do
|
|||
|
||||
describe '#resolve' do
|
||||
it 'batch-resolves by target project full path and individual IID' do
|
||||
result = batch(max_queries: 2) do
|
||||
result = batch_sync(max_queries: 2) do
|
||||
resolve_mr(project, iid: iid_1) + resolve_mr(project, iid: iid_2)
|
||||
end
|
||||
|
||||
|
@ -25,7 +25,7 @@ describe Resolvers::MergeRequestsResolver do
|
|||
end
|
||||
|
||||
it 'batch-resolves by target project full path and IIDS' do
|
||||
result = batch(max_queries: 2) do
|
||||
result = batch_sync(max_queries: 2) do
|
||||
resolve_mr(project, iids: [iid_1, iid_2])
|
||||
end
|
||||
|
||||
|
@ -33,7 +33,7 @@ describe Resolvers::MergeRequestsResolver do
|
|||
end
|
||||
|
||||
it 'can batch-resolve merge requests from different projects' do
|
||||
result = batch(max_queries: 3) do
|
||||
result = batch_sync(max_queries: 3) do
|
||||
resolve_mr(project, iid: iid_1) +
|
||||
resolve_mr(project, iid: iid_2) +
|
||||
resolve_mr(other_project, iid: other_iid)
|
||||
|
@ -43,13 +43,13 @@ describe Resolvers::MergeRequestsResolver do
|
|||
end
|
||||
|
||||
it 'resolves an unknown iid to be empty' do
|
||||
result = batch { resolve_mr(project, iid: -1) }
|
||||
result = batch_sync { resolve_mr(project, iid: -1) }
|
||||
|
||||
expect(result).to be_empty
|
||||
expect(result.compact).to be_empty
|
||||
end
|
||||
|
||||
it 'resolves empty iids to be empty' do
|
||||
result = batch { resolve_mr(project, iids: []) }
|
||||
result = batch_sync { resolve_mr(project, iids: []) }
|
||||
|
||||
expect(result).to be_empty
|
||||
end
|
||||
|
|
|
@ -46,7 +46,7 @@ describe Resolvers::NamespaceProjectsResolver do
|
|||
|
||||
context "when passing a non existent, batch loaded namespace" do
|
||||
let(:namespace) do
|
||||
BatchLoader.for("non-existent-path").batch do |_fake_paths, loader, _|
|
||||
BatchLoader::GraphQL.for("non-existent-path").batch do |_fake_paths, loader, _|
|
||||
loader.call("non-existent-path", nil)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ describe Resolvers::ProjectResolver do
|
|||
it 'batch-resolves projects by full path' do
|
||||
paths = [project1.full_path, project2.full_path]
|
||||
|
||||
result = batch(max_queries: 1) do
|
||||
result = batch_sync(max_queries: 1) do
|
||||
paths.map { |path| resolve_project(path) }
|
||||
end
|
||||
|
||||
|
@ -20,7 +20,7 @@ describe Resolvers::ProjectResolver do
|
|||
end
|
||||
|
||||
it 'resolves an unknown full_path to nil' do
|
||||
result = batch { resolve_project('unknown/project') }
|
||||
result = batch_sync { resolve_project('unknown/project') }
|
||||
|
||||
expect(result).to be_nil
|
||||
end
|
||||
|
|
|
@ -46,9 +46,9 @@ describe Gitlab::Graphql::Authorize::AuthorizeResource do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#authorized?' do
|
||||
describe '#authorized_resource?' do
|
||||
it 'is true' do
|
||||
expect(loading_resource.authorized?(project)).to be(true)
|
||||
expect(loading_resource.authorized_resource?(project)).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -72,9 +72,9 @@ describe Gitlab::Graphql::Authorize::AuthorizeResource do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#authorized?' do
|
||||
describe '#authorized_resource?' do
|
||||
it 'is false' do
|
||||
expect(loading_resource.authorized?(project)).to be(false)
|
||||
expect(loading_resource.authorized_resource?(project)).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -121,9 +121,9 @@ describe Gitlab::Graphql::Authorize::AuthorizeResource do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#authorized?' do
|
||||
describe '#authorized_resource?' do
|
||||
it 'raises a comprehensive error message' do
|
||||
expect { loading_resource.authorized?(project) }.to raise_error(error)
|
||||
expect { loading_resource.authorized_resource?(project) }.to raise_error(error)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ describe Gitlab::Graphql::Loaders::BatchLfsOidLoader do
|
|||
it 'batch-resolves LFS blob IDs' do
|
||||
expect(Gitlab::Git::Blob).to receive(:batch_lfs_pointers).once.and_call_original
|
||||
|
||||
result = batch do
|
||||
result = batch_sync do
|
||||
[blob, otherblob].map { |b| described_class.new(repository, b.id).find }
|
||||
end
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@ describe Gitlab::Graphql::Loaders::BatchModelLoader do
|
|||
issue_result = described_class.new(Issue, issue.id).find
|
||||
user_result = described_class.new(User, user.id).find
|
||||
|
||||
expect(issue_result.__sync).to eq(issue)
|
||||
expect(user_result.__sync).to eq(user)
|
||||
expect(issue_result.sync).to eq(issue)
|
||||
expect(user_result.sync).to eq(user)
|
||||
end
|
||||
|
||||
it 'only queries once per model' do
|
||||
|
@ -21,7 +21,7 @@ describe Gitlab::Graphql::Loaders::BatchModelLoader do
|
|||
expect do
|
||||
[described_class.new(User, other_user.id).find,
|
||||
described_class.new(User, user.id).find,
|
||||
described_class.new(Issue, issue.id).find].map(&:__sync)
|
||||
described_class.new(Issue, issue.id).find].map(&:sync)
|
||||
end.not_to exceed_query_limit(2)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ describe Gitlab::Graphql::Loaders::PipelineForShaLoader do
|
|||
pipeline2 = create(:ci_pipeline, project: project, ref: project.default_branch, sha: project.commit.sha)
|
||||
pipeline3 = create(:ci_pipeline, project: project, ref: 'improve/awesome', sha: project.commit('improve/awesome').sha)
|
||||
|
||||
result = batch(max_queries: 1) do
|
||||
result = batch_sync(max_queries: 1) do
|
||||
[pipeline1.sha, pipeline3.sha].map { |sha| described_class.new(project, sha).find_last }
|
||||
end
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ describe 'Setting WIP status of a merge request' do
|
|||
project_path: project.full_path,
|
||||
iid: merge_request.iid.to_s
|
||||
}
|
||||
graphql_mutation(:merge_request_set_wip, variables.merge(input))
|
||||
graphql_mutation(:merge_request_set_wip, variables.merge(input), "clientMutationId\nerrors\nmergeRequest { id\ntitle }")
|
||||
end
|
||||
|
||||
def mutation_response
|
||||
|
|
|
@ -34,6 +34,14 @@ module GraphqlHelpers
|
|||
end
|
||||
end
|
||||
|
||||
# BatchLoader::GraphQL returns a wrapper, so we need to :sync in order
|
||||
# to get the actual values
|
||||
def batch_sync(max_queries: nil, &blk)
|
||||
result = batch(max_queries: nil, &blk)
|
||||
|
||||
result.is_a?(Array) ? result.map(&:sync) : result&.sync
|
||||
end
|
||||
|
||||
def graphql_query_for(name, attributes = {}, fields = nil)
|
||||
<<~QUERY
|
||||
{
|
||||
|
@ -114,7 +122,11 @@ module GraphqlHelpers
|
|||
FIELDS
|
||||
end
|
||||
|
||||
def all_graphql_fields_for(class_name, parent_types = Set.new)
|
||||
def all_graphql_fields_for(class_name, parent_types = Set.new, max_depth: 3)
|
||||
# pulling _all_ fields can generate a _huge_ query (like complexity 180,000),
|
||||
# and significantly increase spec runtime. so limit the depth by default
|
||||
return if max_depth <= 0
|
||||
|
||||
allow_unlimited_graphql_complexity
|
||||
allow_unlimited_graphql_depth
|
||||
|
||||
|
@ -133,9 +145,9 @@ module GraphqlHelpers
|
|||
|
||||
if nested_fields?(field)
|
||||
fields =
|
||||
all_graphql_fields_for(singular_field_type, parent_types | [type])
|
||||
all_graphql_fields_for(singular_field_type, parent_types | [type], max_depth: max_depth - 1)
|
||||
|
||||
"#{name} { #{fields} }"
|
||||
"#{name} { #{fields} }" unless fields.blank?
|
||||
else
|
||||
name
|
||||
end
|
||||
|
|
|
@ -46,7 +46,7 @@ shared_context 'exposing regular notes on a noteable in GraphQL' do
|
|||
discussions {
|
||||
edges {
|
||||
node {
|
||||
#{all_graphql_fields_for('Discussion')}
|
||||
#{all_graphql_fields_for('Discussion', max_depth: 4)}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue