Commit 3d29bff4 authored by Brett Walker's avatar Brett Walker

Add feature flag :graphql_keyset_pagination

To allow falling back to old keyset
pagination
parent 1c508188
...@@ -27,11 +27,18 @@ module Gitlab ...@@ -27,11 +27,18 @@ module Gitlab
class Connection < GraphQL::Relay::BaseConnection class Connection < GraphQL::Relay::BaseConnection
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
# TODO https://gitlab.com/gitlab-org/gitlab/issues/35104
include Gitlab::Graphql::Connections::Keyset::LegacyKeysetConnection
def cursor_from_node(node) def cursor_from_node(node)
return legacy_cursor_from_node(node) if use_legacy_pagination?
encoded_json_from_ordering(node) encoded_json_from_ordering(node)
end end
def sliced_nodes def sliced_nodes
return legacy_sliced_nodes if use_legacy_pagination?
@sliced_nodes ||= @sliced_nodes ||=
begin begin
OrderInfo.validate_ordering(ordered_nodes, order_list) OrderInfo.validate_ordering(ordered_nodes, order_list)
......
# frozen_string_literal: true
# TODO https://gitlab.com/gitlab-org/gitlab/issues/35104
module Gitlab
module Graphql
module Connections
module Keyset
module LegacyKeysetConnection
def legacy_cursor_from_node(node)
encode(node[legacy_order_field].to_s)
end
# rubocop: disable CodeReuse/ActiveRecord
def legacy_sliced_nodes
@sliced_nodes ||=
begin
sliced = nodes
sliced = sliced.where(legacy_before_slice) if before.present?
sliced = sliced.where(legacy_after_slice) if after.present?
sliced
end
end
# rubocop: enable CodeReuse/ActiveRecord
private
def use_legacy_pagination?
strong_memoize(:feature_disabled) do
Feature.disabled?(:graphql_keyset_pagination, default_enabled: true)
end
end
def legacy_before_slice
if legacy_sort_direction == :asc
arel_table[legacy_order_field].lt(decode(before))
else
arel_table[legacy_order_field].gt(decode(before))
end
end
def legacy_after_slice
if legacy_sort_direction == :asc
arel_table[legacy_order_field].gt(decode(after))
else
arel_table[legacy_order_field].lt(decode(after))
end
end
def legacy_order_info
@legacy_order_info ||= nodes.order_values.first
end
def legacy_order_field
@legacy_order_field ||= legacy_order_info&.expr&.name || nodes.primary_key
end
def legacy_sort_direction
@legacy_order_direction ||= legacy_order_info&.direction || :desc
end
end
end
end
end
end
# frozen_string_literal: true
# TODO https://gitlab.com/gitlab-org/gitlab/issues/35104
require 'spec_helper'
describe Gitlab::Graphql::Connections::Keyset::LegacyKeysetConnection do
describe 'old keyset_connection' do
let(:described_class) { Gitlab::Graphql::Connections::Keyset::Connection }
let(:nodes) { Project.all.order(id: :asc) }
let(:arguments) { {} }
subject(:connection) do
described_class.new(nodes, arguments, max_page_size: 3)
end
before do
stub_feature_flags(graphql_keyset_pagination: false)
end
def encoded_property(value)
Base64Bp.urlsafe_encode64(value.to_s, padding: false)
end
describe '#cursor_from_nodes' do
let(:project) { create(:project) }
it 'returns an encoded ID' do
expect(connection.cursor_from_node(project))
.to eq(encoded_property(project.id))
end
context 'when an order was specified' do
let(:nodes) { Project.order(:updated_at) }
it 'returns the encoded value of the order' do
expect(connection.cursor_from_node(project))
.to eq(encoded_property(project.updated_at))
end
end
end
describe '#sliced_nodes' do
let(:projects) { create_list(:project, 4) }
context 'when before is passed' do
let(:arguments) { { before: encoded_property(projects[1].id) } }
it 'only returns the project before the selected one' do
expect(subject.sliced_nodes).to contain_exactly(projects.first)
end
context 'when the sort order is descending' do
let(:nodes) { Project.all.order(id: :desc) }
it 'returns the correct nodes' do
expect(subject.sliced_nodes).to contain_exactly(*projects[2..-1])
end
end
end
context 'when after is passed' do
let(:arguments) { { after: encoded_property(projects[1].id) } }
it 'only returns the project before the selected one' do
expect(subject.sliced_nodes).to contain_exactly(*projects[2..-1])
end
context 'when the sort order is descending' do
let(:nodes) { Project.all.order(id: :desc) }
it 'returns the correct nodes' do
expect(subject.sliced_nodes).to contain_exactly(projects.first)
end
end
end
context 'when both before and after are passed' do
let(:arguments) do
{
after: encoded_property(projects[1].id),
before: encoded_property(projects[3].id)
}
end
it 'returns the expected set' do
expect(subject.sliced_nodes).to contain_exactly(projects[2])
end
end
end
describe '#paged_nodes' do
let!(:projects) { create_list(:project, 5) }
it 'returns the collection limited to max page size' do
expect(subject.paged_nodes.size).to eq(3)
end
it 'is a loaded memoized array' do
expect(subject.paged_nodes).to be_an(Array)
expect(subject.paged_nodes.object_id).to eq(subject.paged_nodes.object_id)
end
context 'when `first` is passed' do
let(:arguments) { { first: 2 } }
it 'returns only the first elements' do
expect(subject.paged_nodes).to contain_exactly(projects.first, projects.second)
end
end
context 'when `last` is passed' do
let(:arguments) { { last: 2 } }
it 'returns only the last elements' do
expect(subject.paged_nodes).to contain_exactly(projects[3], projects[4])
end
end
context 'when both are passed' do
let(:arguments) { { first: 2, last: 2 } }
it 'raises an error' do
expect { subject.paged_nodes }.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
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