diff options
author | Rémy Coutable <remy@rymai.me> | 2017-02-08 12:43:58 +0000 |
---|---|---|
committer | Rémy Coutable <remy@rymai.me> | 2017-02-08 12:43:58 +0000 |
commit | 0fc474291eb84a8a128baea743c54ec661c2d44b (patch) | |
tree | b2ddf56e23ecdeb8ab3018272869961731ac9803 | |
parent | 40704daad839b7a9744b8d9c9ebc82e56ac452a1 (diff) | |
parent | e217bb21a6c87b8599cb0ba1636928c2bd0ca814 (diff) | |
download | gitlab-ce-0fc474291eb84a8a128baea743c54ec661c2d44b.tar.gz |
Merge branch 'feature/gb/paginated-environments-api' into 'master'
Expose paginated environments list API endpoint
See merge request !8928
-rw-r--r-- | app/models/ci/build.rb | 2 | ||||
-rw-r--r-- | app/serializers/environment_serializer.rb | 47 | ||||
-rw-r--r-- | app/serializers/pipeline_serializer.rb | 40 | ||||
-rw-r--r-- | lib/gitlab/serializer/ci/variables.rb (renamed from lib/gitlab/serialize/ci/variables.rb) | 2 | ||||
-rw-r--r-- | lib/gitlab/serializer/pagination.rb | 36 | ||||
-rw-r--r-- | spec/lib/gitlab/serializer/ci/variables_spec.rb (renamed from spec/lib/gitlab/serialize/ci/variables_spec.rb) | 2 | ||||
-rw-r--r-- | spec/lib/gitlab/serializer/pagination_spec.rb | 49 | ||||
-rw-r--r-- | spec/serializers/environment_serializer_spec.rb | 132 | ||||
-rw-r--r-- | spec/serializers/pipeline_serializer_spec.rb | 6 |
9 files changed, 282 insertions, 34 deletions
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 5213ea9d02b..8c1b076c2d7 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -20,7 +20,7 @@ module Ci end serialize :options - serialize :yaml_variables, Gitlab::Serialize::Ci::Variables + serialize :yaml_variables, Gitlab::Serializer::Ci::Variables validates :coverage, numericality: true, allow_blank: true validates_presence_of :ref diff --git a/app/serializers/environment_serializer.rb b/app/serializers/environment_serializer.rb index 91955542f25..fe16a3784c4 100644 --- a/app/serializers/environment_serializer.rb +++ b/app/serializers/environment_serializer.rb @@ -1,3 +1,50 @@ class EnvironmentSerializer < BaseSerializer + Item = Struct.new(:name, :size, :latest) + 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 diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb index b2de6c5832e..2bc6cf3266e 100644 --- a/app/serializers/pipeline_serializer.rb +++ b/app/serializers/pipeline_serializer.rb @@ -1,41 +1,25 @@ class PipelineSerializer < BaseSerializer class InvalidResourceError < StandardError; end - include API::Helpers::Pagination - Struct.new('Pagination', :request, :response) entity PipelineEntity - def represent(resource, opts = {}) - if paginated? - raise InvalidResourceError unless resource.respond_to?(:page) - - super(paginate(resource.includes(project: :namespace)), opts) - else - super(resource, opts) - end - end - - def paginated? - defined?(@pagination) - end - def with_pagination(request, response) - tap { @pagination = Struct::Pagination.new(request, response) } + tap { @paginator = Gitlab::Serializer::Pagination.new(request, response) } end - private - - # Methods needed by `API::Helpers::Pagination` - # - def params - @pagination.request.query_parameters + def paginated? + @paginator.present? end - def request - @pagination.request - end + def represent(resource, opts = {}) + if resource.is_a?(ActiveRecord::Relation) + resource = resource.includes(project: :namespace) + end - def header(header, value) - @pagination.response.headers[header] = value + if paginated? + super(@paginator.paginate(resource), opts) + else + super(resource, opts) + end end end diff --git a/lib/gitlab/serialize/ci/variables.rb b/lib/gitlab/serializer/ci/variables.rb index 3a9443bfcd9..c059c454eac 100644 --- a/lib/gitlab/serialize/ci/variables.rb +++ b/lib/gitlab/serializer/ci/variables.rb @@ -1,5 +1,5 @@ module Gitlab - module Serialize + module Serializer module Ci # This serializer could make sure our YAML variables' keys and values # are always strings. This is more for legacy build data because diff --git a/lib/gitlab/serializer/pagination.rb b/lib/gitlab/serializer/pagination.rb new file mode 100644 index 00000000000..bf2c0acc729 --- /dev/null +++ b/lib/gitlab/serializer/pagination.rb @@ -0,0 +1,36 @@ +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 diff --git a/spec/lib/gitlab/serialize/ci/variables_spec.rb b/spec/lib/gitlab/serializer/ci/variables_spec.rb index 7ea74da5252..b810c68ea03 100644 --- a/spec/lib/gitlab/serialize/ci/variables_spec.rb +++ b/spec/lib/gitlab/serializer/ci/variables_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Serialize::Ci::Variables do +describe Gitlab::Serializer::Ci::Variables do subject do described_class.load(described_class.dump(object)) end diff --git a/spec/lib/gitlab/serializer/pagination_spec.rb b/spec/lib/gitlab/serializer/pagination_spec.rb new file mode 100644 index 00000000000..519eb1b274f --- /dev/null +++ b/spec/lib/gitlab/serializer/pagination_spec.rb @@ -0,0 +1,49 @@ +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 diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb index 3c37660885d..1b95f1ff198 100644 --- a/spec/serializers/environment_serializer_spec.rb +++ b/spec/serializers/environment_serializer_spec.rb @@ -52,4 +52,136 @@ describe EnvironmentSerializer do expect(json).to be_an_instance_of Array 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 diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index 7cbf131e41e..2aaef03cb93 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -52,14 +52,14 @@ describe PipelineSerializer do expect(serializer).to be_paginated 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 let(:resource) { create(:ci_empty_pipeline) } let(:pagination) { { page: 1, per_page: 1 } } it 'raises error' do - expect { subject } - .to raise_error(PipelineSerializer::InvalidResourceError) + expect { subject }.to raise_error( + Gitlab::Serializer::Pagination::InvalidResourceError) end end end |