Commit 9318fba9 authored by Jan Provaznik's avatar Jan Provaznik

Merge branch '62706-graphql-complexity-values-are-incorrectly-doubled' into 'master'

Reduce GraphQL complexity for non-connection fields

Closes #62706

See merge request gitlab-org/gitlab-ce!29165
parents 102c0e81 0cedd43e
...@@ -29,15 +29,18 @@ module Types ...@@ -29,15 +29,18 @@ module Types
# proc because we set complexity depending on arguments and number of # proc because we set complexity depending on arguments and number of
# items which can be loaded. # items which can be loaded.
proc do |ctx, args, child_complexity| proc do |ctx, args, child_complexity|
page_size = @max_page_size || ctx.schema.default_max_page_size
limit_value = [args[:first], args[:last], page_size].compact.min
# Resolvers may add extra complexity depending on used arguments # Resolvers may add extra complexity depending on used arguments
complexity = child_complexity + self.resolver&.try(:resolver_complexity, args, child_complexity: child_complexity).to_i complexity = child_complexity + self.resolver&.try(:resolver_complexity, args, child_complexity: child_complexity).to_i
field_defn = to_graphql
if field_defn.connection?
# Resolvers may add extra complexity depending on number of items being loaded. # Resolvers may add extra complexity depending on number of items being loaded.
page_size = field_defn.connection_max_page_size || ctx.schema.default_max_page_size
limit_value = [args[:first], args[:last], page_size].compact.min
multiplier = self.resolver&.try(:complexity_multiplier, args).to_f multiplier = self.resolver&.try(:complexity_multiplier, args).to_f
complexity += complexity * limit_value * multiplier complexity += complexity * limit_value * multiplier
end
complexity.to_i complexity.to_i
end end
......
...@@ -29,18 +29,20 @@ describe Resolvers::BaseResolver do ...@@ -29,18 +29,20 @@ describe Resolvers::BaseResolver do
end end
end end
context 'when field is a connection' do
it 'increases complexity based on arguments' do it 'increases complexity based on arguments' do
field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 1) field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 1)
expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 3 expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 3
expect(field.to_graphql.complexity.call({}, { search: 'foo' }, 1)).to eq 7 expect(field.to_graphql.complexity.call({}, { search: 'foo' }, 1)).to eq 7
end end
it 'does not increase complexity when filtering by iids' do it 'does not increase complexity when filtering by iids' do
field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 100) field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 100)
expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 6 expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 6
expect(field.to_graphql.complexity.call({}, { sort: 'foo', iid: 1 }, 1)).to eq 3 expect(field.to_graphql.complexity.call({}, { sort: 'foo', iid: 1 }, 1)).to eq 3
expect(field.to_graphql.complexity.call({}, { sort: 'foo', iids: [1, 2, 3] }, 1)).to eq 3 expect(field.to_graphql.complexity.call({}, { sort: 'foo', iids: [1, 2, 3] }, 1)).to eq 3
end end
end
end end
...@@ -121,7 +121,7 @@ describe Resolvers::IssuesResolver do ...@@ -121,7 +121,7 @@ describe Resolvers::IssuesResolver do
end end
it 'increases field complexity based on arguments' do it 'increases field complexity based on arguments' do
field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 100) field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 100)
expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 4 expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 4
expect(field.to_graphql.complexity.call({}, { labelName: 'foo' }, 1)).to eq 8 expect(field.to_graphql.complexity.call({}, { labelName: 'foo' }, 1)).to eq 8
......
...@@ -57,7 +57,7 @@ describe Resolvers::NamespaceProjectsResolver, :nested_groups do ...@@ -57,7 +57,7 @@ describe Resolvers::NamespaceProjectsResolver, :nested_groups do
end end
it 'has an high complexity regardless of arguments' do it 'has an high complexity regardless of arguments' do
field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 100) field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 100)
expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 24 expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 24
expect(field.to_graphql.complexity.call({}, { include_subgroups: true }, 1)).to eq 24 expect(field.to_graphql.complexity.call({}, { include_subgroups: true }, 1)).to eq 24
......
...@@ -28,18 +28,29 @@ describe Types::BaseField do ...@@ -28,18 +28,29 @@ describe Types::BaseField do
expect(field.to_graphql.complexity).to eq 12 expect(field.to_graphql.complexity).to eq 12
end end
it 'sets complexity depending on arguments for resolvers' do context 'when field has a resolver proc' do
field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, max_page_size: 100, null: true) context 'and is a connection' do
let(:field) { described_class.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: resolver, max_page_size: 100, null: true) }
it 'sets complexity depending on arguments for resolvers' do
expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 4 expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 4
expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 3 expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 3
end end
it 'sets complexity depending on number load limits for resolvers' do it 'sets complexity depending on number load limits for resolvers' do
field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, max_page_size: 100, null: true)
expect(field.to_graphql.complexity.call({}, { first: 1 }, 2)).to eq 2 expect(field.to_graphql.complexity.call({}, { first: 1 }, 2)).to eq 2
expect(field.to_graphql.complexity.call({}, { first: 1, foo: true }, 2)).to eq 4 expect(field.to_graphql.complexity.call({}, { first: 1, foo: true }, 2)).to eq 4
end end
end end
context 'and is not a connection' do
it 'sets complexity as normal' do
field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, max_page_size: 100, null: true)
expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 2
expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 2
end
end
end
end
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