Commit 875071dd authored by Bob Van Landuyt's avatar Bob Van Landuyt

Merge branch 'ajk-13984-connections' into 'master'

Add connection redaction and connection extension methods

See merge request gitlab-org/gitlab!47967
parents bbfddf81 5c89f5e6
# frozen_string_literal: true
module Gitlab
module Graphql
module ConnectionCollectionMethods
extend ActiveSupport::Concern
included do
delegate :to_a, :size, :include?, :empty?, to: :nodes
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Graphql
module ConnectionRedaction
class RedactionState
attr_reader :redactor
attr_reader :redacted_nodes
def redactor=(redactor)
@redactor = redactor
@redacted_nodes = nil
end
def redacted(&block)
@redacted_nodes ||= redactor.present? ? redactor.redact(yield) : yield
end
end
delegate :redactor=, to: :redaction_state
def nodes
redaction_state.redacted { super.to_a }
end
private
def redaction_state
@redaction_state ||= RedactionState.new
end
end
end
end
# frozen_string_literal: true
# We use the Keyset / Stable cursor connection by default for ActiveRecord::Relation.
# However, there are times when that may not be powerful enough (yet), and we
# want to use standard offset pagination.
module Gitlab
module Graphql
module Pagination
class ArrayConnection < ::GraphQL::Pagination::ArrayConnection
prepend ::Gitlab::Graphql::ConnectionRedaction
include ::Gitlab::Graphql::ConnectionCollectionMethods
end
end
end
end
......@@ -12,6 +12,10 @@ module Gitlab
schema.connections.add(
Gitlab::Graphql::ExternallyPaginatedArray,
Gitlab::Graphql::Pagination::ExternallyPaginatedArrayConnection)
schema.connections.add(
Array,
Gitlab::Graphql::Pagination::ArrayConnection)
end
end
end
......
......@@ -5,6 +5,9 @@ module Gitlab
module Graphql
module Pagination
class ExternallyPaginatedArrayConnection < GraphQL::Pagination::ArrayConnection
include ::Gitlab::Graphql::ConnectionCollectionMethods
prepend ::Gitlab::Graphql::ConnectionRedaction
def start_cursor
items.previous_cursor
end
......
......@@ -31,6 +31,8 @@ module Gitlab
module Keyset
class Connection < GraphQL::Pagination::ActiveRecordRelationConnection
include Gitlab::Utils::StrongMemoize
include ::Gitlab::Graphql::ConnectionCollectionMethods
prepend ::Gitlab::Graphql::ConnectionRedaction
# rubocop: disable Naming/PredicateName
# https://relay.dev/graphql/connections.htm#sec-undefined.PageInfo.Fields
......
......@@ -7,6 +7,8 @@ module Gitlab
module Graphql
module Pagination
class OffsetActiveRecordRelationConnection < GraphQL::Pagination::ActiveRecordRelationConnection
prepend ::Gitlab::Graphql::ConnectionRedaction
include ::Gitlab::Graphql::ConnectionCollectionMethods
end
end
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Gitlab::Graphql::Pagination::ArrayConnection do
let(:nodes) { (1..10) }
subject(:connection) { described_class.new(nodes, max_page_size: 100) }
it_behaves_like 'a connection with collection methods'
it_behaves_like 'a redactable connection' do
let(:unwanted) { 5 }
end
end
......@@ -13,6 +13,12 @@ RSpec.describe Gitlab::Graphql::Pagination::ExternallyPaginatedArrayConnection d
described_class.new(all_nodes, { max_page_size: values.size }.merge(arguments))
end
it_behaves_like 'a connection with collection methods'
it_behaves_like 'a redactable connection' do
let(:unwanted) { 3 }
end
describe '#nodes' do
let(:paged_nodes) { connection.nodes }
......
......@@ -21,6 +21,13 @@ RSpec.describe Gitlab::Graphql::Pagination::Keyset::Connection do
Gitlab::Json.parse(Base64Bp.urlsafe_decode64(cursor))
end
it_behaves_like 'a connection with collection methods'
it_behaves_like 'a redactable connection' do
let_it_be(:projects) { create_list(:project, 2) }
let(:unwanted) { projects.second }
end
describe '#cursor_for' do
let(:project) { create(:project) }
let(:cursor) { connection.cursor_for(project) }
......
......@@ -6,4 +6,15 @@ RSpec.describe Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection
it 'subclasses from GraphQL::Relay::RelationConnection' do
expect(described_class.superclass).to eq GraphQL::Pagination::ActiveRecordRelationConnection
end
it_behaves_like 'a connection with collection methods' do
let(:connection) { described_class.new(Project.all) }
end
it_behaves_like 'a redactable connection' do
let_it_be(:users) { create_list(:user, 2) }
let(:connection) { described_class.new(User.all, max_page_size: 10) }
let(:unwanted) { users.second }
end
end
# frozen_string_literal: true
# requires:
# - `connection` (no-empty, containing `unwanted` and at least one more item)
# - `unwanted` (single item in collection)
RSpec.shared_examples 'a redactable connection' do
context 'no redactor set' do
it 'contains the unwanted item' do
expect(connection.nodes).to include(unwanted)
end
it 'does not redact more than once' do
connection.nodes
r_state = connection.send(:redaction_state)
expect(r_state.redacted { raise 'Should not be called!' }).to be_present
end
end
let_it_be(:constant_redactor) do
Class.new do
def initialize(remove)
@remove = remove
end
def redact(items)
items - @remove
end
end
end
context 'redactor is set' do
let(:redactor) do
constant_redactor.new([unwanted])
end
before do
connection.redactor = redactor
end
it 'does not contain the unwanted item' do
expect(connection.nodes).not_to include(unwanted)
expect(connection.nodes).not_to be_empty
end
it 'does not redact more than once' do
expect(redactor).to receive(:redact).once.and_call_original
connection.nodes
connection.nodes
connection.nodes
end
end
end
# frozen_string_literal: true
RSpec.shared_examples 'a connection with collection methods' do
%i[to_a size include? empty?].each do |method_name|
it "responds to #{method_name}" do
expect(connection).to respond_to(method_name)
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