Commit 0fc47429 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'feature/gb/paginated-environments-api' into 'master'

Expose paginated environments list API endpoint

See merge request !8928
parents 40704daa e217bb21
...@@ -20,7 +20,7 @@ module Ci ...@@ -20,7 +20,7 @@ module Ci
end end
serialize :options serialize :options
serialize :yaml_variables, Gitlab::Serialize::Ci::Variables serialize :yaml_variables, Gitlab::Serializer::Ci::Variables
validates :coverage, numericality: true, allow_blank: true validates :coverage, numericality: true, allow_blank: true
validates_presence_of :ref validates_presence_of :ref
......
class EnvironmentSerializer < BaseSerializer class EnvironmentSerializer < BaseSerializer
Item = Struct.new(:name, :size, :latest)
entity EnvironmentEntity entity EnvironmentEntity
def within_folders
tap { @itemize = true }
end
def with_pagination(request, response)
tap { @paginator = Gitlab::Serializer::Pagination.new(request, response) }
end
def itemized?
@itemize
end
def paginated?
@paginator.present?
end
def represent(resource, opts = {})
resource = @paginator.paginate(resource) if paginated?
if itemized?
itemize(resource).map do |item|
{ name: item.name,
size: item.size,
latest: super(item.latest, opts) }
end
else
super(resource, opts)
end
end
private
def itemize(resource)
items = resource.group(:item_name).order('item_name ASC')
.pluck('COALESCE(environment_type, name) AS item_name',
'COUNT(*) AS environments_count',
'MAX(id) AS last_environment_id')
environments = resource.where(id: items.map(&:last)).index_by(&:id)
items.map do |name, size, id|
Item.new(name, size, environments[id])
end
end
end end
class PipelineSerializer < BaseSerializer class PipelineSerializer < BaseSerializer
class InvalidResourceError < StandardError; end class InvalidResourceError < StandardError; end
include API::Helpers::Pagination
Struct.new('Pagination', :request, :response)
entity PipelineEntity entity PipelineEntity
def represent(resource, opts = {}) def with_pagination(request, response)
if paginated? tap { @paginator = Gitlab::Serializer::Pagination.new(request, response) }
raise InvalidResourceError unless resource.respond_to?(:page)
super(paginate(resource.includes(project: :namespace)), opts)
else
super(resource, opts)
end
end end
def paginated? def paginated?
defined?(@pagination) @paginator.present?
end
def with_pagination(request, response)
tap { @pagination = Struct::Pagination.new(request, response) }
end end
private def represent(resource, opts = {})
if resource.is_a?(ActiveRecord::Relation)
# Methods needed by `API::Helpers::Pagination` resource = resource.includes(project: :namespace)
#
def params
@pagination.request.query_parameters
end end
def request if paginated?
@pagination.request super(@paginator.paginate(resource), opts)
else
super(resource, opts)
end end
def header(header, value)
@pagination.response.headers[header] = value
end end
end end
module Gitlab module Gitlab
module Serialize module Serializer
module Ci module Ci
# This serializer could make sure our YAML variables' keys and values # This serializer could make sure our YAML variables' keys and values
# are always strings. This is more for legacy build data because # are always strings. This is more for legacy build data because
......
module Gitlab
module Serializer
class Pagination
class InvalidResourceError < StandardError; end
include ::API::Helpers::Pagination
def initialize(request, response)
@request = request
@response = response
end
def paginate(resource)
if resource.respond_to?(:page)
super(resource)
else
raise InvalidResourceError
end
end
private
# Methods needed by `API::Helpers::Pagination`
#
attr_reader :request
def params
@request.query_parameters
end
def header(header, value)
@response.headers[header] = value
end
end
end
end
require 'spec_helper' require 'spec_helper'
describe Gitlab::Serialize::Ci::Variables do describe Gitlab::Serializer::Ci::Variables do
subject do subject do
described_class.load(described_class.dump(object)) described_class.load(described_class.dump(object))
end end
......
require 'spec_helper'
describe Gitlab::Serializer::Pagination do
let(:request) { spy('request') }
let(:response) { spy('response') }
let(:headers) { spy('headers') }
before do
allow(request).to receive(:query_parameters)
.and_return(params)
allow(response).to receive(:headers)
.and_return(headers)
end
let(:pagination) { described_class.new(request, response) }
describe '#paginate' do
subject { pagination.paginate(resource) }
let(:resource) { User.all }
let(:params) { { page: 1, per_page: 2 } }
context 'when a multiple resources are present in relation' do
before { create_list(:user, 3) }
it 'correctly paginates the resource' do
expect(subject.count).to be 2
end
it 'appends relevant headers' do
expect(headers).to receive(:[]=).with('X-Total', '3')
expect(headers).to receive(:[]=).with('X-Total-Pages', '2')
expect(headers).to receive(:[]=).with('X-Per-Page', '2')
subject
end
end
context 'when an invalid resource is about to be paginated' do
let(:resource) { create(:user) }
it 'raises error' do
expect { subject }.to raise_error(
described_class::InvalidResourceError)
end
end
end
end
...@@ -52,4 +52,136 @@ describe EnvironmentSerializer do ...@@ -52,4 +52,136 @@ describe EnvironmentSerializer do
expect(json).to be_an_instance_of Array expect(json).to be_an_instance_of Array
end end
end end
context 'when representing environments within folders' do
let(:serializer) do
described_class.new(project: project).within_folders
end
let(:resource) { Environment.all }
subject { serializer.represent(resource) }
context 'when there is a single environment' do
before { create(:environment, name: 'staging') }
it 'represents one standalone environment' do
expect(subject.count).to eq 1
expect(subject.first[:name]).to eq 'staging'
expect(subject.first[:size]).to eq 1
expect(subject.first[:latest][:name]).to eq 'staging'
end
end
context 'when there are multiple environments in folder' do
before do
create(:environment, name: 'staging/my-review-1')
create(:environment, name: 'staging/my-review-2')
end
it 'represents one item that is a folder' do
expect(subject.count).to eq 1
expect(subject.first[:name]).to eq 'staging'
expect(subject.first[:size]).to eq 2
expect(subject.first[:latest][:name]).to eq 'staging/my-review-2'
expect(subject.first[:latest][:environment_type]).to eq 'staging'
end
end
context 'when there are multiple folders and standalone environments' do
before do
create(:environment, name: 'staging/my-review-1')
create(:environment, name: 'staging/my-review-2')
create(:environment, name: 'production/my-review-3')
create(:environment, name: 'testing')
end
it 'represents multiple items grouped within folders' do
expect(subject.count).to eq 3
expect(subject.first[:name]).to eq 'production'
expect(subject.first[:size]).to eq 1
expect(subject.first[:latest][:name]).to eq 'production/my-review-3'
expect(subject.first[:latest][:environment_type]).to eq 'production'
expect(subject.second[:name]).to eq 'staging'
expect(subject.second[:size]).to eq 2
expect(subject.second[:latest][:name]).to eq 'staging/my-review-2'
expect(subject.second[:latest][:environment_type]).to eq 'staging'
expect(subject.third[:name]).to eq 'testing'
expect(subject.third[:size]).to eq 1
expect(subject.third[:latest][:name]).to eq 'testing'
expect(subject.third[:latest][:environment_type]).to be_nil
end
end
end
context 'when used with pagination' do
let(:request) { spy('request') }
let(:response) { spy('response') }
let(:resource) { Environment.all }
let(:pagination) { { page: 1, per_page: 2 } }
let(:serializer) do
described_class.new(project: project)
.with_pagination(request, response)
end
before do
allow(request).to receive(:query_parameters)
.and_return(pagination)
end
subject { serializer.represent(resource) }
it 'creates a paginated serializer' do
expect(serializer).to be_paginated
end
context 'when resource is paginatable relation' do
context 'when there is a single environment object in relation' do
before { create(:environment) }
it 'serializes environments' do
expect(subject.first).to have_key :id
end
end
context 'when multiple environment objects are serialized' do
before { create_list(:environment, 3) }
it 'serializes appropriate number of objects' do
expect(subject.count).to be 2
end
it 'appends relevant headers' do
expect(response).to receive(:[]=).with('X-Total', '3')
expect(response).to receive(:[]=).with('X-Total-Pages', '2')
expect(response).to receive(:[]=).with('X-Per-Page', '2')
subject
end
end
context 'when grouping environments within folders' do
let(:serializer) do
described_class.new(project: project)
.with_pagination(request, response)
.within_folders
end
before do
create(:environment, name: 'staging/review-1')
create(:environment, name: 'staging/review-2')
create(:environment, name: 'production/deploy-3')
create(:environment, name: 'testing')
end
it 'paginates grouped items including ordering' do
expect(subject.count).to eq 2
expect(subject.first[:name]).to eq 'production'
expect(subject.second[:name]).to eq 'staging'
end
end
end
end
end end
...@@ -52,14 +52,14 @@ describe PipelineSerializer do ...@@ -52,14 +52,14 @@ describe PipelineSerializer do
expect(serializer).to be_paginated expect(serializer).to be_paginated
end end
context 'when resource does is not paginatable' do context 'when resource is not paginatable' do
context 'when a single pipeline object is being serialized' do context 'when a single pipeline object is being serialized' do
let(:resource) { create(:ci_empty_pipeline) } let(:resource) { create(:ci_empty_pipeline) }
let(:pagination) { { page: 1, per_page: 1 } } let(:pagination) { { page: 1, per_page: 1 } }
it 'raises error' do it 'raises error' do
expect { subject } expect { subject }.to raise_error(
.to raise_error(PipelineSerializer::InvalidResourceError) Gitlab::Serializer::Pagination::InvalidResourceError)
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