Skip to content

Commit

Permalink
feat(APIv2): RHICOMPL-3956 align offset with standard expectations
Browse files Browse the repository at this point in the history
  • Loading branch information
RoamingNoMaD committed Sep 5, 2023
1 parent 19953b7 commit 3f6eb71
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 9 deletions.
2 changes: 1 addition & 1 deletion app/controllers/concerns/v2/collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module Collection

def resolve_collection
result = filter_by_tags(sort(search(policy_scope(expand_resource))))
result.paginate(page: pagination_offset, per_page: pagination_limit)
result.paginate(page: page, per_page: pagination_limit)
end

# :nocov:
Expand Down
120 changes: 120 additions & 0 deletions app/controllers/concerns/v2/metadata.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# frozen_string_literal: true

module V2
# Sets up the metadata to be passed fo fast_jsonapi to be
# added to our API responses
module Metadata
extend ActiveSupport::Concern

included do
before_action :set_headers

def set_headers
response.headers['Content-Type'] = 'application/vnd.api+json'
end

# This is part of a JSON schema, no need for strict metrics
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/AbcSize
def metadata(opts = {})
opts[:total] ||= count_collection

{
meta: {
total: opts[:total],
self.class::SEARCH => permitted_params[self.class::SEARCH],
tags: tags,
limit: pagination_limit,
offset: pagination_offset,
sort_by: permitted_params[:sort_by]
}.compact,
links: links(last_offset(opts[:total]))
}
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength

def links(last_offset)
{
first: meta_link(offset: 0),
last: meta_link(offset: last_offset),
previous: previous_link(last_offset),
next: next_link(last_offset)
}.compact
end

def path_prefix
request.fullpath[%r{(/(\w+))+/compliance}].sub(%r{/compliance}, '')
end

def previous_link(last_offset)
return unless pagination_offset.positive? && pagination_offset <= last_offset

meta_link(offset: previous_offset)
end

def next_link(last_offset)
return unless pagination_offset < last_offset

meta_link(offset: next_offset(last_offset))
end

def previous_offset
[(pagination_offset - pagination_limit), 0].max
end

def next_offset(last_offset)
return last_offset if (pagination_offset + pagination_limit) >= last_offset

pagination_offset + pagination_limit
end

def last_offset(total)
(total / pagination_limit.to_f).ceil
end

private

def count_collection
# Count the whole collection using a single column and not the whole table. This column
# by default is the primary key of the table, however, in certain cases using a different
# indexed column might produce faster results without even accessing the table.
resolve_collection.except(:select).select(resolve_collection.base_class.count_by).count
end

def base_link_url
api_version = request.fullpath.delete_prefix("#{path_prefix}/#{Settings.app_name}")
api_version.sub!(%r{/#{controller_name}.*}, '')
"#{path_prefix}/#{Settings.app_name}#{api_version}/#{controller_name}"
end

def base_link_params
{
self.class::SEARCH => permitted_params[self.class::SEARCH],
include: permitted_params[:include],
limit: pagination_limit,
tags: permitted_params[:tags],
sort_by: permitted_params[:sort_by]
}
end

def meta_link(other_params = {})
link_params = base_link_params.merge(other_params).compact
# As the tags aren't a "real" array, unfortunately, we have to do these
# kind of jugglings to build the URL properly
# rubocop:disable Style/RedundantRegexpArgument
[
base_link_url,
link_params.to_query
.sub(/^tag%5B%5D\=/, 'tags=')
.gsub(/\&tags%5B%5D\=/, '&tags=')
].join('?')
# rubocop:enable Style/RedundantRegexpArgument
end

def tags
TagFiltering.tags_supported?(resource) ? params.fetch(:tags, []) : nil
end
end
end
end
24 changes: 24 additions & 0 deletions app/controllers/concerns/v2/pagination.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

module V2
# Default options and configuration for pagination
module Pagination
extend ActiveSupport::Concern

ParamType = ActionController::Parameters

included do
def pagination_limit
permitted_params[:limit] || 10
end

def pagination_offset
permitted_params[:offset] || 0
end

def page
(pagination_offset / pagination_limit) + 1
end
end
end
end
2 changes: 1 addition & 1 deletion app/controllers/concerns/v2/parameter_handling.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def permitted_params_for_action(action, params)
included do
permitted_params_for_action :index, {
limit: ParamType.integer & ParamType.gt(0) & ParamType.lte(100),
offset: ParamType.integer & ParamType.gt(0),
offset: ParamType.integer & ParamType.gte(0),
sort_by: ParamType.array(ParamType.string) | ParamType.string,
self::SEARCH => ParamType.string
}
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/v2/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ class ApplicationController < ::ActionController::API
include ::Pundit::Authorization
include ::Authentication
include ::ExceptionNotifierCustomData
include ::Metadata
include ::Pagination
include V2::Metadata
include V2::Pagination
include V2::Collection
include ::Rendering
include V2::ParameterHandling
Expand Down
8 changes: 4 additions & 4 deletions spec/controllers/v2/concerns/paginable_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@

(1..(20.0 / per_page).ceil).each do |page|
it "returns #{per_page} records from the #{page.ordinalize} page" do
idx = ((per_page * (page - 1)))
nth_page = items[idx..(idx + per_page - 1)].map do |sg|
offset = (page - 1) * per_page
nth_page = items[offset..(offset + per_page - 1)].map do |sg|
hash_including(
'id' => sg.id,
'type' => subject.send(:resource).model_name.element,
Expand All @@ -39,13 +39,13 @@

get :index, params: extra_params.merge(
'limit' => per_page,
'offset' => page,
'offset' => offset,
parents: parents
)

expect(response_body_data).to match_array(nth_page)
expect(response.parsed_body['meta']['limit']).to eq(per_page)
expect(response.parsed_body['meta']['offset']).to eq(page)
expect(response.parsed_body['meta']['offset']).to eq(offset)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion spec/controllers/v2/concerns/with_metadata_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# ```
#
RSpec.shared_examples 'with metadata' do |*parents|
let(:meta_keys) { %w[total limit offset relationships] }
let(:meta_keys) { %w[total limit offset] }
let(:item_count) { 10 }
let(:extra_params) { {} }

Expand Down

0 comments on commit 3f6eb71

Please sign in to comment.