3

In sanity studio you get a nice list of the most recent version of all your documents. If there is a draft you get that, if not, you get the published one.

I need the same list for a few filters and scripts. The following groq does the job but is not very fast and does not work in the new API (v2021-03-25).

*[
  _type == $type &&
  !defined(*[_id == "drafts." + ^._id])
]._id

A way around the breaking changes in the API is to use length() = 0 in place of !defined() but that makes an already slow query 10-20 X slower.

Does anyone know a way of making filters that consider only the latest version?

Edit: An example where I need this is if I want to see all documents without any categories. Regardless whether it is the published document or the draft that has no categories it shows up in a normal filter. So if you add categories but don't immediately want to publish it will be confusing in the no-categories-list. ,'-)

2 Answers 2

6

100 X improvement on API v2021-03-25 🥳

The only way I was able to solve this with speed was to first make a projection of the sub-query so it doesn't run once for every non-draft. Then I thought, why not project both sets and then figure out the overlap, and that was even faster! It runs more than 10 x faster than possible on API v1 and 100 x faster than any suggestions for new API.

{
  'drafts': *[ _type == $type && _id in path("drafts.**") ]._id,
  'published': *[ _type == $type && !(_id in path("drafts.**"))]._id,
}
{
  'current': published[ !("drafts." + @ in ^.drafts) ] + drafts
}
  1. First I get both drafts and non-drafts and "store" it in this projection, like a variable-😉-ish
  2. Then I start with my non-drafts - published
  3. And filter out any that has a counterpart in my drafts "variable"
  4. Lastly I add all drafts to the my list of filtered non-drafts
3
  • 1
    This is an extremely interesting use-case of GROQ's projections, I'm amazed 🤯 Thanks for sharing, @cfm! I hope you managed to get the structure builder part of the challenge going, feel free to follow up here or in another question if otherwise 🙌 Apr 7, 2021 at 11:04
  • Yes, I think I can use this in the structure builder even though I don't have pagination. I just add my extra filters of no-categories or whatever at the end and it's less than 150 ms long. Only thing left is figuring out how to make a S.documentList() of my client.fetch(API-v-new) result. If you think it can be useful give me som upvotes :-D
    – cfm
    Apr 7, 2021 at 15:31
  • I think it'd require a list of S.documentListItem. Sanity will automatically load the preview for each, only when they come into view. Should be something like: S.list().title("").items(async () => const ids = await client.fetch(QUERY); return ids.map(id => S.documentListItem().id(id).schemaType(SCHEMA_TYPE)) Apr 9, 2021 at 18:58
1

Overall I think you're on the right track. Some ideas to help you out:

  1. Drafts are always fresher and newer than published documents, so if a given doc's id in path("drafts.**"), that's already the last updated one.
  2. Knowing the above allows you to skip the defined(*[_id == ...]) part of the query for drafts, speeding up your execution
  3. As drafts are already included, we can exclude published documents with a draft (defined(*[_id == "drafts." + ^._id][0]))
  4. Notice I added a [0] to the end of the query to pick only the first element that matches. This will improve performance slightly.
  5. For getting only documents that have no categories, use count(categoriesField) < 1
  6. Order documents with | order(_updatedAt desc) to get the freshest documents first
  7. And paginate your request to reduce the payload and speed things up.

Here's a sample query applying these principles (I haven't ran it, you may have to do some adjustments there):

*[
  _type == $type &&
  // Assuming you only want those without categories:
  count(categories) < 1 &&
  (
    // Is either a draft -> drafts are always fresher
    _id in path("drafts.**") ||
    // Or a published document with no draft
    !defined(*[_id == "drafts." + ^._id][0])

    // 👆 with the check above we're ensuring only
    // published documents run the expensive defined query
  )
]
// Order by last updated
| order(_updatedAt desc)
// Paginate for faster queries
[$paginationStart..$paginationEnd]
// Get only the _id, assuming that's what you want
._id

Hope this helps 🙌

1
  • Thank you for chiming in @henrique! Yes, makes sense to hide the inner query for drafts. And a lot of good principles in your post. Your query runs in both old and new API. However, I'm not sure how, or if I can, add pagination for a S.documentList() in the desk structure. And without pagination it's still too slow in the new API. The user will think the list is empty and move on.
    – cfm
    Apr 3, 2021 at 17:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.