Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add paging links to the API #15148

Merged
merged 9 commits into from
Jul 27, 2017
Merged

Add paging links to the API #15148

merged 9 commits into from
Jul 27, 2017

Conversation

jntullo
Copy link

@jntullo jntullo commented May 18, 2017

This adds paging links / page count in the following format if both limit and offset are specified in the GET request:

GET http://www.example.com/api/vms?offset=2&limit=2&sort_by=name&expand=resources
{
  "name"=>"vms",
 "count"=>7,
 "subcount"=>2,
 "pages"=>4,
 "resources"=>
  [
   {"href"=>"http://www.example.com/api/vms/10000000007254",
    "id"=>10000000007254,
    "vendor"=>"vmware",
    "name"=>"cc",
...
    "tenant_id"=>10000000007758
   },
   {"href"=>"http://www.example.com/api/vms/10000000007257",
    "id"=>10000000007257,
    "vendor"=>"vmware",
    "name"=>"dd",
...
    "tenant_id"=>10000000007758
   }
],
 "actions"=>[{"name"=>"query", "method"=>"post", "href"=>"http://www.example.com/api/vms"}],
 "links"=>
  {
   "self"=>"http://www.example.com/api/vms?offset=2&limit=2&sort_by=name&expand=resources",
   "next"=>"http://www.example.com/api/vms?offset=4&limit=2&sort_by=name&expand=resources",
   "previous"=>"http://www.example.com/api/vms?offset=0&limit=2&sort_by=name&expand=resources",
   "first"=>"http://www.example.com/api/vms?offset=0&limit=2&sort_by=name&expand=resources",
   "last"=>"http://www.example.com/api/vms?offset=6&limit=2&sort_by=name&expand=resources"
   }
}

If there is no previous (ie you are on first), there will be no previous link. This holds true for the other cases such as being on the last, etc.

Things still working on:

  • Additional tests
  • Ensuring that filtering works / makes sense: for filtering, it is necessary to have a subcount. currently, the counts we have are total count (which is the count on the collection) and subcount (number that are being returned), which is not sufficient. This PR does include a hotfix to get a subquery count, but am looking at a better way

@miq-bot add_label api, enhancement, wip
@miq-bot assign @abellotti
cc: @imtayadeway

@jntullo jntullo changed the title [WIP] Add paging links to the API Add paging links to the API May 23, 2017
@jntullo
Copy link
Author

jntullo commented May 23, 2017

@miq-bot remove_label wip

@miq-bot miq-bot removed the wip label May 23, 2017
Copy link
Contributor

@imtayadeway imtayadeway left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jntullo this is looking great! I just have some questions above. Feel free to 👖 if you need to discuss. Thanks!

@@ -0,0 +1,81 @@
module Api
class LinkBuilder
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you consider LinksBuilder for this? I think this could better convey intent, since it returns a thing comprised of links

@@ -0,0 +1,81 @@
module Api
class LinkBuilder
PAGING_LINKS = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you want s/paging/pagination/ here

end

def links
PAGING_LINKS.each_with_object({}) do |link, object|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about expressing this as a literal (over metaprogramming) here? Something like:

{
  :self => self,
  :next => next,
 # etc => etc
}.compact

format_href(prev_offset)
end

def self
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overriding self here may have unintended consequences....perhaps self_link?

end

def paging_count
@paging_count ||= subquery_count.nil? ? count : subquery_count
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT about the (slightly) shorter:

@paging_count ||= subquery_count || count

Jbuilder.new do |json|
json.ignore_nil!
[:name, :count, :subcount].each do |opt_name|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this disrupt the other caller of this method (expand_subcollection) which still sends a :count option?

:last
].freeze

attr_reader :offset, :limit, :href, :count, :subcount, :subquery_count
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find a consumer of the first reader - offset, outside the class. WDYT about giving this and similar attributes private readers instead? I think this would help make the responsibilities of this class clearer

@limit = params["limit"].to_i if params["limit"]
@href = href
return unless counts
@count = counts[:count]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary (but food for thought), the number of parameters needed by this object suggest that it might be helpful to Introduce Parameter Object

def previous
return if offset.zero?
prev_offset = offset - limit
return if prev_offset < 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to suggest that if I ask for page with limit 10, offset 5, I won't see a previous link showing the first five results. I think I'd expect to see a link (expressing limit 10, offset -5) though I'm not sure if that makes sense. @AllenBW any thoughts?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm yeah, this is a tough one. I guess I was just thinking first would cover this case, but perhaps they don't want that. Thoughts @AllenBW ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UGGGHHHH sorry for the delayed response to this, it totally didn't hit my 📻

showing all links no matter what offset would be besssssssst. the example case being... lets say you wanta make a list of the different pages (say 1 to 10) and when you click a number you see that cut of results, if we start with 5 we would wanta also be able to checkout 4, 3, etc etc

when ya say, @jntullo first would cover this case, watcha mean?

again so many apologies for my late reply here :( here have an I'm sorry 🌮

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AllenBW with the first link, the offset would be 0, at which point you'd be able to see 0..5. But I could see the case for wanting to continue with that same offset

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OOOOOO so collection of ten limit of 2 offset of 6, would you have links for offset of 4, 2 and 0? @jntullo

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, you would get previous which would be offset of 4, you would get first, which would be offset of 0, you'd get next which would be offset of 8

attr_reader :offset, :limit, :href, :count, :subcount, :subquery_count

def initialize(params, href, counts)
@offset = params["offset"].to_i if params["offset"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to pass in the params if you have what you need in the href arg, and that url needs to be parsed/rebuilt as part of this logic anyway?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@imtayadeway not a bad suggestion, the only reason why I am not entirely sold on it is the rebuilding part is a bit simpler than the parsing part, and if we already have those parameters, I would prefer to just use those rather than re-parsing it. If that makes sense? Open to discuss though.

@chessbyte
Copy link
Member

@jntullo bump

@miq-bot
Copy link
Member

miq-bot commented Jun 14, 2017

This pull request is not mergeable. Please rebase and repush.


Rbac.filtered(res, options)
def subquery_search(res, options)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not clear offhand what subquery_search does (as called from 179 if miq_expression.present), might need a better name

@limit = params["limit"].to_i if params["limit"]
@href = href
return unless counts
@counts = counts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe replace above 2 lines with

@counts = counts if counts

def last_href
return if (offset + limit) >= paging_count
last_offset = paging_count - (paging_count % limit)
format_href(last_offset)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while I can see us skipping previous and next (i.e you're in the first page already or you're in the last so no need to paginate further), I think we should always return the self, first and last href's.

@abellotti
Copy link
Member

This is really nice @jntullo
Minor fix needed,

  • for limit=12 offset=10, previous was limit=12 offset=-2, I expected that to be limit=12 offset=0 (i.e. same as first)

@abellotti
Copy link
Member

Thanks @jntullo for updating this PR. LGTM!! 😍

/cc @imtayadeway does this look good to you ? you have the X in the reviewers section.

@miq-bot
Copy link
Member

miq-bot commented Jul 25, 2017

Checked commits jntullo/manageiq@f10ea5a~...c4d59f6 with ruby 2.2.6, rubocop 0.47.1, and haml-lint 0.20.0
8 files checked, 4 offenses detected

app/controllers/api/base_controller/renderer.rb

@jntullo
Copy link
Author

jntullo commented Jul 25, 2017

Looks like the rubocop.yml was updated - there are a lot of those errors throughout renderer, will fix all of them separately.

@abellotti abellotti added this to the Sprint 66 Ending Aug 7, 2017 milestone Jul 27, 2017
@abellotti abellotti merged commit ad21f82 into ManageIQ:master Jul 27, 2017
@jntullo jntullo deleted the enhancement/paging branch November 28, 2017 19:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants