Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-06-18 14:18:50 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-06-18 14:18:50 +0300
commit8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781 (patch)
treea77e7fe7a93de11213032ed4ab1f33a3db51b738 /lib/gitlab/graphql
parent00b35af3db1abfe813a778f643dad221aad51fca (diff)
Add latest changes from gitlab-org/gitlab@13-1-stable-ee
Diffstat (limited to 'lib/gitlab/graphql')
-rw-r--r--lib/gitlab/graphql/authorize/authorize_field_service.rb14
-rw-r--r--lib/gitlab/graphql/authorize/instrumentation.rb6
-rw-r--r--lib/gitlab/graphql/filterable_array.rb14
-rw-r--r--lib/gitlab/graphql/loaders/full_path_model_loader.rb26
-rw-r--r--lib/gitlab/graphql/pagination/connections.rb4
-rw-r--r--lib/gitlab/graphql/pagination/filterable_array_connection.rb17
-rw-r--r--lib/gitlab/graphql/pagination/keyset/connection.rb100
7 files changed, 129 insertions, 52 deletions
diff --git a/lib/gitlab/graphql/authorize/authorize_field_service.rb b/lib/gitlab/graphql/authorize/authorize_field_service.rb
index 61668b634fd..cbf3e7b8429 100644
--- a/lib/gitlab/graphql/authorize/authorize_field_service.rb
+++ b/lib/gitlab/graphql/authorize/authorize_field_service.rb
@@ -84,13 +84,25 @@ module Gitlab
elsif resolved_type.is_a? Array
# A simple list of rendered types each object being an object to authorize
resolved_type.select do |single_object_type|
- allowed_access?(current_user, single_object_type.object)
+ allowed_access?(current_user, realized(single_object_type).object)
end
else
raise "Can't authorize #{@field}"
end
end
+ # Ensure that we are dealing with realized objects, not delayed promises
+ def realized(thing)
+ case thing
+ when BatchLoader::GraphQL
+ thing.sync
+ when GraphQL::Execution::Lazy
+ thing.value # part of the private api, but we need to unwrap it here.
+ else
+ thing
+ end
+ end
+
def allowed_access?(current_user, object)
object = object.sync if object.respond_to?(:sync)
diff --git a/lib/gitlab/graphql/authorize/instrumentation.rb b/lib/gitlab/graphql/authorize/instrumentation.rb
index f9ff2b30eae..15ecc3b04f0 100644
--- a/lib/gitlab/graphql/authorize/instrumentation.rb
+++ b/lib/gitlab/graphql/authorize/instrumentation.rb
@@ -9,16 +9,12 @@ module Gitlab
def instrument(_type, field)
service = AuthorizeFieldService.new(field)
- if service.authorizations? && !resolver_skips_authorizations?(field)
+ if service.authorizations?
field.redefine { resolve(service.authorized_resolve) }
else
field
end
end
-
- def resolver_skips_authorizations?(field)
- field.metadata[:resolver].try(:skip_authorizations?)
- end
end
end
end
diff --git a/lib/gitlab/graphql/filterable_array.rb b/lib/gitlab/graphql/filterable_array.rb
deleted file mode 100644
index 4909d291fd6..00000000000
--- a/lib/gitlab/graphql/filterable_array.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Graphql
- class FilterableArray < Array
- attr_reader :filter_callback
-
- def initialize(filter_callback, *args)
- super(args)
- @filter_callback = filter_callback
- end
- end
- end
-end
diff --git a/lib/gitlab/graphql/loaders/full_path_model_loader.rb b/lib/gitlab/graphql/loaders/full_path_model_loader.rb
new file mode 100644
index 00000000000..0aa237c78de
--- /dev/null
+++ b/lib/gitlab/graphql/loaders/full_path_model_loader.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module Loaders
+ # Suitable for use to find resources that expose `where_full_path_in`,
+ # such as Project, Group, Namespace
+ class FullPathModelLoader
+ attr_reader :model_class, :full_path
+
+ def initialize(model_class, full_path)
+ @model_class, @full_path = model_class, full_path
+ end
+
+ def find
+ BatchLoader::GraphQL.for(full_path).batch(key: model_class) 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)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql/pagination/connections.rb b/lib/gitlab/graphql/pagination/connections.rb
index febdc938317..8f37fa3f474 100644
--- a/lib/gitlab/graphql/pagination/connections.rb
+++ b/lib/gitlab/graphql/pagination/connections.rb
@@ -10,10 +10,6 @@ module Gitlab
Gitlab::Graphql::Pagination::Keyset::Connection)
schema.connections.add(
- Gitlab::Graphql::FilterableArray,
- Gitlab::Graphql::Pagination::FilterableArrayConnection)
-
- schema.connections.add(
Gitlab::Graphql::ExternallyPaginatedArray,
Gitlab::Graphql::Pagination::ExternallyPaginatedArrayConnection)
end
diff --git a/lib/gitlab/graphql/pagination/filterable_array_connection.rb b/lib/gitlab/graphql/pagination/filterable_array_connection.rb
deleted file mode 100644
index 4a76cd5fb00..00000000000
--- a/lib/gitlab/graphql/pagination/filterable_array_connection.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Graphql
- module Pagination
- # FilterableArrayConnection is useful especially for lazy-loaded values.
- # It allows us to call a callback only on the slice of array being
- # rendered in the "after loaded" phase. For example we can check
- # permissions only on a small subset of items.
- class FilterableArrayConnection < GraphQL::Pagination::ArrayConnection
- def nodes
- @nodes ||= items.filter_callback.call(super)
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/graphql/pagination/keyset/connection.rb b/lib/gitlab/graphql/pagination/keyset/connection.rb
index 1a32ab468b1..17cd22d38ad 100644
--- a/lib/gitlab/graphql/pagination/keyset/connection.rb
+++ b/lib/gitlab/graphql/pagination/keyset/connection.rb
@@ -32,6 +32,49 @@ module Gitlab
class Connection < GraphQL::Pagination::ActiveRecordRelationConnection
include Gitlab::Utils::StrongMemoize
+ # rubocop: disable Naming/PredicateName
+ # https://relay.dev/graphql/connections.htm#sec-undefined.PageInfo.Fields
+ def has_previous_page
+ strong_memoize(:has_previous_page) do
+ if after
+ # If `after` is specified, that points to a specific record,
+ # even if it's the first one. Since we're asking for `after`,
+ # then the specific record we're pointing to is in the
+ # previous page
+ true
+ elsif last
+ limited_nodes
+ !!@has_previous_page
+ else
+ # Key thing to remember. When `before` is specified (and no `last`),
+ # the spec says return _all_ edges minus anything after the `before`.
+ # Which means the returned list starts at the very first record.
+ # Then the max_page kicks in, and returns the first max_page items.
+ # Because of this, `has_previous_page` will be false
+ false
+ end
+ end
+ end
+
+ def has_next_page
+ strong_memoize(:has_next_page) do
+ if before
+ # If `before` is specified, that points to a specific record,
+ # even if it's the last one. Since we're asking for `before`,
+ # then the specific record we're pointing to is in the
+ # next page
+ true
+ elsif first
+ # If we count the number of requested items plus one (`limit_value + 1`),
+ # then if we get `limit_value + 1` then we know there is a next page
+ relation_count(set_limit(sliced_nodes, limit_value + 1)) == limit_value + 1
+ else
+ false
+ end
+ end
+ end
+ # rubocop: enable Naming/PredicateName
+
def cursor_for(node)
encoded_json_from_ordering(node)
end
@@ -39,7 +82,7 @@ module Gitlab
def sliced_nodes
@sliced_nodes ||=
begin
- OrderInfo.validate_ordering(ordered_items, order_list)
+ OrderInfo.validate_ordering(ordered_items, order_list) unless loaded?(ordered_items)
sliced = ordered_items
sliced = slice_nodes(sliced, before, :before) if before.present?
@@ -54,20 +97,30 @@ module Gitlab
# So we're ok loading them into memory here as that's bound to happen
# anyway. Having them ready means we can modify the result while
# rendering the fields.
- @nodes ||= load_paged_nodes.to_a
+ @nodes ||= limited_nodes.to_a
end
private
- def load_paged_nodes
- if first && last
- raise Gitlab::Graphql::Errors::ArgumentError.new("Can only provide either `first` or `last`, not both")
- end
+ # Apply `first` and `last` to `sliced_nodes`
+ def limited_nodes
+ strong_memoize(:limited_nodes) do
+ if first && last
+ raise Gitlab::Graphql::Errors::ArgumentError.new("Can only provide either `first` or `last`, not both")
+ end
- if last
- sliced_nodes.last(limit_value)
- else
- sliced_nodes.limit(limit_value) # rubocop: disable CodeReuse/ActiveRecord
+ if last
+ # grab one more than we need
+ paginated_nodes = sliced_nodes.last(limit_value + 1)
+
+ # there is an extra node, so there is a previous page
+ @has_previous_page = paginated_nodes.count > limit_value
+ @has_previous_page ? paginated_nodes.last(limit_value) : paginated_nodes
+ elsif loaded?(sliced_nodes)
+ sliced_nodes.take(limit_value) # rubocop: disable CodeReuse/ActiveRecord
+ else
+ sliced_nodes.limit(limit_value) # rubocop: disable CodeReuse/ActiveRecord
+ end
end
end
@@ -82,9 +135,19 @@ module Gitlab
# rubocop: enable CodeReuse/ActiveRecord
def limit_value
+ # note: only first _or_ last can be specified, not both
@limit_value ||= [first, last, max_page_size].compact.min
end
+ def loaded?(items)
+ case items
+ when Array
+ true
+ else
+ items.loaded?
+ end
+ end
+
def ordered_items
strong_memoize(:ordered_items) do
unless items.primary_key.present?
@@ -93,6 +156,16 @@ module Gitlab
list = OrderInfo.build_order_list(items)
+ if loaded?(items)
+ @order_list = list.presence || [items.primary_key]
+
+ # already sorted, or trivially sorted
+ next items if list.present? || items.size <= 1
+
+ pkey = items.primary_key.to_sym
+ next items.sort_by { |item| item[pkey] }.reverse
+ end
+
# ensure there is a primary key ordering
if list&.last&.attribute_name != items.primary_key
items.order(arel_table[items.primary_key].desc) # rubocop: disable CodeReuse/ActiveRecord
@@ -121,7 +194,12 @@ module Gitlab
order_list.each do |field|
field_name = field.attribute_name
- ordering[field_name] = node[field_name].to_s
+ field_value = node[field_name]
+ ordering[field_name] = if field_value.is_a?(Time)
+ field_value.strftime('%Y-%m-%d %H:%M:%S.%N %Z')
+ else
+ field_value.to_s
+ end
end
encode(ordering.to_json)