craftcms / cms

Build bespoke content experiences with Craft.
https://craftcms.com
Other
3.27k stars 635 forks source link

[FR] GraphQL: Return pagination information in query results #4847

Closed daltonrooney closed 4 years ago

daltonrooney commented 5 years ago

In CraftQL, we can include a request for the total count and other pagination information along with our main query. The query format looks like this:

{
  entriesConnection {
    totalCount
    pageInfo {
      hasPreviousPage
      hasNextPage
      currentPage
      totalPages
      first
      last
    }
    entries {
      ...on Posts {
        title
        body
      }
    }
  }
}

At the very least, I can use totalCount plus the limit & current offset params to build a pagination component, but without totalCount I'm kind of stuck. I'm wondering if there's an equivalent in the new Craft GraphQL implementation or plans to add a similar feature.

andris-sevcenko commented 5 years ago

@daltonrooney right, so the connection pattern was dropped because the whole edge pagination model relies on paging by cursor which is something that Craft does not support currently.

I'll look into adding totalCount for relational fields.

narration-sd commented 5 years ago

Ah, that's why I noted CraftQL didn't support cursors or passing either...

On Wed, Aug 28, 2019, 23:31 Andris Sevcenko notifications@github.com wrote:

@daltonrooney https://github.com/daltonrooney right, so the connection pattern was dropped because the whole edge pagination model relies on paging by cursor which is something that Craft does not support currently.

I'll look into adding totalCount for relational fields.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/craftcms/cms/issues/4847?email_source=notifications&email_token=AAB4RCNOT5VFNV5JDE3WYO3QG5UN5A5CNFSM4IR5VR6KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD5NM6NA#issuecomment-526044980, or mute the thread https://github.com/notifications/unsubscribe-auth/AAB4RCM5L3SUE6NN2RGEKKDQG5UN5ANCNFSM4IR5VR6A .

brandonkelly commented 5 years ago

@andris-sevcenko should probably just be count if that isn’t conflicting with something else; totalCount is a bit redundant.

monochromat13 commented 5 years ago

+1 on this. Just came across the same issue.

andris-sevcenko commented 5 years ago

This would require some adjustments to eager-loading mechanics for Craft, so we've slated this for 3.4.

andris-sevcenko commented 4 years ago

Thanks for the patience! This will be a part of the next 3.4 release!

andris-sevcenko commented 4 years ago

A heads up - the field has been renamed to _count, as fields starting with double underscore are reserved for introspection purposes in GraphQL.

weotch commented 4 years ago

Could you provide an example of how this should work? I'm trying this:

{
  blogs: entries(type:"blog") {
    id,
  }
  total: entry {
    _count(field: "blogs")
  }
}

And getting this result:

{
  "data": {
    "blogs": [
      {
        "id": "924"
      },
      {
        "id": "918"
      },
    ],
    "total": {
      "_count": 0
    }
  }
}

Thanks!

andris-sevcenko commented 4 years ago

Huh, looking into that @weotch

andris-sevcenko commented 4 years ago

@weotch

total: entry {
    _count(field: "blogs")
  }

This will just fetch a random entry and return the relation count on the field blogs for that entry. So it's a rather high chance there either there are no relations there or that the entry returned does not even have that field.

andris-sevcenko commented 4 years ago

@weotch never mind, found a bug on our end and I've pushed a fix for the next release.

To get the fix early, change your craftcms/cms requirement in composer.json to:

"require": {
  "craftcms/cms": "dev-develop#5b2dd5d86298f85ce7c4b574007a7360d0ae2e10 as 3.4.3",
  "...": "..."
}

Then run composer update.

weotch commented 4 years ago

Thanks! So with that change, the syntax from my example would be correct?

andris-sevcenko commented 4 years ago

Right, just keep in mind that querying for an entry without specifying any parameters like so

 total: entry {
    _count(field: "blogs")
  }

would return a rather random entry.

daltonrooney commented 4 years ago

@weotch Are you trying to get the total count for the entire query, or for some particular field within the results?

weotch commented 4 years ago

Are you trying to get the total count for the entire query, or for some particular field within the results?

I'm trying to get the total count of all entries in a particular channel (unaffected by limit), for the purpose of then forming pagination queries in the future. In my example, blog is a channel handle/slug.

weotch commented 4 years ago

@andris-sevcenko I updated to 5b2dd5d86298f85ce7c4b574007a7360d0ae2e10 but I'm seeing no change in the GraphQL response.

daltonrooney commented 4 years ago

I'm trying to get the total count of all entries in a particular channel (unaffected by limit), for the purpose of then forming pagination queries in the future.

Yeah, that's what I'm trying to do also, but I don't think that's how this feature was implemented. From the changelog: "Added the __count field to all Elements when using GraphQL that returns the total related elements for a field."

So this query:

query {
  entries(section:"pages") { 
    id
    title
    ...on pages_pages_Entry {
      partners {
        id
      }
      _count(field:"partners")
    }
  }
}

Returns this result:

{
  "data": {
    "entries": [
      {
        "id": "5",
        "title": "About",
        "partners": [
          {
            "id": "37"
          },
          {
            "id": "40"
          }
        ],
        "_count": 2
      },
      {
        "id": "37",
        "title": "Contact",
        "partners": [],
        "_count": 0
      },
      {
        "id": "40",
        "title": "Privacy Policy",
        "partners": [],
        "_count": 0
      }
    ]
  }
}

As far as I can tell, there's still no way to get a count of the total number of items that match the query, as in the CraftQL example I posted above.

brandonkelly commented 4 years ago

As far as I can tell, there's still no way to get a count of the total number of items that match the query, as in the CraftQL example I posted above.

That’s correct, we don’t currently have a way to return the total number of entries on the main query. Will reopen this, sounds like we need to look into that.

weotch commented 4 years ago

Thanks @brandonkelly!

andris-sevcenko commented 4 years ago

So, would you say that an entryCount with the same allowed arguments that entry queries have would suit your needs?

daltonrooney commented 4 years ago

@andris-sevcenko Yes that should work for us. Would it work like .count() and ignore any limit and offset params, just in case they get passed?

brandonkelly commented 4 years ago

@daltonrooney Since you wouldn’t want the total count to be returned for each individual entry returned by your entries query, it would need to be defined separately, like this:

{
  entries(section: "news", limit: 3) {
    title
  },
  total: entryCount(section: "news")
}

That would return something like

{
  "entries": [
    {
      "title": "Foo",
    },
    {
      "title": "Bar",
    },
    {
      "title": "Baz",
    }
  ],
  "total": 500
}
weotch commented 4 years ago

I like that solution.

daltonrooney commented 4 years ago

@brandonkelly Sure, that's what I was imagining, it's just that query params can get more complex (similar to @khalwat's example here), and we'd have to pass along one string with limit + offset params for the results page and another string without to get the total count. Not a big deal at all, can totally work around that.

andris-sevcenko commented 4 years ago

Alright, just pushed this for Craft 3.5.

@daltonrooney FWIW if you want you can still pass the limit and offset parameters to the count queries as they will just be ignored - this is in line with how you can pass them to an element query, but they are ignored if you finish the query by calling count().

jasondibenedetto commented 4 years ago

Just bumping this thread to query if there is a goto solution for pagination/count pre Craft 3.5?

andris-sevcenko commented 4 years ago

@jasondibenedetto Unfortunately, there's no current solution for 3.5. We'll discuss perhaps moving it from 3.5 to 3.4.current, but that depends on a number of factors. I'll keep you in the loop!

jasondibenedetto commented 4 years ago

Thanks @andris-sevcenko, I'll keep on the look out.

denisyilmaz commented 4 years ago

will this be part of Craft 3.5?

andris-sevcenko commented 4 years ago

@denisyilmaz yes.

mediabeastnz commented 3 years ago

Is there an example of how to paginate using GQL?

andris-sevcenko commented 3 years ago

You would have to build the pager component yourself, but, as far as queries go, you use the limit and offset arguments to specify what subset of the elements you wish to fetch.

daltonrooney commented 3 years ago

@mediabeastnz Here's an example of a working application with pagination that might be helpful: https://gist.github.com/daltonrooney/927a68196e915829349b7a087a1644a5

andris-sevcenko commented 3 years ago

Thanks, @daltonrooney, you're a 🌟!

toddpadwick commented 3 years ago

Hi @andris-sevcenko @daltonrooney Thanks for providing that repo and for the update here... but I'm still struggling to work out how to get the total possible offsets (pages) in my query? Could you elaborate on this a little for me?

I have a query that pulls in all my entries with a fixed limit set, and an offset based on the page param negative 1. I'd like to display pagination links for for each possible page of entries available. How do I add this to the query to return the maximum possible pages without having to introduce another query, as presumably, that would be costly on load and would negate the point in server-side pagination?)

query getEntries($type: [String], $limit: Int, $offset: Int) {
  entries(section: "publications", limit: $limit, offset: $offset) {
    slug
    id
    typeHandle
    sectionHandle
    postDate
    sectionId
    typeId
    uri
    url
    title
    <Insert page info here>
}
brandonkelly commented 3 years ago

@toddpadwick It wouldn’t make sense to include pagination info within every single result of your entries query, so instead you would do that with a separate entryCount query:

query getEntries($type: [String], $limit: Int, $offset: Int) {
  entries(section: "publications", limit: $limit, offset: $offset) {
    slug
    id
    typeHandle
    sectionHandle
    postDate
    sectionId
    typeId
    uri
    url
    title
  }
  entryCount(section: "publications")
}
toddpadwick commented 3 years ago

Okay, thanks @brandonkelly I've got that working nicely now :)

vitalijalbu commented 1 month ago

How about commerce?